Do not trim element content for string leaf values 44/106944/6
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 20 Jul 2023 23:35:57 +0000 (01:35 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 21 Jul 2023 07:37:30 +0000 (09:37 +0200)
Trimming element text leads to values being mis-represented -- for
example if the content is a YANG string, we really need to pick it up
directly.

We should completely avoid trimming, but that would lead to not
recognizing previously-accepted XML documents, for example those which
correspond to a uint8 with leading whitespace.

JIRA: YANGTOOLS-1522
Change-Id: Ib1e05a9428a66765b2f85cfde723fe27076f68ee
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/AbstractXmlCodec.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/IdentityrefXmlCodec.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/QuotedXmlCodec.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/StringXmlCodec.java [new file with mode: 0644]
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlCodecFactory.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java
codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlStringInstanceIdentifierCodec.java
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1522Test.java [new file with mode: 0644]

index 942e4dbc0a17cdeaba458d86cf731e4842a002d2..8cc15779a0bd5e2af46e10dff04955fcd0cb5f6e 100644 (file)
@@ -5,12 +5,12 @@
  * 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.codec.xml;
 
 import static java.util.Objects.requireNonNull;
 
 import javax.xml.namespace.NamespaceContext;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.impl.codec.DataStringCodec;
 import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
 
@@ -20,10 +20,9 @@ import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec;
  * @param <T> Deserialized object type
  */
 abstract class AbstractXmlCodec<T> implements XmlCodec<T> {
-
     private final DataStringCodec<T> codec;
 
-    protected AbstractXmlCodec(final DataStringCodec<T> codec) {
+    AbstractXmlCodec(final DataStringCodec<T> codec) {
         this.codec = requireNonNull(codec);
     }
 
@@ -34,7 +33,13 @@ abstract class AbstractXmlCodec<T> implements XmlCodec<T> {
 
     @Override
     public final T parseValue(final NamespaceContext namespaceContext, final String str) {
-        return codec.deserialize(str);
+        return codec.deserialize(trimValue(str));
+    }
+
+    // FIXME: YANGTOOLS-1523: remove this method
+    @Deprecated
+    @NonNull String trimValue(final @NonNull String str) {
+        return str.trim();
     }
 
     final String serialize(final T input) {
index 9f9980e8f1d7af0d71cef8c36cd4c50ff6c810c1..cb931bf53eec102f310e27fcf495522cbebf998a 100644 (file)
@@ -11,25 +11,23 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects;
-import java.util.Iterator;
-import java.util.Map.Entry;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.data.util.codec.IdentityCodecUtil;
 import org.opendaylight.yangtools.yang.data.util.codec.QNameCodecUtil;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
-import org.opendaylight.yangtools.yang.model.api.Module;
 
 final class IdentityrefXmlCodec implements XmlCodec<QName> {
-    private final EffectiveModelContext schemaContext;
-    private final QNameModule parentModule;
+    private final @NonNull EffectiveModelContext context;
+    private final @NonNull QNameModule parentModule;
 
     IdentityrefXmlCodec(final EffectiveModelContext context, final QNameModule parentModule) {
-        schemaContext = requireNonNull(context);
+        this.context = requireNonNull(context);
         this.parentModule = requireNonNull(parentModule);
     }
 
@@ -40,16 +38,16 @@ final class IdentityrefXmlCodec implements XmlCodec<QName> {
 
     @Override
     public QName parseValue(final NamespaceContext ctx, final String str) {
-        return IdentityCodecUtil.parseIdentity(str, schemaContext, prefix -> {
+        // FIXME: YANGTOOLS-1523: do not trim()
+        return IdentityCodecUtil.parseIdentity(str.trim(), context, prefix -> {
             if (prefix.isEmpty()) {
                 return parentModule;
             }
 
-            final String prefixedNS = ctx.getNamespaceURI(prefix);
+            final var prefixedNS = ctx.getNamespaceURI(prefix);
             checkArgument(prefixedNS != null, "Failed to resolve prefix %s", prefix);
 
-            final Iterator<? extends Module> modules =
-                schemaContext.findModules(XMLNamespace.of(prefixedNS)).iterator();
+            final var modules = context.findModules(XMLNamespace.of(prefixedNS)).iterator();
             checkArgument(modules.hasNext(), "Could not find module for namespace %s", prefixedNS);
             return modules.next().getQNameModule();
         }).getQName();
@@ -57,10 +55,10 @@ final class IdentityrefXmlCodec implements XmlCodec<QName> {
 
     @Override
     public void writeValue(final XMLStreamWriter ctx, final QName value) throws XMLStreamException {
-        final RandomPrefix prefixes = new RandomPrefix(ctx.getNamespaceContext());
-        final String str = QNameCodecUtil.encodeQName(value, uri -> prefixes.encodePrefix(uri.getNamespace()));
+        final var prefixes = new RandomPrefix(ctx.getNamespaceContext());
+        final var str = QNameCodecUtil.encodeQName(value, uri -> prefixes.encodePrefix(uri.getNamespace()));
 
-        for (Entry<XMLNamespace, String> e : prefixes.getPrefixes()) {
+        for (var e : prefixes.getPrefixes()) {
             ctx.writeNamespace(e.getValue(), e.getKey().toString());
         }
         ctx.writeCharacters(str);
index 720728bb4fc147e323fb17a52d1b88b31ec0af41..ca2bb35e4dce46d5d0b4f1f872f962fa8f86b6fb 100644 (file)
@@ -5,20 +5,20 @@
  * 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.codec.xml;
 
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.codec.DataStringCodec;
 
-final class QuotedXmlCodec<T> extends AbstractXmlCodec<T> {
+// FIXME: YANGTOOLS-1523: make this class final
+class QuotedXmlCodec<T> extends AbstractXmlCodec<T> {
     QuotedXmlCodec(final DataStringCodec<T> codec) {
         super(codec);
     }
 
     @Override
-    public void writeValue(final XMLStreamWriter ctx, final T value) throws XMLStreamException {
+    public final void writeValue(final XMLStreamWriter ctx, final T value) throws XMLStreamException {
         ctx.writeCharacters(serialize(value));
     }
 }
diff --git a/codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/StringXmlCodec.java b/codec/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/StringXmlCodec.java
new file mode 100644 (file)
index 0000000..a38a76b
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.codec.xml;
+
+import org.opendaylight.yangtools.yang.data.impl.codec.StringStringCodec;
+
+// FIXME: YANGTOOLS-1523: remove this class
+@Deprecated
+final class StringXmlCodec extends QuotedXmlCodec<String> {
+    StringXmlCodec(final StringStringCodec codec) {
+        super(codec);
+    }
+
+    @Override
+    @Deprecated
+    String trimValue(final String str) {
+        return str;
+    }
+}
index 920b7db8575725fc02db4ef9e2a673487675f735..fbe3fe86ea126ab465ff0a89142fe2a6a58a92e2 100644 (file)
@@ -142,7 +142,8 @@ public final class XmlCodecFactory extends AbstractCodecFactory<XmlCodec<?>> {
 
     @Override
     protected XmlCodec<?> stringCodec(final StringTypeDefinition type) {
-        return new QuotedXmlCodec<>(StringStringCodec.from(type));
+        // FIXME: YANGTOOLS-1523: use QuotedXmlCodec
+        return new StringXmlCodec(StringStringCodec.from(type));
     }
 
     @Override
index 24c704e13e15fc2703135789b18a151453496932..fbbfdff4eb6a2b0f7dd719fc7a74ed6ec3b3197f 100644 (file)
@@ -431,7 +431,7 @@ public final class XmlParserStream implements Closeable, Flushable {
 
         if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
             parent.setAttributes(getElementAttributes(in));
-            setValue((SimpleNodeDataWithSchema<?>) parent, in.getElementText().trim(), in.getNamespaceContext());
+            setValue((SimpleNodeDataWithSchema<?>) parent, in.getElementText(), in.getNamespaceContext());
             if (isNextEndDocument(in)) {
                 return;
             }
index 1b24568963080f1c236867ee014faa80238bf6c5..3e592e551b556a08c14005250ea0ec5790dc302d 100644 (file)
@@ -11,7 +11,6 @@ import static java.util.Objects.requireNonNull;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
-import java.util.Iterator;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
@@ -36,22 +35,22 @@ final class XmlStringInstanceIdentifierCodec extends AbstractModuleStringInstanc
     private final @NonNull XmlCodecFactory codecFactory;
     private final @NonNull EffectiveModelContext context;
 
-    XmlStringInstanceIdentifierCodec(final EffectiveModelContext context, final XmlCodecFactory xmlCodecFactory) {
+    XmlStringInstanceIdentifierCodec(final EffectiveModelContext context, final XmlCodecFactory codecFactory) {
         this.context = requireNonNull(context);
-        this.dataContextTree = DataSchemaContextTree.from(context);
-        this.codecFactory = requireNonNull(xmlCodecFactory);
+        this.codecFactory = requireNonNull(codecFactory);
+        dataContextTree = DataSchemaContextTree.from(context);
     }
 
     @Override
     protected Module moduleForPrefix(final String prefix) {
-        final String prefixedNS = getNamespaceContext().getNamespaceURI(prefix);
-        final Iterator<? extends Module> modules = context.findModules(XMLNamespace.of(prefixedNS)).iterator();
+        final var prefixedNS = getNamespaceContext().getNamespaceURI(prefix);
+        final var modules = context.findModules(XMLNamespace.of(prefixedNS)).iterator();
         return modules.hasNext() ? modules.next() : null;
     }
 
     @Override
     protected String prefixForNamespace(final XMLNamespace namespace) {
-        final Iterator<? extends Module> modules = context.findModules(namespace).iterator();
+        final var modules = context.findModules(namespace).iterator();
         return modules.hasNext() ? modules.next().getName() : null;
     }
 
@@ -85,15 +84,15 @@ final class XmlStringInstanceIdentifierCodec extends AbstractModuleStringInstanc
     public YangInstanceIdentifier parseValue(final NamespaceContext ctx, final String str) {
         pushNamespaceContext(ctx);
         try {
-            return deserialize(str);
+            // FIXME: YANGTOOLS-1523: do not trim()
+            return deserialize(str.trim());
         } finally {
             popNamespaceContext();
         }
     }
 
     @Override
-    public void writeValue(final XMLStreamWriter ctx, final YangInstanceIdentifier value)
-            throws XMLStreamException {
+    public void writeValue(final XMLStreamWriter ctx, final YangInstanceIdentifier value) throws XMLStreamException {
         ctx.writeCharacters(serialize(value));
     }
 
@@ -102,7 +101,7 @@ final class XmlStringInstanceIdentifierCodec extends AbstractModuleStringInstanc
     }
 
     private static void popNamespaceContext() {
-        final Deque<NamespaceContext> stack = TL_CONTEXT.get();
+        final var stack = TL_CONTEXT.get();
         stack.pop();
         if (stack.isEmpty()) {
             TL_CONTEXT.set(null);
@@ -110,7 +109,7 @@ final class XmlStringInstanceIdentifierCodec extends AbstractModuleStringInstanc
     }
 
     private static void pushNamespaceContext(final NamespaceContext context) {
-        Deque<NamespaceContext> stack = TL_CONTEXT.get();
+        var stack = TL_CONTEXT.get();
         if (stack == null) {
             stack = new ArrayDeque<>(1);
             TL_CONTEXT.set(stack);
diff --git a/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1522Test.java b/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1522Test.java
new file mode 100644 (file)
index 0000000..d764bb4
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.codec.xml;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.StringReader;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
+import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+class YT1522Test {
+    @Test
+    void testWhitespaceLeaf() throws Exception {
+        final var context = YangParserTestUtils.parseYang("""
+                module foo {
+                  namespace foo;
+                  prefix foo;
+
+                  leaf foo {
+                    type string {
+                      length 1..4;
+                    }
+                  }
+                }""");
+        final var result = new NormalizationResultHolder();
+        final var streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+        final var xmlParser = XmlParserStream.create(streamWriter,
+            Inference.ofDataTreePath(context, QName.create("foo", "foo")));
+        xmlParser.parse(UntrustedXML.createXMLStreamReader(new StringReader("""
+            <foo xmlns="foo">  </foo>""")));
+        assertEquals(ImmutableNodes.leafNode(QName.create("foo", "foo"), "  "), result.getResult().data());
+    }
+}