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.newInstance();
49 private static XMLEventReader eventReader;
51 private XmlTreeBuilder() {
55 * The method is designed to take and parse XML as {@link InputStream}. The
56 * output of the operation SHOULD be root <code>CompositeNode</code> or
57 * <code>SimpleElement</code> depends on element that XML document begins.
58 * The XML header is omitted by XML parser.
62 * @return root <code>Node</code> element conformant to XML start element in
63 * most cases it will be CompositeNode which contains child Nodes
64 * @throws XMLStreamException
66 public static Node<?> buildDataTree(final InputStream inputStream) throws XMLStreamException {
67 eventReader = xmlInputFactory.createXMLEventReader(inputStream);
69 final Stack<Node<?>> processingQueue = new Stack<>();
70 Node<?> parentNode = null;
72 while (eventReader.hasNext()) {
73 final XMLEvent event = eventReader.nextEvent();
75 if (event.isStartElement()) {
76 final StartElement startElement = event.asStartElement();
77 if (!processingQueue.isEmpty()) {
78 parentNode = processingQueue.peek();
80 MutableCompositeNode compParentNode = null;
81 if (parentNode instanceof MutableCompositeNode) {
82 compParentNode = (MutableCompositeNode) parentNode;
84 Node<?> newNode = null;
85 if (isCompositeNodeEvent(event)) {
86 newNode = resolveCompositeNodeFromStartElement(startElement, compParentNode);
87 } else if (isSimpleNodeEvent(event)) {
88 newNode = resolveSimpleNodeFromStartElement(startElement, compParentNode);
91 if (newNode != null) {
92 processingQueue.push(newNode);
93 if (compParentNode != null) {
94 compParentNode.getChildren().add(newNode);
97 } else if (event.isEndElement()) {
98 root = processingQueue.pop();
99 if (root instanceof MutableCompositeNode) {
100 ((MutableCompositeNode) root).init();
108 * Checks if the XMLEvent is compliant to SimpleNode tag that contains only
109 * characters value. If the SimpleNode is composed only by empty XML tag
110 * (i.e. {@code <emptyTag />} or {@code<emptyTag></emptyTag>}) the result
111 * will be also <code>true</code>.
114 * actual XMLEvent that is processed
115 * @return <code>true</code> only and only if the XMLEvent Start Element is
116 * Simple element tag and contains character values or is empty XML
118 * @throws XMLStreamException
122 private static boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
123 checkArgument(event != null, "XML Event cannot be NULL!");
124 if (event.isStartElement()) {
125 if (eventReader.hasNext()) {
126 final XMLEvent innerEvent;
127 innerEvent = eventReader.peek();
128 if (innerEvent.isCharacters()) {
129 final Characters chars = innerEvent.asCharacters();
130 if (!chars.isWhiteSpace()) {
133 } else if (innerEvent.isEndElement()) {
142 * Checks if XMLEvent is equivalent to CompositeNode Event. The
143 * CompositeNode Event is XML element that conforms to the XML element that
144 * contains 1..N XML child elements. (i.e. {@code <compositeNode>
145 * <simpleNode>data</simpleNode>
149 * actual XMLEvent that is processed
150 * @return <code>true</code> only if XML Element contains 1..N child
151 * elements, otherwise returns <code>false</code>
152 * @throws XMLStreamException
156 private static boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
157 checkArgument(event != null, "XML Event cannot be NULL!");
158 if (event.isStartElement()) {
159 if (eventReader.hasNext()) {
161 innerEvent = eventReader.peek();
162 if (innerEvent.isCharacters()) {
163 Characters chars = innerEvent.asCharacters();
164 if (chars.isWhiteSpace()) {
165 eventReader.nextEvent();
166 innerEvent = eventReader.peek();
169 if (innerEvent.isStartElement()) {
178 * Creates and returns <code>SimpleNode</code> instance from actually
179 * processed XML Start Element.
181 * @param startElement
182 * actual XML Start Element that is processed
184 * Parent CompositeNode
185 * @return <code>new SimpleNode</code> instance from actually processed XML
187 * @throws XMLStreamException
191 private static SimpleNode<String> resolveSimpleNodeFromStartElement(final StartElement startElement,
192 CompositeNode parent) throws XMLStreamException {
193 checkArgument(startElement != null, "Start Element cannot be NULL!");
196 if (eventReader.hasNext()) {
197 final XMLEvent innerEvent = eventReader.peek();
198 if (innerEvent.isCharacters()) {
199 final Characters chars = innerEvent.asCharacters();
200 if (!chars.isWhiteSpace()) {
201 data = innerEvent.asCharacters().getData();
203 } else if (innerEvent.isEndElement()) {
207 return NodeFactory.createImmutableSimpleNode(resolveElementQName(startElement), parent, data);
211 * Creates and returns <code>MutableCompositeNode</code> instance from
212 * actually processed XML Start Element.
214 * @param startElement
215 * actual XML Start Element that is processed
217 * Parent CompositeNode
218 * @return <code>new MutableCompositeNode</code> instance from actually
219 * processed XML Start Element
222 * @see MutableCompositeNode
224 private static MutableCompositeNode resolveCompositeNodeFromStartElement(final StartElement startElement,
225 CompositeNode parent) {
226 checkArgument(startElement != null, "Start Element cannot be NULL!");
228 return NodeFactory.createMutableCompositeNode(resolveElementQName(startElement), parent,
229 new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
233 * Extract and retrieve XML Element QName to OpenDaylight QName.
237 * @return QName instance composed of <code>elements</code> Namespace and
242 private static QName resolveElementQName(final StartElement element) {
243 checkArgument(element != null, "Start Element cannot be NULL!");
245 final String nsURI = element.getName().getNamespaceURI();
246 final String localName = element.getName().getLocalPart();
247 return new QName(URI.create(nsURI), localName);