2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Ericsson
4 * Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.yangtools.parser.data.dom;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
27 import org.w3c.dom.Attr;
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.Node;
31 import org.w3c.dom.NodeList;
33 import org.oran.smo.yangtools.parser.ParserExecutionContext;
34 import org.oran.smo.yangtools.parser.data.YangData;
35 import org.oran.smo.yangtools.parser.data.parser.JsonParser.JsonObject;
36 import org.oran.smo.yangtools.parser.findings.Finding;
37 import org.oran.smo.yangtools.parser.findings.ParserFindingType;
38 import org.oran.smo.yangtools.parser.model.yangdom.YangDomDocumentRoot;
39 import org.oran.smo.yangtools.parser.util.QNameHelper;
42 * Root element for a YANG data DOM. Contains a tree structure of data originating from XML of JSON. The direct
43 * children of this root object in effect represent the values or structure of top-level data nodes.
45 * Not to be confused with class {@link YangDomDocumentRoot}, which is the root of a *model* DOM.
47 * Will not perform a check against any YANG schema. Therefore, can also be used, if so desired, as a simple
48 * multi-purpose XML/JSON parser if so desired.
50 * @author Mark Hollmann
52 public class YangDataDomDocumentRoot extends YangDataDomNode {
55 * Where the data originally came from. This can make a difference. In XML, the values are all
56 * represented as strings; in JSON, a primitive value can be a String, Double or Boolean. If conversion
57 * is required later on to a highly-typed Java object, a client must know where the data originally
60 public enum SourceDataType {
65 private final YangData yangData;
67 private final SourceDataType sourceDataType;
69 public YangDataDomDocumentRoot(final YangData yangData, final SourceDataType sourceDataType) {
71 this.yangData = yangData;
72 this.sourceDataType = sourceDataType;
76 public YangData getYangData() {
81 public SourceDataType getSourceDataType() {
82 return sourceDataType;
85 public void buildFromXmlDocument(final ParserExecutionContext context, final Document document) {
88 * DOM Document root is a special case. It is not an element as such. It only
89 * has a single element as "child"; this single element is the actual root XML
92 final Element rootXmlElement = document.getDocumentElement();
93 final String rootXmlElementName = QNameHelper.extractName(rootXmlElement.getTagName());
96 * It is perfectly valid and possible and realistic for the XML root element to declare
97 * namespaces, so we populate the prefix resolver of the root here.
99 final List<Attr> rootElementXmlAttributes = getAttributesfromXmlElement(rootXmlElement);
100 populateXmlPrefixResolver(rootElementXmlAttributes, getPrefixResolver());
103 * There is always confusion about the root XML element - and it usually depends on the input.
104 * Sometimes, data is parsed that sits inside a file that a programmer has prepared; sometimes
105 * it could be a NETCONF reply, etc.
107 * We are trying to be as lenient as possible, and we support the following:
111 * 1.) As root element <config>, for example:
114 * <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
119 * 2.) RFC 9195 ("A File Format for YANG Instance Data")
121 * Lately, we are aligning with draft-ietf-netmod-yang-instance-file-format-10,
122 * which looks like this:
124 * <?xml version="1.0" encoding="UTF-8"?>
125 * <instance-data-set xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-instance-data">
126 * <name>read-only-acm-rules</name>
128 * <module>ietf-netconf-acm@2018-02-14</module>
131 * <date>1776-07-04</date>
132 * <description>Initial version</description>
134 * <description>Access control rules for a read-only role.</description>
136 * <nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
139 * </instance-data-set>
143 * 3.) A NETCONF RPC reply:
145 * <rpc-reply message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
147 * <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
152 * 4.) Just <data> as root:
155 * <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
160 * 5.) Data directly at the root:
162 * <netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
166 final List<Element> xmlElementsToProcess = new ArrayList<>();
168 if (rootXmlElementName.equals("config")) {
170 xmlElementsToProcess.addAll(getChildElementNodes(rootXmlElement));
172 } else if (rootXmlElementName.equals("instance-data-set")) {
174 * Need to find the <content-data> element underneath first. Whatever is
175 * underneath that, will be handled.
177 final Element contentDataElement = getNamedChildElement(rootXmlElement, "content-data");
178 if (contentDataElement != null) {
180 * We must add handle whatever prefixes have been defined on the <content-data> element.
181 * Never seen in the wild, but who knows....
183 final List<Attr> contentDataXmlAttributes = getAttributesfromXmlElement(contentDataElement);
184 populateXmlPrefixResolver(contentDataXmlAttributes, getPrefixResolver());
186 xmlElementsToProcess.addAll(getChildElementNodes(contentDataElement));
188 } else if (rootXmlElementName.equals("rpc-reply")) {
190 * Need to find the <data> element underneath first. Whatever is
191 * underneath that, will be handled.
193 final Element dataElement = getNamedChildElement(rootXmlElement, "data");
194 if (dataElement != null) {
196 * We must add handle whatever prefixes have been defined on the <data> element.
198 final List<Attr> dataXmlAttributes = getAttributesfromXmlElement(dataElement);
199 populateXmlPrefixResolver(dataXmlAttributes, getPrefixResolver());
201 xmlElementsToProcess.addAll(getChildElementNodes(dataElement));
203 } else if (rootXmlElementName.equals("data")) {
205 xmlElementsToProcess.addAll(getChildElementNodes(rootXmlElement));
209 * Right... assume so that the root element is immediately data.
211 context.addFinding(new Finding(yangData, ParserFindingType.P071_INCORRECT_ROOT_ELEMENT_OF_DATA_FILE.toString(),
212 "Expected <instance-data-set> (or <config> or <rpc-reply> or <data>) as first element in data file. Assume root element represents data. If this is not the case, this will likely lead to other findings."));
214 xmlElementsToProcess.add(rootXmlElement);
217 if (xmlElementsToProcess.isEmpty()) {
218 context.addFinding(new Finding(yangData, ParserFindingType.P079_EMPTY_DATA_FILE.toString(),
219 "The instance data input seems to be empty."));
225 for (final Element childXmlNode : xmlElementsToProcess) {
226 final YangDataDomNode childYangDataDomNode = new YangDataDomNode(context, this, childXmlNode);
227 childYangDataDomNode.processXmlChildElements(context, childXmlNode);
231 * It quite frequently (at least for hand-drafted XML files) happens that namespaces are not declared
232 * at the top of the document. We make sure that the children all have a valid namespace, otherwise
233 * there will be more processing errors later on, so highlight this here.
235 for (final YangDataDomNode child : getChildren()) {
236 if (child.getNamespace() == null) {
237 context.addFinding(new Finding(this, ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(),
238 "The top-level data nodes must have a namespace, but none is declared."));
244 * Given the root XML element of a document, searches for a named child element, and return that child's children.
246 private Element getNamedChildElement(final Element element, final String soughtChildName) {
248 final Collection<? extends Element> childrenOfRoot = getChildElementNodes(element);
249 for (final Element child : childrenOfRoot) {
250 if (soughtChildName.equals(QNameHelper.extractName(child.getTagName()))) {
258 private Collection<? extends Element> getChildElementNodes(final Element element) {
260 final List<Element> result = new ArrayList<>();
262 final NodeList childXmlNodes = element.getChildNodes();
263 for (int i = 0; i < childXmlNodes.getLength(); ++i) {
264 final Node childXmlNode = childXmlNodes.item(i);
265 if (childXmlNode.getNodeType() == Node.ELEMENT_NODE) {
266 result.add((Element) childXmlNode);
273 // ============================ JSON handling ============================
275 public void buildFromJsonDocument(final ParserExecutionContext context, final JsonObject rootJsonObject) {
278 * No fluffing around with namespaces here, of course.
281 processJsonChildElements(context, rootJsonObject);
284 * We make sure that the children all have a valid module name, otherwise there will be
285 * more processing errors later on, so highlight this here.
287 for (final YangDataDomNode child : getChildren()) {
288 if (child.getModuleName().equals(ROOT_SLASH)) {
289 context.addFinding(new Finding(this, ParserFindingType.P015_INVALID_SYNTAX_IN_DOCUMENT.toString(),
290 "The name of all top-level data nodes name must be prefixed with the name of the module that owns the data node."));