1   /* Copyright 2002-2024 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.files.ccsds.utils.generation;
18  
19  import java.io.IOException;
20  import java.util.List;
21  
22  import org.orekit.files.ccsds.utils.FileFormat;
23  import org.orekit.utils.AccurateFormatter;
24  import org.orekit.utils.units.Unit;
25  
26  /** Generator for eXtended Markup Language CCSDS messages.
27   * @author Luc Maisonobe
28   * @since 11.0
29   */
30  public class XmlGenerator extends AbstractGenerator {
31  
32      /** Default number of space for each indentation level. */
33      public static final int DEFAULT_INDENT = 2;
34  
35      /** Name of the units attribute. */
36      public static final String UNITS = "units";
37  
38      /** NDM/XML version 3 location.
39       * @since 12.0
40       */
41      public static final String NDM_XML_V3_SCHEMA_LOCATION = "https://sanaregistry.org/r/ndmxml_unqualified/ndmxml-3.0.0-master-3.0.xsd";
42  
43      /** XML prolog. */
44      private static final String PROLOG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n";
45  
46      /** Root element start tag, with schema.
47       * @since 12.0
48       */
49      private static final String ROOT_START_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\" id=\"%s\" version=\"%.1f\">%n";
50  
51      /** Root element start tag, without schema.
52       * @since 12.0
53       */
54      private static final String ROOT_START_WITHOUT_SCHEMA = "<%s id=\"%s\" version=\"%.1f\">%n";
55  
56      /** Constant for XML schema instance.
57       * @since 12.0
58       */
59      private static final String XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
60  
61      /** Element end tag.
62       * @since 12.0
63       */
64      private static final String START_TAG_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\">%n";
65  
66      /** Element end tag.
67       * @since 12.0
68       */
69      private static final String START_TAG_WITHOUT_SCHEMA = "<%s>%n";
70  
71      /** Element end tag. */
72      private static final String END_TAG = "</%s>%n";
73  
74      /** Leaf element format without attributes. */
75      private static final String LEAF_0_ATTRIBUTES = "<%s>%s</%s>%n";
76  
77      /** Leaf element format with one attribute. */
78      private static final String LEAF_1_ATTRIBUTE = "<%s %s=\"%s\">%s</%s>%n";
79  
80      /** Leaf element format with two attributes. */
81      private static final String LEAF_2_ATTRIBUTES = "<%s %s=\"%s\" %s=\"%s\">%s</%s>%n";
82  
83      /** Comment key. */
84      private static final String COMMENT = "COMMENT";
85  
86      /** Schema location. */
87      private final String schemaLocation;
88  
89      /** Indentation size. */
90      private final int indentation;
91  
92      /** Nesting level. */
93      private int level;
94  
95      /** Simple constructor.
96       * @param output destination of generated output
97       * @param indentation number of space for each indentation level
98       * @param outputName output name for error messages
99       * @param maxRelativeOffset maximum offset in seconds to use relative dates
100      * (if a date is too far from reference, it will be displayed as calendar elements)
101      * @param writeUnits if true, units must be written
102      * @param schemaLocation schema location to use, may be null
103      * @see #DEFAULT_INDENT
104      * @see #NDM_XML_V3_SCHEMA_LOCATION
105      * @throws IOException if an I/O error occurs.
106      */
107     public XmlGenerator(final Appendable output, final int indentation,
108                         final String outputName, final double maxRelativeOffset,
109                         final boolean writeUnits, final String schemaLocation) throws IOException {
110         super(output, outputName, maxRelativeOffset, writeUnits);
111         this.schemaLocation = schemaLocation;
112         this.indentation    = indentation;
113         this.level          = 0;
114         writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, PROLOG));
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public FileFormat getFormat() {
120         return FileFormat.XML;
121     }
122 
123     /** {@inheritDoc} */
124     @Override
125     public void startMessage(final String root, final String messageTypeKey, final double version) throws IOException {
126         indent();
127         if (schemaLocation == null || level > 0) {
128             writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, ROOT_START_WITHOUT_SCHEMA,
129                                        root, messageTypeKey, version));
130         } else {
131             writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, ROOT_START_WITH_SCHEMA,
132                                        root, XMLNS_XSI, schemaLocation, messageTypeKey, version));
133         }
134         ++level;
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public void endMessage(final String root) throws IOException {
140         --level;
141         indent();
142         writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, END_TAG,
143                                    root));
144     }
145 
146     /** {@inheritDoc} */
147     @Override
148     public void writeComments(final List<String> comments) throws IOException {
149         for (final String comment : comments) {
150             writeEntry(COMMENT, comment, null, false);
151         }
152     }
153 
154     /** Write an element with one attribute.
155      * @param name tag name
156      * @param value element value
157      * @param attributeName attribute name
158      * @param attributeValue attribute value
159      * @throws IOException if an I/O error occurs.
160      */
161     public void writeOneAttributeElement(final String name, final String value,
162                                          final String attributeName, final String attributeValue)
163         throws IOException {
164         indent();
165         writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
166                                    name, attributeName, attributeValue, value, name));
167     }
168 
169     /** Write an element with two attributes.
170      * @param name tag name
171      * @param value element value
172      * @param attribute1Name attribute 1 name
173      * @param attribute1Value attribute 1 value
174      * @param attribute2Name attribute 2 name
175      * @param attribute2Value attribute 2 value
176      * @throws IOException if an I/O error occurs.
177      */
178     public void writeTwoAttributesElement(final String name, final String value,
179                                           final String attribute1Name, final String attribute1Value,
180                                           final String attribute2Name, final String attribute2Value)
181         throws IOException {
182         indent();
183         writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_2_ATTRIBUTES,
184                                    name,
185                                    attribute1Name, attribute1Value, attribute2Name, attribute2Value,
186                                    value, name));
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     public void writeEntry(final String key, final String value, final Unit unit, final boolean mandatory) throws IOException {
192         if (value == null) {
193             complain(key, mandatory);
194         } else {
195             indent();
196             if (writeUnits(unit)) {
197                 writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
198                                            key, UNITS, siToCcsdsName(unit.getName()), value, key));
199             } else {
200                 writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_0_ATTRIBUTES,
201                                            key, value, key));
202             }
203         }
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public void enterSection(final String name) throws IOException {
209         indent();
210         if (schemaLocation != null && level == 0) {
211             // top level tag for ndm messages (it is called before enterMessage)
212             writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, START_TAG_WITH_SCHEMA,
213                                        name, XMLNS_XSI, schemaLocation));
214         } else {
215             writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, START_TAG_WITHOUT_SCHEMA, name));
216         }
217         ++level;
218         super.enterSection(name);
219     }
220 
221     /** {@inheritDoc} */
222     @Override
223     public String exitSection() throws IOException {
224         final String name = super.exitSection();
225         --level;
226         indent();
227         writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, END_TAG, name));
228         return name;
229     }
230 
231     /** Indent line.
232      * @throws IOException if an I/O error occurs.
233      */
234     private void indent() throws IOException {
235         for (int i = 0; i < level * indentation; ++i) {
236             writeRawData(' ');
237         }
238     }
239 
240 }