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