XmlGenerator.java

  1. /* Copyright 2002-2025 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. import java.io.IOException;
  19. import java.util.List;

  20. import org.orekit.files.ccsds.utils.FileFormat;
  21. import org.orekit.utils.AccurateFormatter;
  22. import org.orekit.utils.Formatter;
  23. import org.orekit.utils.units.Unit;

  24. /** Generator for eXtended Markup Language CCSDS messages.
  25.  * @author Luc Maisonobe
  26.  * @since 11.0
  27.  */
  28. public class XmlGenerator extends AbstractGenerator {

  29.     /** Default number of space for each indentation level. */
  30.     public static final int DEFAULT_INDENT = 2;

  31.     /** Name of the units attribute. */
  32.     public static final String UNITS = "units";

  33.     /** NDM/XML version 3 location.
  34.      * @since 12.0
  35.      */
  36.     public static final String NDM_XML_V3_SCHEMA_LOCATION = "https://sanaregistry.org/r/ndmxml_unqualified/ndmxml-3.0.0-master-3.0.xsd";

  37.     /** XML prolog. */
  38.     private static final String PROLOG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n";

  39.     /** Root element start tag, with schema.
  40.      * @since 12.0
  41.      */
  42.     private static final String ROOT_START_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\" id=\"%s\" version=\"%.1f\">%n";

  43.     /** Root element start tag, without schema.
  44.      * @since 12.0
  45.      */
  46.     private static final String ROOT_START_WITHOUT_SCHEMA = "<%s id=\"%s\" version=\"%.1f\">%n";

  47.     /** Constant for XML schema instance.
  48.      * @since 12.0
  49.      */
  50.     private static final String XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance";

  51.     /** Element end tag.
  52.      * @since 12.0
  53.      */
  54.     private static final String START_TAG_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\">%n";

  55.     /** Element end tag.
  56.      * @since 12.0
  57.      */
  58.     private static final String START_TAG_WITHOUT_SCHEMA = "<%s>%n";

  59.     /** Element end tag. */
  60.     private static final String END_TAG = "</%s>%n";

  61.     /** Leaf element format without attributes. */
  62.     private static final String LEAF_0_ATTRIBUTES = "<%s>%s</%s>%n";

  63.     /** Leaf element format with one attribute. */
  64.     private static final String LEAF_1_ATTRIBUTE = "<%s %s=\"%s\">%s</%s>%n";

  65.     /** Leaf element format with two attributes. */
  66.     private static final String LEAF_2_ATTRIBUTES = "<%s %s=\"%s\" %s=\"%s\">%s</%s>%n";

  67.     /** Comment key. */
  68.     private static final String COMMENT = "COMMENT";

  69.     /** Schema location. */
  70.     private final String schemaLocation;

  71.     /** Indentation size. */
  72.     private final int indentation;

  73.     /** Nesting level. */
  74.     private int level;

  75.     /** Simple constructor.
  76.      * @param output destination of generated output
  77.      * @param indentation number of space for each indentation level
  78.      * @param outputName output name for error messages
  79.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  80.      * (if a date is too far from reference, it will be displayed as calendar elements)
  81.      * @param formatter used to convert doubles and dates to string
  82.      * @param writeUnits if true, units must be written
  83.      * @param schemaLocation schema location to use, may be null
  84.      * @see #DEFAULT_INDENT
  85.      * @see #NDM_XML_V3_SCHEMA_LOCATION
  86.      * @throws IOException if an I/O error occurs.
  87.      */
  88.     public XmlGenerator(final Appendable output, final int indentation,
  89.                         final String outputName, final double maxRelativeOffset,
  90.                         final boolean writeUnits, final String schemaLocation,
  91.                         final Formatter formatter) throws IOException {
  92.         super(output, outputName, maxRelativeOffset, writeUnits, formatter);
  93.         this.schemaLocation = schemaLocation;
  94.         this.indentation    = indentation;
  95.         this.level          = 0;
  96.         writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, PROLOG));
  97.     }

  98.     /** Simple constructor.
  99.      * @param output destination of generated output
  100.      * @param indentation number of space for each indentation level
  101.      * @param outputName output name for error messages
  102.      * @param maxRelativeOffset maximum offset in seconds to use relative dates
  103.      * (if a date is too far from reference, it will be displayed as calendar elements)
  104.      * @param writeUnits if true, units must be written
  105.      * @param schemaLocation schema location to use, may be null
  106.      * @see #DEFAULT_INDENT
  107.      * @see #NDM_XML_V3_SCHEMA_LOCATION
  108.      * @throws IOException if an I/O error occurs.
  109.      */
  110.     public XmlGenerator(final Appendable output, final int indentation,
  111.                         final String outputName, final double maxRelativeOffset,
  112.                         final boolean writeUnits, final String schemaLocation) throws IOException {
  113.         this(output, indentation, outputName, maxRelativeOffset, writeUnits, schemaLocation, new AccurateFormatter());
  114.     }


  115.     /** {@inheritDoc} */
  116.     @Override
  117.     public FileFormat getFormat() {
  118.         return FileFormat.XML;
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     public void startMessage(final String root, final String messageTypeKey, final double version) throws IOException {
  123.         indent();
  124.         if (schemaLocation == null || level > 0) {
  125.             writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, ROOT_START_WITHOUT_SCHEMA,
  126.                                        root, messageTypeKey, version));
  127.         } else {
  128.             writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, ROOT_START_WITH_SCHEMA,
  129.                                        root, XMLNS_XSI, schemaLocation, messageTypeKey, version));
  130.         }
  131.         ++level;
  132.     }

  133.     /** {@inheritDoc} */
  134.     @Override
  135.     public void endMessage(final String root) throws IOException {
  136.         --level;
  137.         indent();
  138.         writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, END_TAG,
  139.                                    root));
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public void writeComments(final List<String> comments) throws IOException {
  144.         for (final String comment : comments) {
  145.             writeEntry(COMMENT, comment, null, false);
  146.         }
  147.     }

  148.     /** Write an element with one attribute.
  149.      * @param name tag name
  150.      * @param value element value
  151.      * @param attributeName attribute name
  152.      * @param attributeValue attribute value
  153.      * @throws IOException if an I/O error occurs.
  154.      */
  155.     public void writeOneAttributeElement(final String name, final String value,
  156.                                          final String attributeName, final String attributeValue)
  157.         throws IOException {
  158.         indent();
  159.         writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
  160.                                    name, attributeName, attributeValue, value, name));
  161.     }

  162.     /** Write an element with two attributes.
  163.      * @param name tag name
  164.      * @param value element value
  165.      * @param attribute1Name attribute 1 name
  166.      * @param attribute1Value attribute 1 value
  167.      * @param attribute2Name attribute 2 name
  168.      * @param attribute2Value attribute 2 value
  169.      * @throws IOException if an I/O error occurs.
  170.      */
  171.     public void writeTwoAttributesElement(final String name, final String value,
  172.                                           final String attribute1Name, final String attribute1Value,
  173.                                           final String attribute2Name, final String attribute2Value)
  174.         throws IOException {
  175.         indent();
  176.         writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, LEAF_2_ATTRIBUTES,
  177.                                    name,
  178.                                    attribute1Name, attribute1Value, attribute2Name, attribute2Value,
  179.                                    value, name));
  180.     }

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     public void writeEntry(final String key, final String value, final Unit unit, final boolean mandatory) throws IOException {
  184.         if (value == null) {
  185.             complain(key, mandatory);
  186.         } else {
  187.             indent();
  188.             if (writeUnits(unit)) {
  189.                 writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
  190.                                            key, UNITS, siToCcsdsName(unit.getName()), value, key));
  191.             } else {
  192.                 writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, LEAF_0_ATTRIBUTES,
  193.                                            key, value, key));
  194.             }
  195.         }
  196.     }

  197.     /** {@inheritDoc} */
  198.     @Override
  199.     public void enterSection(final String name) throws IOException {
  200.         indent();
  201.         if (schemaLocation != null && level == 0) {
  202.             // top level tag for ndm messages (it is called before enterMessage)
  203.             writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, START_TAG_WITH_SCHEMA,
  204.                                        name, XMLNS_XSI, schemaLocation));
  205.         } else {
  206.             writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, START_TAG_WITHOUT_SCHEMA, name));
  207.         }
  208.         ++level;
  209.         super.enterSection(name);
  210.     }

  211.     /** {@inheritDoc} */
  212.     @Override
  213.     public String exitSection() throws IOException {
  214.         final String name = super.exitSection();
  215.         --level;
  216.         indent();
  217.         writeRawData(String.format(Formatter.STANDARDIZED_LOCALE, END_TAG, name));
  218.         return name;
  219.     }

  220.     /** Indent line.
  221.      * @throws IOException if an I/O error occurs.
  222.      */
  223.     private void indent() throws IOException {
  224.         for (int i = 0; i < level * indentation; ++i) {
  225.             writeRawData(' ');
  226.         }
  227.     }

  228. }