Move YangLibraryConstants
[yangtools.git] / yang / yang-data-codec-xml / src / main / java / org / opendaylight / yangtools / yang / data / codec / xml / XmlParserStream.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.codec.xml;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12 import static com.google.common.base.Verify.verify;
13 import static java.util.Objects.requireNonNull;
14
15 import com.google.common.annotations.Beta;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.ImmutableMap;
18 import java.io.Closeable;
19 import java.io.Flushable;
20 import java.io.IOException;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.util.AbstractMap.SimpleImmutableEntry;
24 import java.util.Deque;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Optional;
32 import java.util.Set;
33 import javax.xml.XMLConstants;
34 import javax.xml.namespace.NamespaceContext;
35 import javax.xml.stream.XMLStreamConstants;
36 import javax.xml.stream.XMLStreamException;
37 import javax.xml.stream.XMLStreamReader;
38 import javax.xml.stream.util.StreamReaderDelegate;
39 import javax.xml.transform.TransformerException;
40 import javax.xml.transform.TransformerFactory;
41 import javax.xml.transform.TransformerFactoryConfigurationError;
42 import javax.xml.transform.dom.DOMResult;
43 import javax.xml.transform.dom.DOMSource;
44 import javax.xml.transform.stax.StAXSource;
45 import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
46 import org.opendaylight.yangtools.rfc7952.model.api.AnnotationSchemaNode;
47 import org.opendaylight.yangtools.rfc8528.data.api.YangLibraryConstants;
48 import org.opendaylight.yangtools.rfc8528.data.api.YangLibraryConstants.ContainerName;
49 import org.opendaylight.yangtools.rfc8528.model.api.MountPointSchemaNode;
50 import org.opendaylight.yangtools.yang.common.QName;
51 import org.opendaylight.yangtools.yang.common.QNameModule;
52 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
53 import org.opendaylight.yangtools.yang.data.util.AbstractMountPointDataWithSchema;
54 import org.opendaylight.yangtools.yang.data.util.AbstractNodeDataWithSchema;
55 import org.opendaylight.yangtools.yang.data.util.AnyXmlNodeDataWithSchema;
56 import org.opendaylight.yangtools.yang.data.util.AnydataNodeDataWithSchema;
57 import org.opendaylight.yangtools.yang.data.util.CompositeNodeDataWithSchema;
58 import org.opendaylight.yangtools.yang.data.util.ContainerNodeDataWithSchema;
59 import org.opendaylight.yangtools.yang.data.util.LeafListEntryNodeDataWithSchema;
60 import org.opendaylight.yangtools.yang.data.util.LeafListNodeDataWithSchema;
61 import org.opendaylight.yangtools.yang.data.util.LeafNodeDataWithSchema;
62 import org.opendaylight.yangtools.yang.data.util.ListEntryNodeDataWithSchema;
63 import org.opendaylight.yangtools.yang.data.util.ListNodeDataWithSchema;
64 import org.opendaylight.yangtools.yang.data.util.MountPointData;
65 import org.opendaylight.yangtools.yang.data.util.OperationAsContainer;
66 import org.opendaylight.yangtools.yang.data.util.ParserStreamUtils;
67 import org.opendaylight.yangtools.yang.data.util.SimpleNodeDataWithSchema;
68 import org.opendaylight.yangtools.yang.data.util.YangModeledAnyXmlNodeDataWithSchema;
69 import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
71 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
72 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
73 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
74 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
75 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
76 import org.opendaylight.yangtools.yang.model.api.Module;
77 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
78 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
79 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
80 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83 import org.w3c.dom.Document;
84 import org.xml.sax.SAXException;
85
86 /**
87  * This class provides functionality for parsing an XML source containing YANG-modeled data. It disallows multiple
88  * instances of the same element except for leaf-list and list entries. It also expects that the YANG-modeled data in
89  * the XML source are wrapped in a root element. This class is NOT thread-safe.
90  *
91  * <p>
92  * Due to backwards compatibility reasons, RFC7952 metadata emitted by this parser may include key QNames with empty URI
93  * (as exposed via {@link #LEGACY_ATTRIBUTE_NAMESPACE}) as their QNameModule. These indicate an unqualified XML
94  * attribute and their value can be assumed to be a String. Furthermore, this extends to qualified attributes, which
95  * uses the proper namespace, but will not bind to a proper module revision -- these need to be reconciled with a
96  * particular SchemaContext and are expected to either be fully decoded, or contain a String value. Handling of such
97  * annotations is at the discretion of the user encountering it: preferred way of handling is to either filter or
98  * normalize them to proper QNames/values when encountered. This caveat will be removed in a future version.
99  */
100 @Beta
101 public final class XmlParserStream implements Closeable, Flushable {
102     /**
103      * {@link QNameModule} for use with legacy XML attributes.
104      * @deprecated The use on this namespace is discouraged and users are strongly encouraged to proper RFC7952 metadata
105      *             annotations.
106      */
107     @Deprecated
108     public static final QNameModule LEGACY_ATTRIBUTE_NAMESPACE = QNameModule.create(URI.create("")).intern();
109
110     private static final Logger LOG = LoggerFactory.getLogger(XmlParserStream.class);
111     private static final String XML_STANDARD_VERSION = "1.0";
112     private static final String COM_SUN_TRANSFORMER =
113             "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
114
115     private static final TransformerFactory TRANSFORMER_FACTORY;
116
117     static {
118         TransformerFactory fa = TransformerFactory.newInstance();
119         if (!fa.getFeature(StAXSource.FEATURE)) {
120             LOG.warn("Platform-default TransformerFactory {} does not support StAXSource, attempting fallback to {}",
121                     fa, COM_SUN_TRANSFORMER);
122             fa = TransformerFactory.newInstance(COM_SUN_TRANSFORMER, null);
123             if (!fa.getFeature(StAXSource.FEATURE)) {
124                 throw new TransformerFactoryConfigurationError("No TransformerFactory supporting StAXResult found.");
125             }
126         }
127
128         TRANSFORMER_FACTORY = fa;
129     }
130
131     // Cache of nsUri Strings to QNameModules, as resolved in context
132     private final Map<String, Optional<QNameModule>> resolvedNamespaces = new HashMap<>();
133     // Cache of nsUri Strings to QNameModules, as inferred from document
134     private final Map<String, QNameModule> rawNamespaces = new HashMap<>();
135     private final NormalizedNodeStreamWriter writer;
136     private final XmlCodecFactory codecs;
137     private final DataSchemaNode parentNode;
138     private final boolean strictParsing;
139
140     private XmlParserStream(final NormalizedNodeStreamWriter writer, final XmlCodecFactory codecs,
141             final DataSchemaNode parentNode, final boolean strictParsing) {
142         this.writer = requireNonNull(writer);
143         this.codecs = requireNonNull(codecs);
144         this.parentNode = parentNode;
145         this.strictParsing = strictParsing;
146     }
147
148     /**
149      * Construct a new {@link XmlParserStream} with strict parsing mode switched on.
150      *
151      * @param writer Output writer
152      * @param codecs Shared codecs
153      * @param parentNode Parent root node
154      * @return A new stream instance
155      */
156     public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final XmlCodecFactory codecs,
157             final SchemaNode parentNode) {
158         return create(writer, codecs, parentNode, true);
159     }
160
161     /**
162      * Construct a new {@link XmlParserStream}.
163      *
164      * @param writer Output writer
165      * @param codecs Shared codecs
166      * @param parentNode Parent root node
167      * @param strictParsing parsing mode
168      *            if set to true, the parser will throw an exception if it encounters unknown child nodes
169      *            (nodes, that are not defined in the provided SchemaContext) in containers and lists
170      *            if set to false, the parser will skip unknown child nodes
171      * @return A new stream instance
172      */
173     public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final XmlCodecFactory codecs,
174             final SchemaNode parentNode, final boolean strictParsing) {
175         final DataSchemaNode parent;
176         if (parentNode instanceof DataSchemaNode) {
177             parent = (DataSchemaNode) parentNode;
178         } else if (parentNode instanceof OperationDefinition) {
179             parent = OperationAsContainer.of((OperationDefinition) parentNode);
180         } else {
181             throw new IllegalArgumentException("Illegal parent node " + parentNode);
182         }
183         return new XmlParserStream(writer, codecs, parent, strictParsing);
184     }
185
186     /**
187      * Utility method for use when caching {@link XmlCodecFactory} is not feasible. Users with high performance
188      * requirements should use {@link #create(NormalizedNodeStreamWriter, XmlCodecFactory, SchemaNode)} instead and
189      * maintain a {@link XmlCodecFactory} to match the current {@link SchemaContext}.
190      */
191     public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext,
192             final SchemaNode parentNode) {
193         return create(writer, schemaContext, parentNode, true);
194     }
195
196     /**
197      * Utility method for use when caching {@link XmlCodecFactory} is not feasible. Users with high performance
198      * requirements should use {@link #create(NormalizedNodeStreamWriter, XmlCodecFactory, SchemaNode)} instead and
199      * maintain a {@link XmlCodecFactory} to match the current {@link SchemaContext}.
200      */
201     public static XmlParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext,
202             final SchemaNode parentNode, final boolean strictParsing) {
203         return create(writer, XmlCodecFactory.create(schemaContext), parentNode, strictParsing);
204     }
205
206     /**
207      * This method parses the XML source and emits node events into a NormalizedNodeStreamWriter based on the
208      * YANG-modeled data contained in the XML source.
209      *
210      * @param reader
211      *              StAX reader which is to used to walk through the XML source
212      * @return
213      *              instance of XmlParserStream
214      * @throws XMLStreamException
215      *              if a well-formedness error or an unexpected processing condition occurs while parsing the XML
216      * @throws URISyntaxException
217      *              if the namespace URI of an XML element contains a syntax error
218      * @throws IOException
219      *              if an error occurs while parsing the value of an anyxml node
220      * @throws SAXException
221      *              if an error occurs while parsing the value of an anyxml node
222      */
223     public XmlParserStream parse(final XMLStreamReader reader) throws XMLStreamException, URISyntaxException,
224             IOException, SAXException {
225         if (reader.hasNext()) {
226             reader.nextTag();
227             final AbstractNodeDataWithSchema<?> nodeDataWithSchema;
228             if (parentNode instanceof ContainerSchemaNode) {
229                 nodeDataWithSchema = new ContainerNodeDataWithSchema((ContainerSchemaNode) parentNode);
230             } else if (parentNode instanceof ListSchemaNode) {
231                 nodeDataWithSchema = new ListNodeDataWithSchema((ListSchemaNode) parentNode);
232             } else if (parentNode instanceof YangModeledAnyXmlSchemaNode) {
233                 nodeDataWithSchema = new YangModeledAnyXmlNodeDataWithSchema((YangModeledAnyXmlSchemaNode) parentNode);
234             } else if (parentNode instanceof AnyXmlSchemaNode) {
235                 nodeDataWithSchema = new AnyXmlNodeDataWithSchema((AnyXmlSchemaNode) parentNode);
236             } else if (parentNode instanceof LeafSchemaNode) {
237                 nodeDataWithSchema = new LeafNodeDataWithSchema((LeafSchemaNode) parentNode);
238             } else if (parentNode instanceof LeafListSchemaNode) {
239                 nodeDataWithSchema = new LeafListNodeDataWithSchema((LeafListSchemaNode) parentNode);
240             } else if (parentNode instanceof AnyDataSchemaNode) {
241                 nodeDataWithSchema = new AnydataNodeDataWithSchema((AnyDataSchemaNode) parentNode);
242             } else {
243                 throw new IllegalStateException("Unsupported schema node type " + parentNode.getClass() + ".");
244             }
245
246             read(reader, nodeDataWithSchema, reader.getLocalName());
247             nodeDataWithSchema.write(writer);
248         }
249
250         return this;
251     }
252
253     /**
254      * This method traverses a {@link DOMSource} and emits node events into a NormalizedNodeStreamWriter based on the
255      * YANG-modeled data contained in the source.
256      *
257      * @param src
258      *              {@link DOMSource} to be traversed
259      * @return
260      *              instance of XmlParserStream
261      * @throws XMLStreamException
262      *              if a well-formedness error or an unexpected processing condition occurs while parsing the XML
263      * @throws URISyntaxException
264      *              if the namespace URI of an XML element contains a syntax error
265      * @throws IOException
266      *              if an error occurs while parsing the value of an anyxml node
267      * @throws SAXException
268      *              if an error occurs while parsing the value of an anyxml node
269      */
270     @Beta
271     public XmlParserStream traverse(final DOMSource src) throws XMLStreamException, URISyntaxException, IOException,
272             SAXException {
273         return parse(new DOMSourceXMLStreamReader(src));
274     }
275
276     private ImmutableMap<QName, Object> getElementAttributes(final XMLStreamReader in) {
277         checkState(in.isStartElement(), "Attributes can be extracted only from START_ELEMENT.");
278         final Map<QName, Object> attributes = new LinkedHashMap<>();
279
280         for (int attrIndex = 0; attrIndex < in.getAttributeCount(); attrIndex++) {
281             final String attributeNS = in.getAttributeNamespace(attrIndex);
282
283             // Skip namespace definitions
284             if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attributeNS)) {
285                 continue;
286             }
287
288             final String localName = in.getAttributeLocalName(attrIndex);
289             final String attrValue = in.getAttributeValue(attrIndex);
290             if (Strings.isNullOrEmpty(attributeNS)) {
291                 StreamWriterFacade.warnLegacyAttribute(localName);
292                 attributes.put(QName.create(LEGACY_ATTRIBUTE_NAMESPACE, localName), attrValue);
293                 continue;
294             }
295
296             // Cross-relate attribute namespace to the module
297             final Optional<QNameModule> optModule = resolveXmlNamespace(attributeNS);
298             if (optModule.isPresent()) {
299                 final QName qname = QName.create(optModule.get(), localName);
300                 final Optional<AnnotationSchemaNode> optAnnotation = AnnotationSchemaNode.find(
301                     codecs.getSchemaContext(), qname);
302                 if (optAnnotation.isPresent()) {
303                     final AnnotationSchemaNode schema = optAnnotation.get();
304                     final Object value = codecs.codecFor(schema).parseValue(in.getNamespaceContext(), attrValue);
305                     attributes.put(schema.getQName(), value);
306                     continue;
307                 }
308
309                 LOG.debug("Annotation for {} not found, using legacy QName", qname);
310             }
311
312             attributes.put(QName.create(rawXmlNamespace(attributeNS), localName), attrValue);
313         }
314
315         return ImmutableMap.copyOf(attributes);
316     }
317
318     private static Document readAnyXmlValue(final XMLStreamReader in) throws XMLStreamException {
319         // Underlying reader might return null when asked for version, however when such reader is plugged into
320         // Stax -> DOM transformer, it fails with NPE due to null version. Use default xml version in such case.
321         final XMLStreamReader inWrapper;
322         if (in.getVersion() == null) {
323             inWrapper = new StreamReaderDelegate(in) {
324                 @Override
325                 public String getVersion() {
326                     final String ver = super.getVersion();
327                     return ver != null ? ver : XML_STANDARD_VERSION;
328                 }
329             };
330         } else {
331             inWrapper = in;
332         }
333
334         final DOMResult result = new DOMResult();
335         try {
336             TRANSFORMER_FACTORY.newTransformer().transform(new StAXSource(inWrapper), result);
337         } catch (final TransformerException e) {
338             throw new XMLStreamException("Unable to read anyxml value", e);
339         }
340         return (Document) result.getNode();
341     }
342
343     private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema<?> parent, final String rootElement)
344             throws XMLStreamException {
345         if (!in.hasNext()) {
346             return;
347         }
348
349         if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
350             parent.setAttributes(getElementAttributes(in));
351             setValue((SimpleNodeDataWithSchema<?>) parent, in.getElementText().trim(), in.getNamespaceContext());
352             if (isNextEndDocument(in)) {
353                 return;
354             }
355
356             if (!isAtElement(in)) {
357                 in.nextTag();
358             }
359             return;
360         }
361
362         if (parent instanceof ListEntryNodeDataWithSchema || parent instanceof ContainerNodeDataWithSchema) {
363             parent.setAttributes(getElementAttributes(in));
364         }
365
366         if (parent instanceof LeafListNodeDataWithSchema || parent instanceof ListNodeDataWithSchema) {
367             String xmlElementName = in.getLocalName();
368             while (xmlElementName.equals(parent.getSchema().getQName().getLocalName())) {
369                 read(in, newEntryNode(parent), rootElement);
370                 if (in.getEventType() == XMLStreamConstants.END_DOCUMENT
371                         || in.getEventType() == XMLStreamConstants.END_ELEMENT) {
372                     break;
373                 }
374                 xmlElementName = in.getLocalName();
375             }
376
377             return;
378         }
379
380         if (parent instanceof AnyXmlNodeDataWithSchema) {
381             setValue((AnyXmlNodeDataWithSchema) parent, readAnyXmlValue(in), in.getNamespaceContext());
382             if (isNextEndDocument(in)) {
383                 return;
384             }
385
386             if (!isAtElement(in)) {
387                 in.nextTag();
388             }
389
390             return;
391         }
392
393         if (parent instanceof AnydataNodeDataWithSchema) {
394             final AnydataNodeDataWithSchema anydata = (AnydataNodeDataWithSchema) parent;
395             anydata.setObjectModel(DOMSourceAnydata.class);
396             anydata.setAttributes(getElementAttributes(in));
397             setValue(anydata, readAnyXmlValue(in), in.getNamespaceContext());
398             if (isNextEndDocument(in)) {
399                 return;
400             }
401
402             if (!isAtElement(in)) {
403                 in.nextTag();
404             }
405
406             return;
407         }
408
409         if (parent instanceof YangModeledAnyXmlSchemaNode) {
410             parent.setAttributes(getElementAttributes(in));
411         }
412
413         switch (in.nextTag()) {
414             case XMLStreamConstants.START_ELEMENT:
415                 // FIXME: why do we even need this tracker? either document it or remove it
416                 final Set<Entry<String, String>> namesakes = new HashSet<>();
417                 while (in.hasNext()) {
418                     final String xmlElementName = in.getLocalName();
419
420                     DataSchemaNode parentSchema = parent.getSchema();
421
422                     final String parentSchemaName = parentSchema.getQName().getLocalName();
423                     if (parentSchemaName.equals(xmlElementName)
424                             && in.getEventType() == XMLStreamConstants.END_ELEMENT) {
425                         if (isNextEndDocument(in)) {
426                             break;
427                         }
428
429                         if (!isAtElement(in)) {
430                             in.nextTag();
431                         }
432                         break;
433                     }
434
435                     if (in.isEndElement() && rootElement.equals(xmlElementName)) {
436                         break;
437                     }
438
439                     if (parentSchema instanceof YangModeledAnyXmlSchemaNode) {
440                         parentSchema = ((YangModeledAnyXmlSchemaNode) parentSchema).getSchemaOfAnyXmlData();
441                     }
442
443                     final String elementNS = in.getNamespaceURI();
444                     if (!namesakes.add(new SimpleImmutableEntry<>(elementNS, xmlElementName))) {
445                         throw new XMLStreamException(String.format(
446                             "Duplicate namespace \"%s\" element \"%s\" in XML input", elementNS, xmlElementName),
447                             in.getLocation());
448                     }
449
450                     final URI nsUri;
451                     try {
452                         nsUri = rawXmlNamespace(elementNS).getNamespace();
453                     } catch (IllegalArgumentException e) {
454                         throw new XMLStreamException("Failed to convert namespace " + xmlElementName, in.getLocation(),
455                             e);
456                     }
457
458                     final Deque<DataSchemaNode> childDataSchemaNodes =
459                             ParserStreamUtils.findSchemaNodeByNameAndNamespace(parentSchema, xmlElementName, nsUri);
460                     if (!childDataSchemaNodes.isEmpty()) {
461                         // We have a match, proceed with it
462                         read(in, ((CompositeNodeDataWithSchema<?>) parent).addChild(childDataSchemaNodes), rootElement);
463                         continue;
464                     }
465
466                     if (parent instanceof AbstractMountPointDataWithSchema) {
467                         // Parent can potentially hold a mount point, let's see if there is a label present
468                         final Optional<MountPointSchemaNode> optMount;
469                         if (parentSchema instanceof ContainerSchemaNode) {
470                             optMount = MountPointSchemaNode.streamAll((ContainerSchemaNode) parentSchema).findFirst();
471                         } else if (parentSchema instanceof ListSchemaNode) {
472                             optMount = MountPointSchemaNode.streamAll((ListSchemaNode) parentSchema).findFirst();
473                         } else {
474                             throw new XMLStreamException("Unhandled mount-aware schema " + parentSchema);
475                         }
476
477                         if (optMount.isPresent()) {
478                             final QName label = optMount.get().getQName();
479                             LOG.debug("Assuming node {} and namespace {} belongs to mount point {}", xmlElementName,
480                                 nsUri, label);
481
482                             final MountPointData mountData =
483                                     ((AbstractMountPointDataWithSchema<?>) parent).getMountPointData(label);
484                             addMountPointChild(mountData, nsUri, xmlElementName,
485                                 new DOMSource(readAnyXmlValue(in).getDocumentElement()));
486                             continue;
487                         }
488                     }
489
490                     // We have not handled the node -- let's decide what to do about that
491                     if (strictParsing) {
492                         throw new XMLStreamException(String.format(
493                             "Schema for node with name %s and namespace %s does not exist at %s", xmlElementName,
494                             elementNS, parentSchema.getPath(), in.getLocation()));
495                     }
496
497                     LOG.debug("Skipping unknown node ns=\"{}\" localName=\"{}\" at path {}", elementNS, xmlElementName,
498                         parentSchema.getPath());
499                     skipUnknownNode(in);
500                 }
501                 break;
502             case XMLStreamConstants.END_ELEMENT:
503                 if (isNextEndDocument(in)) {
504                     break;
505                 }
506
507                 if (!isAtElement(in)) {
508                     in.nextTag();
509                 }
510                 break;
511             default:
512                 break;
513         }
514     }
515
516     private static void addMountPointChild(final MountPointData mount, final URI namespace, final String localName,
517             final DOMSource source) {
518         final DOMSourceMountPointChild child = new DOMSourceMountPointChild(source);
519         if (YangLibraryConstants.MODULE_NAMESPACE.equals(namespace)) {
520             final Optional<ContainerName> optName = ContainerName.forLocalName(localName);
521             if (optName.isPresent()) {
522                 mount.setContainer(optName.get(), child);
523                 return;
524             }
525
526             LOG.warn("Encountered unknown element {} from YANG Library namespace", localName);
527         }
528
529         mount.addChild(child);
530     }
531
532     private static boolean isNextEndDocument(final XMLStreamReader in) throws XMLStreamException {
533         return !in.hasNext() || in.next() == XMLStreamConstants.END_DOCUMENT;
534     }
535
536     private static boolean isAtElement(final XMLStreamReader in) {
537         return in.getEventType() == XMLStreamConstants.START_ELEMENT
538                 || in.getEventType() == XMLStreamConstants.END_ELEMENT;
539     }
540
541     private static void skipUnknownNode(final XMLStreamReader in) throws XMLStreamException {
542         // in case when the unknown node and at least one of its descendant nodes have the same name
543         // we cannot properly reach the end just by checking if the current node is an end element and has the same name
544         // as the root unknown element. therefore we ignore the names completely and just track the level of nesting
545         int levelOfNesting = 0;
546         while (in.hasNext()) {
547             // in case there are text characters in an element, we cannot skip them by calling nextTag()
548             // therefore we skip them by calling next(), and then proceed to next element
549             in.next();
550             if (!isAtElement(in)) {
551                 in.nextTag();
552             }
553             if (in.isStartElement()) {
554                 levelOfNesting++;
555             }
556
557             if (in.isEndElement()) {
558                 if (levelOfNesting == 0) {
559                     break;
560                 }
561
562                 levelOfNesting--;
563             }
564         }
565
566         in.nextTag();
567     }
568
569     private void setValue(final SimpleNodeDataWithSchema<?> parent, final Object value,
570             final NamespaceContext nsContext) {
571         final DataSchemaNode schema = parent.getSchema();
572         final Object prev = parent.getValue();
573         checkArgument(prev == null, "Node '%s' has already set its value to '%s'", schema.getQName(), prev);
574         parent.setValue(translateValueByType(value, schema, nsContext));
575     }
576
577     private Object translateValueByType(final Object value, final DataSchemaNode node,
578             final NamespaceContext namespaceCtx) {
579         if (node instanceof AnyXmlSchemaNode) {
580             checkArgument(value instanceof Document);
581             /*
582              * FIXME: Figure out some YANG extension dispatch, which will reuse JSON parsing or XML parsing -
583              *        anyxml is not well-defined in JSON.
584              */
585             return new DOMSource(((Document) value).getDocumentElement());
586         }
587         if (node instanceof AnyDataSchemaNode) {
588             checkArgument(value instanceof Document);
589             return new DOMSourceAnydata(new DOMSource(((Document) value).getDocumentElement()));
590         }
591
592         checkArgument(node instanceof TypedDataSchemaNode);
593         checkArgument(value instanceof String);
594         return codecs.codecFor((TypedDataSchemaNode) node).parseValue(namespaceCtx, (String) value);
595     }
596
597     private static AbstractNodeDataWithSchema<?> newEntryNode(final AbstractNodeDataWithSchema<?> parent) {
598         final AbstractNodeDataWithSchema<?> newChild;
599         if (parent instanceof ListNodeDataWithSchema) {
600             newChild = ListEntryNodeDataWithSchema.forSchema(((ListNodeDataWithSchema) parent).getSchema());
601         } else {
602             verify(parent instanceof LeafListNodeDataWithSchema, "Unexpected parent %s", parent);
603             newChild = new LeafListEntryNodeDataWithSchema(((LeafListNodeDataWithSchema) parent).getSchema());
604         }
605         ((CompositeNodeDataWithSchema<?>) parent).addChild(newChild);
606         return newChild;
607     }
608
609     @Override
610     public void close() throws IOException {
611         writer.flush();
612         writer.close();
613     }
614
615     @Override
616     public void flush() throws IOException {
617         writer.flush();
618     }
619
620     private Optional<QNameModule> resolveXmlNamespace(final String xmlNamespace) {
621         return resolvedNamespaces.computeIfAbsent(xmlNamespace, nsUri -> {
622             final Iterator<Module> it = codecs.getSchemaContext().findModules(URI.create(nsUri)).iterator();
623             return it.hasNext() ? Optional.of(it.next().getQNameModule()) : Optional.empty();
624         });
625     }
626
627     private QNameModule rawXmlNamespace(final String xmlNamespace) {
628         return rawNamespaces.computeIfAbsent(xmlNamespace, nsUri -> QNameModule.create(URI.create(nsUri)));
629     }
630 }