XmlLexicalAnalyzer.java

  1. /* Copyright 2002-2022 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.lexical;

  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.Reader;
  21. import java.util.Map;

  22. import javax.xml.parsers.ParserConfigurationException;
  23. import javax.xml.parsers.SAXParser;
  24. import javax.xml.parsers.SAXParserFactory;

  25. import org.hipparchus.exception.DummyLocalizable;
  26. import org.orekit.data.DataSource;
  27. import org.orekit.errors.OrekitException;
  28. import org.orekit.errors.OrekitMessages;
  29. import org.orekit.files.ccsds.utils.FileFormat;
  30. import org.xml.sax.Attributes;
  31. import org.xml.sax.InputSource;
  32. import org.xml.sax.Locator;
  33. import org.xml.sax.SAXException;
  34. import org.xml.sax.helpers.DefaultHandler;

  35. /** Lexical analyzer for XML CCSDS messages.
  36.  * @author Maxime Journot
  37.  * @author Luc Maisonobe
  38.  * @since 11.0
  39.  */
  40. public class XmlLexicalAnalyzer implements LexicalAnalyzer {

  41.     /** Source providing the data to analyze. */
  42.     private final DataSource source;

  43.     /** Simple constructor.
  44.      * @param source source providing the data to parse
  45.      */
  46.     public XmlLexicalAnalyzer(final DataSource source) {
  47.         this.source = source;
  48.     }

  49.     /** {@inheritDoc} */
  50.     @Override
  51.     public <T> T accept(final MessageParser<T> messageParser) {
  52.         try {
  53.             // Create the handler
  54.             final DefaultHandler handler = new XMLHandler(messageParser);

  55.             // Create the XML SAX parser factory
  56.             final SAXParserFactory factory = SAXParserFactory.newInstance();

  57.             // Build the parser
  58.             final SAXParser saxParser = factory.newSAXParser();

  59.             // Read the xml file
  60.             messageParser.reset(FileFormat.XML);
  61.             final DataSource.Opener opener = source.getOpener();
  62.             if (opener.rawDataIsBinary()) {
  63.                 try (InputStream is = opener.openStreamOnce()) {
  64.                     if (is == null) {
  65.                         throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
  66.                     }
  67.                     saxParser.parse(new InputSource(is), handler);
  68.                 }
  69.             } else {
  70.                 try (Reader reader = opener.openReaderOnce()) {
  71.                     if (reader == null) {
  72.                         throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_FILE, source.getName());
  73.                     }
  74.                     saxParser.parse(new InputSource(reader), handler);
  75.                 }
  76.             }

  77.             // Get the content of the file
  78.             return messageParser.build();

  79.         } catch (SAXException | ParserConfigurationException | IOException e) {
  80.             // throw caught exception as an OrekitException
  81.             throw new OrekitException(e, new DummyLocalizable(e.getMessage()));
  82.         }
  83.     }

  84.     /** Handler for parsing XML file formats.
  85.      */
  86.     private class XMLHandler extends DefaultHandler {

  87.         /** CCSDS Message parser to use. */
  88.         private final MessageParser<?> messageParser;

  89.         /** Builder for regular elements. */
  90.         private final XmlTokenBuilder regularBuilder;

  91.         /** Builders for special elements. */
  92.         private Map<String, XmlTokenBuilder> specialElements;

  93.         /** Locator used to get current line number. */
  94.         private Locator locator;

  95.         /** Name of the current element. */
  96.         private String currentElementName;

  97.         /** Line number of the current entry. */
  98.         private int currentLineNumber;

  99.         /** Content of the current entry. */
  100.         private String currentContent;

  101.         /** Attributes of the current element. */
  102.         private Attributes currentAttributes;

  103.         /** Simple constructor.
  104.          * @param messageParser CCSDS Message parser to use
  105.          */
  106.         XMLHandler(final MessageParser<?> messageParser) {
  107.             this.messageParser   = messageParser;
  108.             this.regularBuilder  = new RegularXmlTokenBuilder();
  109.             this.specialElements = messageParser.getSpecialXmlElementsBuilders();
  110.         }

  111.         /** Get a builder for the current element.
  112.          * @param qName XML element ualified name
  113.          * @return builder for this element
  114.          */
  115.         private XmlTokenBuilder getBuilder(final String qName) {
  116.             final XmlTokenBuilder specialBuilder = specialElements.get(qName);
  117.             return (specialBuilder != null) ? specialBuilder : regularBuilder;
  118.         }

  119.         /** {@inheritDoc} */
  120.         @Override
  121.         public void setDocumentLocator(final Locator documentLocator) {
  122.             this.locator = documentLocator;
  123.         }

  124.         /** {@inheritDoc} */
  125.         @Override
  126.         public void characters(final char[] ch, final int start, final int length) throws SAXException {
  127.             // we are only interested in leaf elements between one start and one end tag
  128.             // when nested elements occur, this method is called with the spurious whitespace
  129.             // characters (space, tab, end of line) that occur between two successive start
  130.             // tags, two successive end tags, or one end tag and the following start tag of
  131.             // next element at same level.
  132.             // We need to identify the characters we want and the characters we drop.

  133.             // check if we are after a start tag (thus already dropping the characters
  134.             // between and end tag and a following start or end tag)
  135.             if (currentElementName != null) {
  136.                 // we are after a start tag, we don't know yet if the next tag will be
  137.                 // another start tag (in which case we ignore the characters) or if
  138.                 // it is the end tag of a leaf element, so we just store the characters
  139.                 // and will either use them or drop them when this next tag is seen
  140.                 currentLineNumber = locator.getLineNumber();
  141.                 currentContent    = new String(ch, start, length);
  142.             }
  143.         }

  144.         /** {@inheritDoc} */
  145.         @Override
  146.         public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) {

  147.             currentElementName = qName;
  148.             currentAttributes  = attributes;
  149.             currentLineNumber  = locator.getLineNumber();
  150.             currentContent     = null;

  151.             for (final ParseToken token : getBuilder(qName).
  152.                                           buildTokens(true, qName, currentContent, currentAttributes,
  153.                                           currentLineNumber, source.getName())) {
  154.                 messageParser.process(token);
  155.             }

  156.         }

  157.         /** {@inheritDoc} */
  158.         @Override
  159.         public void endElement(final String uri, final String localName, final String qName) {

  160.             if (currentContent == null) {
  161.                 // for an end tag without content, we keep the line number of the end tag itself
  162.                 currentLineNumber = locator.getLineNumber();
  163.             }

  164.             for (final ParseToken token : getBuilder(qName).
  165.                                           buildTokens(false, qName, currentContent, currentAttributes,
  166.                                                       currentLineNumber, source.getName())) {
  167.                 messageParser.process(token);
  168.             }

  169.             currentElementName = null;
  170.             currentLineNumber  = -1;
  171.             currentContent     = null;

  172.         }

  173.         /** {@inheritDoc} */
  174.         @Override
  175.         public InputSource resolveEntity(final String publicId, final String systemId) {
  176.             // disable external entities
  177.             return new InputSource();
  178.         }

  179.     }

  180. }