import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import java.io.Closeable;
import java.io.Flushable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
-import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stax.StAXSource;
import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
+import org.opendaylight.yangtools.rfc7952.model.api.AnnotationSchemaNode;
import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.util.AbstractNodeDataWithSchema;
import org.opendaylight.yangtools.yang.data.util.AnyXmlNodeDataWithSchema;
import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
* This class provides functionality for parsing an XML source containing YANG-modeled data. It disallows multiple
* instances of the same element except for leaf-list and list entries. It also expects that the YANG-modeled data in
* the XML source are wrapped in a root element. This class is NOT thread-safe.
+ *
+ * <p>
+ * Due to backwards compatibility reasons, RFC7952 metadata emitted by this parser may include key QNames with empty URI
+ * (as exposed via {@link #LEGACY_ATTRIBUTE_NAMESPACE}) as their QNameModule. These indicate an unqualified XML
+ * attribute and their value can be assumed to be a String. Furthermore, this extends to qualified attributes, which
+ * uses the proper namespace, but will not bind to a proper module revision -- these need to be reconciled with a
+ * particular SchemaContext and are expected to either be fully decoded, or contain a String value. Handling of such
+ * annotations is at the discretion of the user encountering it: preferred way of handling is to either filter or
+ * normalize them to proper QNames/values when encountered. This caveat will be removed in a future version.
*/
@Beta
public final class XmlParserStream implements Closeable, Flushable {
+ /**
+ * {@link QNameModule} for use with legacy XML attributes.
+ * @deprecated The use on this namespace is discouraged and users are strongly encouraged to proper RFC7952 metadata
+ * annotations.
+ */
+ @Deprecated
+ public static final QNameModule LEGACY_ATTRIBUTE_NAMESPACE = QNameModule.create(URI.create("")).intern();
+
private static final Logger LOG = LoggerFactory.getLogger(XmlParserStream.class);
private static final String XML_STANDARD_VERSION = "1.0";
private static final String COM_SUN_TRANSFORMER =
return parse(new DOMSourceXMLStreamReader(src));
}
- private static ImmutableMap<QName, String> getElementAttributes(final XMLStreamReader in) {
+ private ImmutableMap<QName, Object> getElementAttributes(final XMLStreamReader in) {
checkState(in.isStartElement(), "Attributes can be extracted only from START_ELEMENT.");
- final Map<QName, String> attributes = new LinkedHashMap<>();
+ final Map<QName, Object> attributes = new LinkedHashMap<>();
for (int attrIndex = 0; attrIndex < in.getAttributeCount(); attrIndex++) {
- String attributeNS = in.getAttributeNamespace(attrIndex);
-
- if (attributeNS == null) {
- attributeNS = "";
- }
+ final String attributeNS = in.getAttributeNamespace(attrIndex);
// Skip namespace definitions
if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attributeNS)) {
continue;
}
- final QName qName = QName.create(URI.create(attributeNS), in.getAttributeLocalName(attrIndex));
- attributes.put(qName, in.getAttributeValue(attrIndex));
+ final String localName = in.getAttributeLocalName(attrIndex);
+ final String attrValue = in.getAttributeValue(attrIndex);
+ if (Strings.isNullOrEmpty(attributeNS)) {
+ StreamWriterFacade.warnLegacyAttribute(localName);
+ attributes.put(QName.create(LEGACY_ATTRIBUTE_NAMESPACE, localName), attrValue);
+ continue;
+ }
+
+ // Cross-relate attribute namespace to the module
+ final URI uri = URI.create(attributeNS);
+ final SchemaContext schemaContext = codecs.getSchemaContext();
+ final Set<Module> modules = schemaContext.findModules(uri);
+ if (!modules.isEmpty()) {
+ final QName qname = QName.create(modules.iterator().next().getQNameModule(), localName);
+ final Optional<AnnotationSchemaNode> optAnnotation = AnnotationSchemaNode.find(schemaContext, qname);
+ if (optAnnotation.isPresent()) {
+ final AnnotationSchemaNode schema = optAnnotation.get();
+ final Object value = codecs.codecFor(schema).parseValue(in.getNamespaceContext(), attrValue);
+ attributes.put(schema.getQName(), value);
+ continue;
+ }
+
+ LOG.debug("Annotation for {} not found, using legacy QName", qname);
+ }
+
+ attributes.put(QName.create(uri, localName), attrValue);
}
return ImmutableMap.copyOf(attributes);
final String xmlElementNamespace = in.getNamespaceURI();
if (!namesakes.add(new SimpleImmutableEntry<>(xmlElementNamespace, xmlElementName))) {
- final Location loc = in.getLocation();
- throw new IllegalStateException(String.format(
- "Duplicate namespace \"%s\" element \"%s\" in XML input at: line %s column %s",
- xmlElementNamespace, xmlElementName, loc.getLineNumber(), loc.getColumnNumber()));
+ throw new XMLStreamException(String.format(
+ "Duplicate namespace \"%s\" element \"%s\" in XML input", xmlElementNamespace,
+ xmlElementName), in.getLocation());
}
final Deque<DataSchemaNode> childDataSchemaNodes =
new URI(xmlElementNamespace));
if (childDataSchemaNodes.isEmpty()) {
- checkState(!strictParsing, "Schema for node with name %s and namespace %s does not exist at %s",
- xmlElementName, xmlElementNamespace, parentSchema.getPath());
- skipUnknownNode(in);
- continue;
+ if (!strictParsing) {
+ LOG.debug("Skipping unknown node ns=\"{}\" localName=\"{}\" at path {}",
+ xmlElementNamespace, xmlElementName, parentSchema.getPath());
+ skipUnknownNode(in);
+ continue;
+ }
+
+ throw new XMLStreamException(String.format(
+ "Schema for node with name %s and namespace %s does not exist at %s",
+ xmlElementName, xmlElementNamespace, parentSchema.getPath(), in.getLocation()));
}
read(in, ((CompositeNodeDataWithSchema<?>) parent).addChild(childDataSchemaNodes), rootElement);