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.ArrayDeque;
15 import java.util.ArrayList;
16 import java.util.Deque;
18 import javax.xml.stream.XMLEventReader;
19 import javax.xml.stream.XMLInputFactory;
20 import javax.xml.stream.XMLStreamException;
21 import javax.xml.stream.events.Characters;
22 import javax.xml.stream.events.StartElement;
23 import javax.xml.stream.events.XMLEvent;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
27 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
28 import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
29 import org.opendaylight.yangtools.yang.data.api.Node;
30 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
33 * The XML Tree Builder is builder utility class designed to facilitate of
34 * loading, reading and parsing XML documents into YANG DOM represented by
36 * The method {@link #buildDataTree(InputStream)} is designed to take and parse
37 * XML as {@link InputStream}. The output of the operation SHOULD be root
38 * <code>CompositeNode</code> or <code>SimpleElement</code> depends by which
39 * element XML begins. The XML header is omitted by XML parser.
41 * @author Lukas Sedlak
47 public final class XmlTreeBuilder {
48 private final static XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance();
50 private XmlTreeBuilder() {
54 * The method is designed to take and parse XML as {@link InputStream}. The
55 * output of the operation SHOULD be root <code>CompositeNode</code> or
56 * <code>SimpleElement</code> depends on element that XML document begins.
57 * The XML header is omitted by XML parser.
61 * @return root <code>Node</code> element conformant to XML start element in
62 * most cases it will be CompositeNode which contains child Nodes
63 * @throws XMLStreamException
65 public static Node<?> buildDataTree(final InputStream inputStream) throws XMLStreamException {
66 final XMLEventReader eventReader = XML_INPUT_FACTORY.createXMLEventReader(inputStream);
68 final Deque<Node<?>> processingQueue = new ArrayDeque<>();
69 Node<?> parentNode = null;
71 while (eventReader.hasNext()) {
72 final XMLEvent event = eventReader.nextEvent();
74 if (event.isStartElement()) {
75 final StartElement startElement = event.asStartElement();
76 if (!processingQueue.isEmpty()) {
77 parentNode = processingQueue.peek();
79 MutableCompositeNode compParentNode = null;
80 if (parentNode instanceof MutableCompositeNode) {
81 compParentNode = (MutableCompositeNode) parentNode;
83 Node<?> newNode = null;
84 if (isCompositeNodeEvent(eventReader, event)) {
85 newNode = resolveCompositeNodeFromStartElement(startElement, compParentNode);
86 } else if (isSimpleNodeEvent(eventReader, event)) {
87 newNode = resolveSimpleNodeFromStartElement(eventReader, startElement, compParentNode);
90 if (newNode != null) {
91 processingQueue.push(newNode);
92 if (compParentNode != null) {
93 compParentNode.getValue().add(newNode);
96 } else if (event.isEndElement()) {
97 root = processingQueue.pop();
98 if (root instanceof MutableCompositeNode) {
99 ((MutableCompositeNode) root).init();
107 * Checks if the XMLEvent is compliant to SimpleNode tag that contains only
108 * characters value. If the SimpleNode is composed only by empty XML tag
109 * (i.e. {@code <emptyTag />} or {@code<emptyTag></emptyTag>}) the result
110 * will be also <code>true</code>.
113 * actual XMLEvent that is processed
114 * @return <code>true</code> only and only if the XMLEvent Start Element is
115 * Simple element tag and contains character values or is empty XML
117 * @throws XMLStreamException
121 private static boolean isSimpleNodeEvent(final XMLEventReader eventReader, final XMLEvent event) throws XMLStreamException {
122 checkArgument(event != null, "XML Event cannot be NULL!");
123 if (event.isStartElement()) {
124 if (eventReader.hasNext()) {
125 final XMLEvent innerEvent;
126 innerEvent = eventReader.peek();
127 if (innerEvent.isCharacters()) {
128 final Characters chars = innerEvent.asCharacters();
129 if (!chars.isWhiteSpace()) {
132 } else if (innerEvent.isEndElement()) {
141 * Checks if XMLEvent is equivalent to CompositeNode Event. The
142 * CompositeNode Event is XML element that conforms to the XML element that
143 * contains 1..N XML child elements. (i.e. {@code <compositeNode>
144 * <simpleNode>data</simpleNode>
148 * actual XMLEvent that is processed
149 * @return <code>true</code> only if XML Element contains 1..N child
150 * elements, otherwise returns <code>false</code>
151 * @throws XMLStreamException
155 private static boolean isCompositeNodeEvent(final XMLEventReader eventReader, final XMLEvent event) throws XMLStreamException {
156 checkArgument(event != null, "XML Event cannot be NULL!");
157 if (event.isStartElement()) {
158 if (eventReader.hasNext()) {
160 innerEvent = eventReader.peek();
161 if (innerEvent.isCharacters()) {
162 Characters chars = innerEvent.asCharacters();
163 if (chars.isWhiteSpace()) {
164 eventReader.nextEvent();
165 innerEvent = eventReader.peek();
168 if (innerEvent.isStartElement()) {
177 * Creates and returns <code>SimpleNode</code> instance from actually
178 * processed XML Start Element.
180 * @param startElement
181 * actual XML Start Element that is processed
183 * Parent CompositeNode
184 * @return <code>new SimpleNode</code> instance from actually processed XML
186 * @throws XMLStreamException
190 private static SimpleNode<String> resolveSimpleNodeFromStartElement(final XMLEventReader eventReader,
191 final StartElement startElement, final CompositeNode parent) throws XMLStreamException {
192 checkArgument(startElement != null, "Start Element cannot be NULL!");
195 if (eventReader.hasNext()) {
196 final XMLEvent innerEvent = eventReader.peek();
197 if (innerEvent.isCharacters()) {
198 final Characters chars = innerEvent.asCharacters();
199 if (!chars.isWhiteSpace()) {
200 data = innerEvent.asCharacters().getData();
202 } else if (innerEvent.isEndElement()) {
206 return NodeFactory.createImmutableSimpleNode(resolveElementQName(startElement), parent, data);
210 * Creates and returns <code>MutableCompositeNode</code> instance from
211 * actually processed XML Start Element.
213 * @param startElement
214 * actual XML Start Element that is processed
216 * Parent CompositeNode
217 * @return <code>new MutableCompositeNode</code> instance from actually
218 * processed XML Start Element
221 * @see MutableCompositeNode
223 private static MutableCompositeNode resolveCompositeNodeFromStartElement(final StartElement startElement,
224 final CompositeNode parent) {
225 checkArgument(startElement != null, "Start Element cannot be NULL!");
227 return NodeFactory.createMutableCompositeNode(resolveElementQName(startElement), parent,
228 new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
232 * Extract and retrieve XML Element QName to OpenDaylight QName.
236 * @return QName instance composed of <code>elements</code> Namespace and
241 private static QName resolveElementQName(final StartElement element) {
242 checkArgument(element != null, "Start Element cannot be NULL!");
244 final String nsURI = element.getName().getNamespaceURI();
245 final String localName = element.getName().getLocalPart();
246 return new QName(URI.create(nsURI), localName);