/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.yang.data.impl; import static com.google.common.base.Preconditions.checkArgument; import java.io.InputStream; import java.net.URI; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.Characters; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.CompositeNode; import org.opendaylight.yangtools.yang.data.api.ModifyAction; import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.SimpleNode; /** * The XML Tree Builder is builder utility class designed to facilitate of * loading, reading and parsing XML documents into YANG DOM represented by * yang-data-api.
* The method {@link #buildDataTree(InputStream)} is designed to take and parse * XML as {@link InputStream}. The output of the operation SHOULD be root * CompositeNode or SimpleElement depends by which * element XML begins. The XML header is omitted by XML parser. * * @author Lukas Sedlak * * @see CompositeNode * @see SimpleNode * @see Node */ public final class XmlTreeBuilder { private final static XMLInputFactory XML_INPUT_FACTORY = XMLInputFactory.newInstance(); private XmlTreeBuilder() { } /** * The method is designed to take and parse XML as {@link InputStream}. The * output of the operation SHOULD be root CompositeNode or * SimpleElement depends on element that XML document begins. * The XML header is omitted by XML parser. * * @param inputStream * XML Input Stream * @return root Node element conformant to XML start element in * most cases it will be CompositeNode which contains child Nodes * @throws XMLStreamException */ public static Node buildDataTree(final InputStream inputStream) throws XMLStreamException { final XMLEventReader eventReader = XML_INPUT_FACTORY.createXMLEventReader(inputStream); final Deque> processingQueue = new ArrayDeque<>(); Node parentNode = null; Node root = null; while (eventReader.hasNext()) { final XMLEvent event = eventReader.nextEvent(); if (event.isStartElement()) { final StartElement startElement = event.asStartElement(); if (!processingQueue.isEmpty()) { parentNode = processingQueue.peek(); } MutableCompositeNode compParentNode = null; if (parentNode instanceof MutableCompositeNode) { compParentNode = (MutableCompositeNode) parentNode; } Node newNode = null; if (isCompositeNodeEvent(eventReader, event)) { newNode = resolveCompositeNodeFromStartElement(startElement, compParentNode); } else if (isSimpleNodeEvent(eventReader, event)) { newNode = resolveSimpleNodeFromStartElement(eventReader, startElement, compParentNode); } if (newNode != null) { processingQueue.push(newNode); if (compParentNode != null) { compParentNode.getValue().add(newNode); } } } else if (event.isEndElement()) { root = processingQueue.pop(); if (root instanceof MutableCompositeNode) { ((MutableCompositeNode) root).init(); } } } return root; } /** * Checks if the XMLEvent is compliant to SimpleNode tag that contains only * characters value. If the SimpleNode is composed only by empty XML tag * (i.e. {@code } or {@code}) the result * will be also true. * * @param event * actual XMLEvent that is processed * @return true only and only if the XMLEvent Start Element is * Simple element tag and contains character values or is empty XML * tag. * @throws XMLStreamException * * @see SimpleNode */ private static boolean isSimpleNodeEvent(final XMLEventReader eventReader, final XMLEvent event) throws XMLStreamException { checkArgument(event != null, "XML Event cannot be NULL!"); if (event.isStartElement()) { if (eventReader.hasNext()) { final XMLEvent innerEvent; innerEvent = eventReader.peek(); if (innerEvent.isCharacters()) { final Characters chars = innerEvent.asCharacters(); if (!chars.isWhiteSpace()) { return true; } } else if (innerEvent.isEndElement()) { return true; } } } return false; } /** * Checks if XMLEvent is equivalent to CompositeNode Event. The * CompositeNode Event is XML element that conforms to the XML element that * contains 1..N XML child elements. (i.e. {@code * data * }) * * @param event * actual XMLEvent that is processed * @return true only if XML Element contains 1..N child * elements, otherwise returns false * @throws XMLStreamException * * @see CompositeNode */ private static boolean isCompositeNodeEvent(final XMLEventReader eventReader, final XMLEvent event) throws XMLStreamException { checkArgument(event != null, "XML Event cannot be NULL!"); if (event.isStartElement()) { if (eventReader.hasNext()) { XMLEvent innerEvent; innerEvent = eventReader.peek(); if (innerEvent.isCharacters()) { Characters chars = innerEvent.asCharacters(); if (chars.isWhiteSpace()) { eventReader.nextEvent(); innerEvent = eventReader.peek(); } } if (innerEvent.isStartElement()) { return true; } } } return false; } /** * Creates and returns SimpleNode instance from actually * processed XML Start Element. * * @param startElement * actual XML Start Element that is processed * @param parent * Parent CompositeNode * @return new SimpleNode instance from actually processed XML * Start Element * @throws XMLStreamException * * @see SimpleNode */ private static SimpleNode resolveSimpleNodeFromStartElement(final XMLEventReader eventReader, final StartElement startElement, final CompositeNode parent) throws XMLStreamException { checkArgument(startElement != null, "Start Element cannot be NULL!"); String data = null; if (eventReader.hasNext()) { final XMLEvent innerEvent = eventReader.peek(); if (innerEvent.isCharacters()) { final Characters chars = innerEvent.asCharacters(); if (!chars.isWhiteSpace()) { data = innerEvent.asCharacters().getData(); } } else if (innerEvent.isEndElement()) { data = ""; } } return NodeFactory.createImmutableSimpleNode(resolveElementQName(startElement), parent, data); } /** * Creates and returns MutableCompositeNode instance from * actually processed XML Start Element. * * @param startElement * actual XML Start Element that is processed * @param parent * Parent CompositeNode * @return new MutableCompositeNode instance from actually * processed XML Start Element * * @see CompositeNode * @see MutableCompositeNode */ private static MutableCompositeNode resolveCompositeNodeFromStartElement(final StartElement startElement, final CompositeNode parent) { checkArgument(startElement != null, "Start Element cannot be NULL!"); return NodeFactory.createMutableCompositeNode(resolveElementQName(startElement), parent, new ArrayList>(), ModifyAction.CREATE, null); } /** * Extract and retrieve XML Element QName to OpenDaylight QName. * * @param element * Start Element * @return QName instance composed of elements Namespace and * Local Part. * * @see QName */ private static QName resolveElementQName(final StartElement element) { checkArgument(element != null, "Start Element cannot be NULL!"); final String nsURI = element.getName().getNamespaceURI(); final String localName = element.getName().getLocalPart(); return new QName(URI.create(nsURI), localName); } }