2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import java.io.InputStream;
14 import java.util.ArrayList;
15 import java.util.Stack;
17 import javax.xml.stream.XMLEventReader;
18 import javax.xml.stream.XMLInputFactory;
19 import javax.xml.stream.XMLStreamException;
20 import javax.xml.stream.events.Characters;
21 import javax.xml.stream.events.StartElement;
22 import javax.xml.stream.events.XMLEvent;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
26 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
27 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
28 import org.opendaylight.yangtools.yang.data.api.Node;
29 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
32 * The XML Tree Builder is builder utility class designed to facilitate of
33 * loading, reading and parsing XML documents into YANG DOM represented by
35 * The method {@link #buildDataTree(InputStream)} is designed to take and parse
36 * XML as {@link InputStream}. The output of the operation SHOULD be root
37 * <code>CompositeNode</code> or <code>SimpleElement</code> depends by which
38 * element XML begins. The XML header is omitted by XML parser.
40 * @author Lukas Sedlak
46 public final class XmlTreeBuilder {
48 private final static XMLInputFactory xmlInputFactory = XMLInputFactory
50 private static XMLEventReader eventReader;
52 private XmlTreeBuilder() {
56 * The method is designed to take and parse XML as {@link InputStream}. The
57 * output of the operation SHOULD be root <code>CompositeNode</code> or
58 * <code>SimpleElement</code> depends on element that XML document begins.
59 * The XML header is omitted by XML parser.
63 * @return root <code>Node</code> element conformant to XML start element in
64 * most cases it will be CompositeNode which contains child Nodes
65 * @throws XMLStreamException
67 public static Node<?> buildDataTree(final InputStream inputStream)
68 throws XMLStreamException {
69 eventReader = xmlInputFactory.createXMLEventReader(inputStream);
71 final Stack<Node<?>> processingQueue = new Stack<>();
72 Node<?> parentNode = null;
74 while (eventReader.hasNext()) {
75 final XMLEvent event = eventReader.nextEvent();
77 if (event.isStartElement()) {
78 final StartElement startElement = event.asStartElement();
79 if (!processingQueue.isEmpty()) {
80 parentNode = processingQueue.peek();
82 CompositeNode compParentNode = null;
83 if (parentNode instanceof CompositeNode) {
84 compParentNode = (CompositeNode) parentNode;
86 Node<?> newNode = null;
87 if (isCompositeNodeEvent(event)) {
88 newNode = resolveCompositeNodeFromStartElement(
89 startElement, compParentNode);
90 } else if (isSimpleNodeEvent(event)) {
91 newNode = resolveSimpleNodeFromStartElement(startElement,
95 if (newNode != null) {
96 processingQueue.push(newNode);
97 if (compParentNode != null) {
98 compParentNode.getChildren().add(newNode);
101 } else if (event.isEndElement()) {
102 root = processingQueue.pop();
109 * Checks if the XMLEvent is compliant to SimpleNode tag that contains only
110 * characters value. If the SimpleNode is composed only by empty XML tag
111 * (i.e. {@code <emptyTag />} or {@code<emptyTag></emptyTag>}) the result
112 * will be also <code>true</code>.
115 * actual XMLEvent that is processed
116 * @return <code>true</code> only and only if the XMLEvent Start Element is
117 * Simple element tag and contains character values or is empty XML
122 private static boolean isSimpleNodeEvent(final XMLEvent event) {
123 checkArgument(event != null, "XML Event cannot be NULL!");
124 if (event.isStartElement()) {
125 if (eventReader.hasNext()) {
126 final XMLEvent innerEvent;
128 innerEvent = eventReader.peek();
129 if (innerEvent.isCharacters()) {
130 final Characters chars = innerEvent.asCharacters();
131 if (!chars.isWhiteSpace()) {
134 } else if (innerEvent.isEndElement()) {
137 } catch (XMLStreamException e) {
146 * Checks if XMLEvent is equivalent to CompositeNode Event. The
147 * CompositeNode Event is XML element that conforms to the XML element that
148 * contains 1..N XML child elements. (i.e. {@code <compositeNode>
149 * <simpleNode>data</simpleNode>
153 * actual XMLEvent that is processed
154 * @return <code>true</code> only if XML Element contains 1..N child
155 * elements, otherwise returns <code>false</code>
159 private static boolean isCompositeNodeEvent(final XMLEvent event) {
160 checkArgument(event != null, "XML Event cannot be NULL!");
161 if (event.isStartElement()) {
162 if (eventReader.hasNext()) {
165 innerEvent = eventReader.peek();
166 if (innerEvent.isCharacters()) {
167 Characters chars = innerEvent.asCharacters();
168 if (chars.isWhiteSpace()) {
169 eventReader.nextEvent();
170 innerEvent = eventReader.peek();
173 if (innerEvent.isStartElement()) {
176 } catch (XMLStreamException e) {
185 * Creates and returns <code>SimpleNode</code> instance from actually
186 * processed XML Start Element.
188 * @param startElement
189 * actual XML Start Element that is processed
191 * Parent CompositeNode
192 * @return <code>new SimpleNode</code> instance from actually processed XML
194 * @throws XMLStreamException
198 private static SimpleNode<String> resolveSimpleNodeFromStartElement(
199 final StartElement startElement, CompositeNode parent)
200 throws XMLStreamException {
201 checkArgument(startElement != null, "Start Element cannot be NULL!");
204 if (eventReader.hasNext()) {
205 final XMLEvent innerEvent = eventReader.peek();
206 if (innerEvent.isCharacters()) {
207 final Characters chars = innerEvent.asCharacters();
208 if (!chars.isWhiteSpace()) {
209 data = innerEvent.asCharacters().getData();
211 } else if (innerEvent.isEndElement()) {
215 return NodeFactory.createImmutableSimpleNode(
216 resolveElementQName(startElement), parent, data);
220 * Creates and returns <code>MutableCompositeNode</code> instance from
221 * actually processed XML Start Element.
223 * @param startElement
224 * actual XML Start Element that is processed
226 * Parent CompositeNode
227 * @return <code>new MutableCompositeNode</code> instance from actually
228 * processed XML Start Element
231 * @see MutableCompositeNode
233 private static CompositeNode resolveCompositeNodeFromStartElement(
234 final StartElement startElement, CompositeNode parent) {
235 checkArgument(startElement != null, "Start Element cannot be NULL!");
237 return NodeFactory.createMutableCompositeNode(
238 resolveElementQName(startElement), parent,
239 new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
243 * Extract and retrieve XML Element QName to OpenDaylight QName.
247 * @return QName instance composed of <code>elements</code> Namespace and
252 private static QName resolveElementQName(final StartElement element) {
253 checkArgument(element != null, "Start Element cannot be NULL!");
255 final String nsURI = element.getName().getNamespaceURI();
256 final String localName = element.getName().getLocalPart();
257 return new QName(URI.create(nsURI), localName);