From 5977491689926c5355a4a3d3d4fc5a953ecc5189 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 1 Nov 2023 13:21:04 +0530 Subject: [PATCH] Use localpart to validate fieldnames --- ballerina/tests/fromXml_test.bal | 124 +++++++++++++++++- .../ballerina/stdlib/data/xml/XmlParser.java | 69 +++++++--- 2 files changed, 167 insertions(+), 26 deletions(-) diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index d48a99c..2692f64 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -368,7 +368,7 @@ function testXmlStringToRecord31() returns error? { } type Rec record {| - string example\:element1; + string element1; string element2; |}; @@ -381,10 +381,85 @@ function testXmlStringToRecord32() returns error? { `; Rec rec1 = check fromXmlStringWithType(xmlStr1); - test:assertEquals(rec1.example\:element1, "Value 1"); + test:assertEquals(rec1.element1, "Value 1"); test:assertEquals(rec1.element2, "Value 2"); } +@Namespace { + prefix: "x", + uri: "example.com" +} +type NSRec1 record {| + string \#content; +|}; + +@test:Config{} +function testXmlStringToRecord33() returns error? { + string xmlStr1 = string `1`; + NSRec1 rec1 = check fromXmlStringWithType(xmlStr1); + test:assertEquals(rec1.get("#content"), "1"); +} + +@Namespace { + prefix: "x", + uri: "example.com" +} +type NSRec2 record {| + @Namespace { + prefix: "x", + uri: "example.com" + } + string bar; +|}; + +@test:Config{} +function testXmlStringToRecord34() returns error? { + string xmlStr1 = string `1`; + NSRec2 rec1 = check fromXmlStringWithType(xmlStr1); + test:assertEquals(rec1.bar, "1"); +} + +@Namespace { + prefix: "x", + uri: "example.com" +} +type NSRec3 record {| + @Namespace { + prefix: "x", + uri: "example.com" + } + record {| + @Namespace { + uri: "example2.com" + } + string baz; + |} bar; +|}; + +@test:Config{} +function testXmlStringToRecord35() returns error? { + string xmlStr1 = string `2`; + NSRec3 rec1 = check fromXmlStringWithType(xmlStr1); + test:assertEquals(rec1.bar.baz, "2"); +} + +@Namespace { + uri: "example.com" +} +type NSRec4 record {| + @Namespace { + uri: "example.com" + } + string bar; +|}; + +@test:Config{} +function testXmlStringToRecord36() returns error? { + string xmlStr1 = string `1`; + NSRec4 rec1 = check fromXmlStringWithType(xmlStr1); + test:assertEquals(rec1.bar, "1"); +} + // Test projection with fixed array size. type DataProj record {| @@ -542,9 +617,6 @@ function testXmlStringToRecordNegative8() { } type DataN7 record {| - @Name { - value: "ns1:A" - } @Namespace { uri: "www.example.com" } @@ -557,3 +629,45 @@ function testXmlStringToRecordNegative9() { DataN7|error rec1 = fromXmlStringWithType(xmlStr1); test:assertEquals((rec1).message(), "namespace mismatched for the field: A"); } + +@Namespace { + prefix: "x", + uri: "example.com" +} +type DataN8 record {| + @Namespace { + uri: "example.com" + } + string bar; +|}; + +@test:Config{} +function testXmlStringToRecordNegative10() { + string xmlStr1 = string `1`; + DataN8|error rec1 = fromXmlStringWithType(xmlStr1); + test:assertEquals((rec1).message(), "namespace mismatched for the field: bar"); +} + +@Namespace { + prefix: "x", + uri: "example.com" +} +type DataN9 record {| + @Namespace { + prefix: "x", + uri: "example.com" + } + record {| + @Namespace { + uri: "example.com" + } + string baz; + |} bar; +|}; + +@test:Config{} +function testXmlStringToRecordNegative11() { + string xmlStr1 = string `2`; + DataN9|error rec1 = fromXmlStringWithType(xmlStr1); + test:assertEquals((rec1).message(), "namespace mismatched for the field: baz"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java index b6f4005..b6190fb 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java @@ -202,7 +202,7 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, XmlParserData xml xmlParserData.rootElement = validateAndGetXmlNameFromRecordAnnotation(rootRecord, rootRecord.getName(), elementName); - validateNamespace(xmlStreamReader, rootRecord, false, xmlParserData); + validateTypeNamespace(xmlStreamReader, rootRecord); // Keep track of fields and attributes xmlParserData.fieldHierarchy.push(new HashMap<>(getAllFieldsInRecordType(rootRecord, xmlParserData))); @@ -391,10 +391,9 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse return; } - validateNamespace(xmlStreamReader, xmlParserData.rootRecord, true, xmlParserData); - Field currentField = xmlParserData.currentField; String fieldName = currentField.getFieldName(); + validateFieldNamespace(xmlStreamReader, xmlParserData.rootRecord, fieldName, xmlParserData); Object temp = currentNode.get(StringUtils.fromString(fieldName)); if (!xmlParserData.siblings.containsKey(elemName)) { xmlParserData.siblings.put(elemName, false); @@ -428,7 +427,7 @@ private void updateNextRecord(XMLStreamReader xmlStreamReader, XmlParserData xml xmlParserData.rootRecord = recordType; updateNextValue(recordType, fieldName, fieldType, xmlParserData); xmlParserData.attributeHierarchy.push(new HashMap<>(getAllAttributesInRecordType(recordType))); - validateNamespace(xmlStreamReader, recordType, false, xmlParserData); + validateTypeNamespace(xmlStreamReader, recordType); handleAttributes(xmlStreamReader, xmlParserData); } @@ -646,7 +645,14 @@ private Map getAllFieldsInRecordType(RecordType recordType, XmlPa Map fields = new HashMap<>(); Map recordFields = recordType.getFields(); for (String key : recordFields.keySet()) { - fields.put(modifiedNames.getOrDefault(key, key), recordFields.get(key)); + String modifiedName = modifiedNames.getOrDefault(key, key); + if (fields.containsKey(modifiedName)) { + // TODO: Handle the cases with different namespace by representing field with QName. + // eg:- + throw DataUtils.getXmlError("Duplicate field '" + modifiedName + "'"); + } + + fields.put(modifiedName, recordFields.get(key)); } xmlParserData.modifiedNamesHierarchy.add(modifiedNames); return fields; @@ -661,9 +667,8 @@ private String getModifiedName(Map fieldAnnotation, String attr return attributeName; } - private void validateNamespace(XMLStreamReader xmlStreamReader, RecordType recordType, boolean isField, - XmlParserData xmlParserData) { - ArrayList namespace = getNamespace(recordType, isField); + private void validateTypeNamespace(XMLStreamReader xmlStreamReader, RecordType recordType) { + ArrayList namespace = getNamespace(recordType); if (namespace.isEmpty()) { return; @@ -673,29 +678,54 @@ private void validateNamespace(XMLStreamReader xmlStreamReader, RecordType recor && xmlStreamReader.getName().getNamespaceURI().equals(namespace.get(1))) { return; } + throw DataUtils.getXmlError("namespace mismatched for the type: " + + recordType.getName()); + } - if (isField) { - throw DataUtils.getXmlError("namespace mismatched for the field: " - + xmlParserData.currentField.getFieldName()); - } else { - throw DataUtils.getXmlError("namespace mismatched for the type: " - + recordType.getName()); + private void validateFieldNamespace(XMLStreamReader xmlStreamReader, RecordType recordType, String fieldName, + XmlParserData xmlParserData) { + ArrayList namespace = getFieldNamespace(recordType, fieldName); + + if (namespace.isEmpty()) { + return; } + + if (xmlStreamReader.getName().getPrefix().equals(namespace.get(0)) + && xmlStreamReader.getName().getNamespaceURI().equals(namespace.get(1))) { + return; + } + throw DataUtils.getXmlError("namespace mismatched for the field: " + xmlParserData.currentField.getFieldName()); } - private ArrayList getNamespace(RecordType recordType, boolean isField) { + private ArrayList getNamespace(RecordType recordType) { BMap annotations = recordType.getAnnotations(); String namespacePrefix = null; String namespaceUri = null; for (BString annotationsKey : annotations.getKeys()) { String key = annotationsKey.getValue(); - if (!isField && !key.contains(Constants.FIELD) && key.endsWith(Constants.NAME_SPACE)) { + if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAME_SPACE)) { BMap namespaceAnnotation = (BMap) annotations.get(annotationsKey); namespacePrefix = namespaceAnnotation.containsKey(Constants.PREFIX) ? ((BString) namespaceAnnotation.get(Constants.PREFIX)).getValue() : ""; namespaceUri = ((BString) namespaceAnnotation.get(Constants.URI)).getValue(); break; - } else if (isField && key.contains(Constants.FIELD)) { + } + } + ArrayList namespace = new ArrayList<>(); + if (namespacePrefix != null && namespaceUri != null) { + namespace.add(namespacePrefix); + namespace.add(namespaceUri); + } + return namespace; + } + + private ArrayList getFieldNamespace(RecordType recordType, String fieldName) { + BMap annotations = recordType.getAnnotations(); + String namespacePrefix = null; + String namespaceUri = null; + for (BString annotationsKey : annotations.getKeys()) { + String key = annotationsKey.getValue(); + if (key.contains(Constants.FIELD) && key.split("\\$field\\$\\.")[1].equals(fieldName)) { BMap namespaceAnnotation = (BMap) annotations.get(annotationsKey); for (BString keyStr : namespaceAnnotation.getKeys()) { if (keyStr.getValue().endsWith(Constants.NAME_SPACE)) { @@ -770,10 +800,7 @@ private Optional handleRecordRestType(XmlParserData xmlParserData, XMLSt } private String getElementName(XMLStreamReader xmlStreamReader) { - QName qName = xmlStreamReader.getName(); - String prefix = qName.getPrefix(); - String attributeName = qName.getLocalPart(); - return prefix.equals("") ? attributeName : prefix + ":" + attributeName; + return xmlStreamReader.getName().getLocalPart(); } static class XmlParserData {