XmlGenerator.java
/* Copyright 2002-2024 CS GROUP
* Licensed to CS GROUP (CS) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* CS licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.orekit.files.ccsds.utils.generation;
import java.io.IOException;
import java.util.List;
import org.orekit.files.ccsds.utils.FileFormat;
import org.orekit.utils.AccurateFormatter;
import org.orekit.utils.units.Unit;
/** Generator for eXtended Markup Language CCSDS messages.
* @author Luc Maisonobe
* @since 11.0
*/
public class XmlGenerator extends AbstractGenerator {
/** Default number of space for each indentation level. */
public static final int DEFAULT_INDENT = 2;
/** Name of the units attribute. */
public static final String UNITS = "units";
/** NDM/XML version 3 location.
* @since 12.0
*/
public static final String NDM_XML_V3_SCHEMA_LOCATION = "https://sanaregistry.org/r/ndmxml_unqualified/ndmxml-3.0.0-master-3.0.xsd";
/** XML prolog. */
private static final String PROLOG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>%n";
/** Root element start tag, with schema.
* @since 12.0
*/
private static final String ROOT_START_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\" id=\"%s\" version=\"%.1f\">%n";
/** Root element start tag, without schema.
* @since 12.0
*/
private static final String ROOT_START_WITHOUT_SCHEMA = "<%s id=\"%s\" version=\"%.1f\">%n";
/** Constant for XML schema instance.
* @since 12.0
*/
private static final String XMLNS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
/** Element end tag.
* @since 12.0
*/
private static final String START_TAG_WITH_SCHEMA = "<%s xmlns:xsi=\"%s\" xsi:noNamespaceSchemaLocation=\"%s\">%n";
/** Element end tag.
* @since 12.0
*/
private static final String START_TAG_WITHOUT_SCHEMA = "<%s>%n";
/** Element end tag. */
private static final String END_TAG = "</%s>%n";
/** Leaf element format without attributes. */
private static final String LEAF_0_ATTRIBUTES = "<%s>%s</%s>%n";
/** Leaf element format with one attribute. */
private static final String LEAF_1_ATTRIBUTE = "<%s %s=\"%s\">%s</%s>%n";
/** Leaf element format with two attributes. */
private static final String LEAF_2_ATTRIBUTES = "<%s %s=\"%s\" %s=\"%s\">%s</%s>%n";
/** Comment key. */
private static final String COMMENT = "COMMENT";
/** Schema location. */
private final String schemaLocation;
/** Indentation size. */
private final int indentation;
/** Nesting level. */
private int level;
/** Simple constructor.
* @param output destination of generated output
* @param indentation number of space for each indentation level
* @param outputName output name for error messages
* @param maxRelativeOffset maximum offset in seconds to use relative dates
* (if a date is too far from reference, it will be displayed as calendar elements)
* @param writeUnits if true, units must be written
* @param schemaLocation schema location to use, may be null
* @see #DEFAULT_INDENT
* @see #NDM_XML_V3_SCHEMA_LOCATION
* @throws IOException if an I/O error occurs.
*/
public XmlGenerator(final Appendable output, final int indentation,
final String outputName, final double maxRelativeOffset,
final boolean writeUnits, final String schemaLocation) throws IOException {
super(output, outputName, maxRelativeOffset, writeUnits);
this.schemaLocation = schemaLocation;
this.indentation = indentation;
this.level = 0;
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, PROLOG));
}
/** {@inheritDoc} */
@Override
public FileFormat getFormat() {
return FileFormat.XML;
}
/** {@inheritDoc} */
@Override
public void startMessage(final String root, final String messageTypeKey, final double version) throws IOException {
indent();
if (schemaLocation == null || level > 0) {
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, ROOT_START_WITHOUT_SCHEMA,
root, messageTypeKey, version));
} else {
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, ROOT_START_WITH_SCHEMA,
root, XMLNS_XSI, schemaLocation, messageTypeKey, version));
}
++level;
}
/** {@inheritDoc} */
@Override
public void endMessage(final String root) throws IOException {
--level;
indent();
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, END_TAG,
root));
}
/** {@inheritDoc} */
@Override
public void writeComments(final List<String> comments) throws IOException {
for (final String comment : comments) {
writeEntry(COMMENT, comment, null, false);
}
}
/** Write an element with one attribute.
* @param name tag name
* @param value element value
* @param attributeName attribute name
* @param attributeValue attribute value
* @throws IOException if an I/O error occurs.
*/
public void writeOneAttributeElement(final String name, final String value,
final String attributeName, final String attributeValue)
throws IOException {
indent();
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
name, attributeName, attributeValue, value, name));
}
/** Write an element with two attributes.
* @param name tag name
* @param value element value
* @param attribute1Name attribute 1 name
* @param attribute1Value attribute 1 value
* @param attribute2Name attribute 2 name
* @param attribute2Value attribute 2 value
* @throws IOException if an I/O error occurs.
*/
public void writeTwoAttributesElement(final String name, final String value,
final String attribute1Name, final String attribute1Value,
final String attribute2Name, final String attribute2Value)
throws IOException {
indent();
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_2_ATTRIBUTES,
name,
attribute1Name, attribute1Value, attribute2Name, attribute2Value,
value, name));
}
/** {@inheritDoc} */
@Override
public void writeEntry(final String key, final String value, final Unit unit, final boolean mandatory) throws IOException {
if (value == null) {
complain(key, mandatory);
} else {
indent();
if (writeUnits(unit)) {
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_1_ATTRIBUTE,
key, UNITS, siToCcsdsName(unit.getName()), value, key));
} else {
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, LEAF_0_ATTRIBUTES,
key, value, key));
}
}
}
/** {@inheritDoc} */
@Override
public void enterSection(final String name) throws IOException {
indent();
if (schemaLocation != null && level == 0) {
// top level tag for ndm messages (it is called before enterMessage)
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, START_TAG_WITH_SCHEMA,
name, XMLNS_XSI, schemaLocation));
} else {
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, START_TAG_WITHOUT_SCHEMA, name));
}
++level;
super.enterSection(name);
}
/** {@inheritDoc} */
@Override
public String exitSection() throws IOException {
final String name = super.exitSection();
--level;
indent();
writeRawData(String.format(AccurateFormatter.STANDARDIZED_LOCALE, END_TAG, name));
return name;
}
/** Indent line.
* @throws IOException if an I/O error occurs.
*/
private void indent() throws IOException {
for (int i = 0; i < level * indentation; ++i) {
writeRawData(' ');
}
}
}