From dbd1b7e79921cdc6ec6dfab55507015f5569d856 Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Thu, 22 Feb 2024 18:34:44 +0530
Subject: [PATCH 1/8] Support anydata and json as expected type of field
---
ballerina/tests/fromXml_test.bal | 368 ++++++++++++++++++
.../stdlib/data/xmldata/xml/XmlParser.java | 73 +++-
.../stdlib/data/xmldata/xml/XmlTraversal.java | 55 ++-
3 files changed, 475 insertions(+), 21 deletions(-)
diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal
index 6ed5ca56..58e75af2 100644
--- a/ballerina/tests/fromXml_test.bal
+++ b/ballerina/tests/fromXml_test.bal
@@ -1620,6 +1620,374 @@ function testCommentMiddleInContent2() returns error? {
test:assertEquals(rec2.A, "John Doe");
}
+@test:Config
+function testAnydataAsFieldTypeWiThFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ John Doe
+ 30
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, {
+ "Name": "John Doe",
+ "Age": "30"
+ });
+
+ string xmlStr2 = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]
+ );
+
+ string xmlStr3 = string `
+ WSO2
+ `;
+ record {
+ anydata name;
+ } rec3 = check fromXmlStringWithType(xmlStr3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.name, "WSO2");
+}
+
+@test:Config
+function testAnydataAsFieldTypeWiThFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+
+ John Doe
+ 30
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, {
+ "Name": "John Doe",
+ "Age": "30"
+ });
+
+ xml xmlVal2 = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]
+ );
+
+ xml xmlVal3 = xml `
+ WSO2
+ `;
+ record {
+ anydata name;
+ } rec3 = check fromXmlWithType(xmlVal3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.name, "WSO2");
+}
+
+@test:Config
+function testJsonAsFieldTypeWiThFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ John Doe
+ 30
+
+ `;
+ record {|
+ json Employee;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, {
+ "Name": "John Doe",
+ "Age": "30"
+ });
+
+ string xmlStr2 = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ json Employee;
+ |} rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]
+ );
+
+ string xmlStr3 = string `
+ WSO2
+ `;
+ record {
+ json name;
+ } rec3 = check fromXmlStringWithType(xmlStr3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.name, "WSO2");
+}
+
+@test:Config
+function testJsonAsFieldTypeWiThFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+
+ John Doe
+ 30
+
+ `;
+ record {|
+ json Employee;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, {
+ "Name": "John Doe",
+ "Age": "30"
+ });
+
+ xml xmlVal2 = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ json Employee;
+ |} rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]
+ );
+
+ xml xmlVal3 = xml `
+ WSO2
+ `;
+ record {
+ json name;
+ } rec3 = check fromXmlWithType(xmlVal3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.name, "WSO2");
+}
+
+@test:Config
+function testAnydataArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata[] Employee;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+
+ string xmlStr2 = string `
+ WSO2
+ Apple
+ `;
+ record {
+ anydata[] name;
+ } rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+}
+
+@test:Config
+function testJsonArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ json[] Employee;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+
+ string xmlStr2 = string `
+ WSO2
+ Apple
+ `;
+ record {
+ json[] name;
+ } rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+}
+
+@test:Config
+function testAnydataArrayAsFieldTypeWiThFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata[] Employee;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+
+ xml xmlVal2 = xml `
+ WSO2
+ Apple
+ `;
+ record {
+ anydata[] name;
+ } rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+}
+
+@test:Config
+function testJsonArrayAsFieldTypeWiThFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ json[] Employee;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+
+ xml xmlVal2 = xml `
+ WSO2
+ Apple
+ `;
+ record {
+ json[] name;
+ } rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+}
+
// Negative cases
type DataN1 record {|
int A;
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index 086c5acc..0806b870 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -276,13 +276,26 @@ private void readText(XMLStreamReader xmlStreamReader,
return;
}
- if (fieldType.getTag() == TypeTags.RECORD_TYPE_TAG) {
- handleContentFieldInRecordType((RecordType) fieldType, bText, xmlParserData);
- } else if (fieldType.getTag() == TypeTags.ARRAY_TAG
- && ((ArrayType) fieldType).getElementType().getTag() == TypeTags.RECORD_TYPE_TAG) {
- handleContentFieldInRecordType((RecordType) ((ArrayType) fieldType).getElementType(), bText, xmlParserData);
- } else {
- xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
+ switch (fieldType.getTag()) {
+ case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType, bText,
+ xmlParserData);
+ case TypeTags.ARRAY_TAG -> {
+ int elementTypeTag = ((ArrayType) fieldType).getElementType().getTag();
+ if (elementTypeTag == TypeTags.RECORD_TYPE_TAG) {
+ handleContentFieldInRecordType((RecordType) ((ArrayType) fieldType).getElementType(), bText,
+ xmlParserData);
+ } else if (elementTypeTag == TypeTags.ANYDATA_TAG || elementTypeTag == TypeTags.JSON_TAG) {
+ BArray tempArr = (BArray) ((BMap) xmlParserData.nodesStack.peek()).get(bFieldName);
+ tempArr.add(tempArr.getLength() - 1, convertStringToRestExpType(bText, fieldType));
+ } else {
+ xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
+ }
+ }
+ case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
+ xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.peek();
+ xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
+ }
+ default -> xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
}
}
@@ -440,16 +453,50 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse
ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(referredType)));
}
- if (referredType.getTag() == TypeTags.RECORD_TYPE_TAG) {
- updateNextRecord(xmlStreamReader, xmlParserData, fieldName, fieldType, (RecordType) referredType);
+ switch (referredType.getTag()) {
+ case TypeTags.RECORD_TYPE_TAG -> updateNextRecord(xmlStreamReader, xmlParserData, fieldName,
+ fieldType, (RecordType) referredType);
+ case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
+ updateNextMap(xmlParserData, fieldName, referredType);
}
}
- case TypeTags.MAP_TAG -> {
- RecordType recordType = TypeCreator.createRecordType(Constants.ANON_TYPE, fieldType.getPackage(), 0,
- new HashMap<>(), ((MapType) fieldType).getConstrainedType(), false, 0);
- updateNextRecord(xmlStreamReader, xmlParserData, fieldName, fieldType, recordType);
+ case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
+ updateNextMap(xmlParserData, fieldName, fieldType);
+ }
+ }
+
+ private void updateNextMap(XmlParserData xmlParserData, String fieldName, Type fieldType) {
+ xmlParserData.parents.push(xmlParserData.siblings);
+ xmlParserData.siblings = new LinkedHashMap<>();
+ xmlParserData.currentNode = updateNextMapValue(xmlParserData, fieldName, fieldType);
+ handleAttributes(xmlStreamReader, xmlParserData);
+ }
+
+ private BMap updateNextMapValue(XmlParserData xmlParserData, String fieldName, Type fieldType) {
+ BMap nextValue;
+ if (fieldType.getTag() == TypeTags.MAP_TAG) {
+ nextValue = ValueCreator.createMapValue((MapType) fieldType);
+ xmlParserData.restTypes.push(((MapType) fieldType).getConstrainedType());
+ } else {
+ nextValue = ValueCreator.createMapValue(TypeCreator.createMapType(fieldType));
+ xmlParserData.restTypes.push(fieldType);
+ }
+
+ xmlParserData.attributeHierarchy.push(new HashMap<>());
+ xmlParserData.fieldHierarchy.push(new HashMap<>());
+ xmlParserData.recordTypeStack.push(null);
+ BMap currentNode = xmlParserData.currentNode;
+ Object temp = currentNode.get(StringUtils.fromString(fieldName));
+ if (temp instanceof BArray) {
+ int arraySize = ((ArrayType) TypeUtils.getType(temp)).getSize();
+ if (arraySize > ((BArray) temp).getLength() || arraySize == -1) {
+ ((BArray) temp).append(nextValue);
}
+ } else {
+ currentNode.put(StringUtils.fromString(fieldName), nextValue);
}
+ xmlParserData.nodesStack.push(currentNode);
+ return nextValue;
}
private void updateNextRecord(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData, String fieldName,
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
index 0d6afbd9..203f3da1 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
@@ -182,7 +182,8 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) {
mapValue.put(bCurrentFieldName, array);
}
Type elementType = TypeUtils.getReferredType(((ArrayType) currentFieldType).getElementType());
- if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) {
+ int elementTypeTag = elementType.getTag();
+ if (elementTypeTag == TypeTags.RECORD_TYPE_TAG) {
currentNode = updateNextRecord(xmlItem, (RecordType) elementType, fieldName,
currentFieldType, mapValue, analyzerData);
traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData);
@@ -190,22 +191,52 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) {
DataUtils.removeExpectedTypeStacks(analyzerData);
currentNode = analyzerData.nodesStack.pop();
return;
+ } else if (elementTypeTag == TypeTags.MAP_TAG) {
+ updateNextMap(elementType, analyzerData);
+ currentNode = updateNextValue(currentFieldType, fieldName, currentFieldType, mapValue,
+ analyzerData);
+ traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData);
+ DataUtils.validateRequiredFields((BMap) currentNode, analyzerData);
+ DataUtils.removeExpectedTypeStacks(analyzerData);
+ currentNode = analyzerData.nodesStack.pop();
+ return;
+ } else if (elementTypeTag == TypeTags.JSON_TAG || elementTypeTag == TypeTags.ANYDATA_TAG) {
+ updateNextMap(elementType, analyzerData);
+ convertWithRestType(xmlItem, elementType, analyzerData);
+ DataUtils.removeExpectedTypeStacks(analyzerData);
+ return;
}
}
case TypeTags.MAP_TAG -> {
- RecordType recordType = TypeCreator.createRecordType("$anonType$", currentFieldType.getPackage(), 0,
- new HashMap<>(), ((MapType) currentFieldType).getConstrainedType(), false, 0);
- currentNode = updateNextRecord(xmlItem, recordType, fieldName,
- currentFieldType, mapValue, analyzerData);
- traverseXml(xmlItem.getChildrenSeq(), recordType, analyzerData);
+ updateNextMap(currentFieldType, analyzerData);
+ currentNode = updateNextValue(currentFieldType, fieldName, currentFieldType, mapValue,
+ analyzerData);
+ traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData);
DataUtils.validateRequiredFields((BMap) currentNode, analyzerData);
DataUtils.removeExpectedTypeStacks(analyzerData);
currentNode = analyzerData.nodesStack.pop();
+ return;
+ }
+ case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> {
+ updateNextMap(currentFieldType, analyzerData);
+ convertWithRestType(xmlItem, currentFieldType, analyzerData);
+ DataUtils.removeExpectedTypeStacks(analyzerData);
+ return;
}
}
traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData);
}
+ private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) {
+ if (fieldType.getTag() == TypeTags.MAP_TAG) {
+ analyzerData.restTypes.push(((MapType) fieldType).getConstrainedType());
+ } else {
+ analyzerData.restTypes.push(fieldType);
+ }
+ analyzerData.fieldHierarchy.push(new HashMap<>());
+ analyzerData.attributeHierarchy.push(new HashMap<>());
+ }
+
private BMap updateNextRecord(BXmlItem xmlItem, RecordType recordType, String fieldName,
Type fieldType, BMap currentMapValue,
XmlAnalyzerData analyzerData) {
@@ -218,10 +249,18 @@ private BMap updateNextRecord(BXmlItem xmlItem, RecordType reco
return nextValue;
}
- private BMap updateNextValue(RecordType recordType, String fieldName, Type fieldType,
+ private BMap updateNextValue(Type type, String fieldName, Type fieldType,
BMap currentMapValue, XmlAnalyzerData analyzerData) {
analyzerData.currentField = null;
- BMap nextValue = ValueCreator.createRecordValue(recordType);
+
+ BMap nextValue;
+ switch (type.getTag()) {
+ case TypeTags.RECORD_TYPE_TAG -> nextValue = ValueCreator.createRecordValue((RecordType) type);
+ case TypeTags.MAP_TAG -> nextValue = ValueCreator.createMapValue((MapType) type);
+ case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
+ nextValue = ValueCreator.createMapValue(TypeCreator.createMapType(type));
+ default -> throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, type);
+ }
Object temp = currentMapValue.get(StringUtils.fromString(fieldName));
if (temp instanceof BArray) {
From b21a2aba46754ae0cdb87681f01135513303c3fe Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Thu, 22 Feb 2024 19:03:43 +0530
Subject: [PATCH 2/8] Correct mistakes in the code
---
.github/workflows/publish-release.yml | 2 +-
README.md | 124 ++++++++++++------
ballerina/Ballerina.toml | 2 +-
ballerina/Dependencies.toml | 2 +-
build-config/resources/Ballerina.toml | 2 +-
.../data/xmldata/compiler/Constants.java | 5 +
.../xmldata/compiler/XmldataCodeAnalyzer.java | 2 +
.../compiler/XmldataCompilerPlugin.java | 2 +
.../compiler/XmldataDiagnosticCodes.java | 2 +
.../compiler/XmldataRecordFieldValidator.java | 18 ++-
gradle.properties | 2 +-
.../stdlib/data/xmldata/FromString.java | 52 +++++---
.../stdlib/data/xmldata/utils/DataUtils.java | 4 +-
.../stdlib/data/xmldata/xml/XmlParser.java | 10 +-
14 files changed, 159 insertions(+), 70 deletions(-)
diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index 2091ca07..10761943 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -12,5 +12,5 @@ jobs:
uses: ballerina-platform/ballerina-standard-library/.github/workflows/release-package-template.yml@main
secrets: inherit
with:
- package-name: persist
+ package-name: data.xmldata
package-org: ballerina
diff --git a/README.md b/README.md
index 179a6a05..a1ebca89 100644
--- a/README.md
+++ b/README.md
@@ -31,11 +31,11 @@ public function main() returns error? {
io:println(book);
}
-type Book record {|
+type Book record {
int id;
string title;
string author;
-|};
+};
```
### Converting an external XML document to a Record value
@@ -52,11 +52,11 @@ public function main() returns error? {
io:println(book);
}
-type Book record {|
+type Book record {
int id;
string title;
string author;
-|};
+};
```
Make sure to handle possible errors that may arise during the file reading or XML to record conversion process. The `check` keyword is utilized to handle these errors, but more sophisticated error handling can be implemented as per your requirements.
@@ -80,11 +80,11 @@ XML data is inherently hierarchical, forming a tree structure. In the given exam
A straightforward record representation of the above XML data is:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
string author;
-|};
+};
```
In this representation, the XML data is efficiently translated into a record value. The `book` element is mapped to a record of type `Book`, and the child elements `id`, `title`, and `author` are converted into record fields of types `int` and `string` correspondingly.
@@ -108,27 +108,27 @@ Consider the XML snippet:
The canonical representation of the above XML as a Ballerina record is:
```ballerina
-type Book record {|
+type Book record {
int id;
- string 'title\-name';
- string 'author\-name';
-|};
+ string title\-name;
+ string author\-name;
+};
```
-Observe how the XML element names `title-name` and `author-name` are represented using delimited identifiers in Ballerina; the `-` characters in the XML element names are escaped using the `\` character.
+Observe how the XML element names `title-name` and `author-name` are represented using delimited identifiers in Ballerina; the `-` characters in the XML element names are escaped using the `\ ` character.
Moreover, the `@Name` annotation can be utilized to explicitly specify the name of the record field, providing control over the translation process:
```ballerina
import ballerina/data.xmldata;
-type Book record {|
+type Book record {
int id;
@xmldata:Name { value: "title-name" }
string title;
@xmldata:Name { value: "author-name" }
string author;
-|};
+};
```
### XML Attributes
@@ -148,16 +148,16 @@ Consider the following XML snippet:
The canonical representation of the above XML as a Ballerina record is:
```ballerina
-type Book record {|
+type Book record {
string lang;
decimal price;
int id;
string title;
string author;
-|};
+};
```
-Additionally the `@Attribute` annotation can be utilized to explicitly specify the name of the record field, providing control over the translation process.
+Additionally, the `@Attribute` annotation can be used to explicitly specify the field as an attribute providing control over the translation process. When element and attribute have same name in the same scope the priority is given to the element unless the expected record field has the `@Attribute` annotation.
### Child Elements
@@ -179,16 +179,16 @@ Examine the XML snippet below:
The canonical representation of the above XML as a Ballerina record is:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
Author author;
-|};
+};
-type Author record {|
+type Author record {
string name;
string country;
-|};
+};
```
In this transformation, child elements, like the `author` element containing its own sub-elements, are converted into nested records. This maintains the hierarchical structure of the XML data within the Ballerina type system, enabling intuitive and type-safe data manipulation.
@@ -198,14 +198,14 @@ Alternatively, inline type definitions offer a compact method for representing c
Consider the subsequent Ballerina record definition, which employs inline type definition for the `author` field:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
- record {|
+ record {
string name;
string country;
- |} author;
-|};
+ } author;
+};
```
### XML Text Content
@@ -227,13 +227,13 @@ Consider the XML snippet below:
The translation into a Ballerina record would be as follows:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
string author;
boolean available;
decimal price;
-|};
+};
```
In scenarios where the parent XML element of text content also includes attributes, the XML text content can be represented by a `string` type field named `#content` within a record type, with the attributes being mapped to their respective fields.
@@ -251,20 +251,18 @@ For instance, examine this XML:
The canonical translation of XML to a Ballerina record is as such:
```ballerina
-type Book record {|
+type Book record {
int id;
Title title;
decimal price;
-|};
+};
-type Title record {|
+type Title record {
string \#content;
string lang;
-|};
+};
```
-Modifications to the default behavior for converting numerical values can be achieved by providing `Options` mappings to the respective functions. This enables developers to choose specific data types and exert finer control over the conversion process.
-
### XML Namespaces
XML namespaces are accommodated by the library, supporting the translation of XML data that contains namespace prefixes. However, the presence of XML namespaces is not mandatory, and the library is capable of processing XML data without namespaces. Should namespaces be present, they will be utilized to resolve the names of XML elements and attributes.
@@ -284,11 +282,11 @@ Examine the XML snippet below with default namespaces:
The translation into a Ballerina record would be:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
string author;
-|};
+};
```
Incorporating namespace validation yields:
@@ -299,11 +297,11 @@ import ballerina/data.xmldata;
@xmldata:Namespace {
uri: "http://example.com/book"
}
-type Book record {|
+type Book record {
int id;
string title;
string author;
-|};
+};
```
Here is the same XML snippet with a namespace prefix:
@@ -321,13 +319,59 @@ The translation into a Ballerina record would be:
```ballerina
import ballerina/data.xmldata;
+@xmldata:Namespace {
+ uri: "http://example.com/book"
+}
+type Book record {|
+ @xmldata:Namespace {
+ uri: "http://example.com/book"
+ }
+ int id;
+ @xmldata:Namespace {
+ uri: "http://example.com/book"
+ }
+ string title;
+ @xmldata:Namespace {
+ uri: "http://example.com/book"
+ }
+ string author;
+|};
+```
+
+Here is the same XML snippet with a namespace prefix:
+
+```xml
+
+ 0
+ string
+ string
+
+```
+
+The translation into a Ballerina record would be:
+
+```ballerina
+import ballerina/data.xmldata;
+
@xmldata:Namespace {
uri: "http://example.com/book",
prefix: "bk"
}
type Book record {|
+ @xmldata:Namespace {
+ uri: "http://example.com/book",
+ prefix: "bk"
+ }
int id;
+ @xmldata:Namespace {
+ uri: "http://example.com/book",
+ prefix: "bk"
+ }
string title;
+ @xmldata:Namespace {
+ uri: "http://example.com/author",
+ prefix: "au"
+ }
string author;
|};
```
@@ -353,11 +397,11 @@ Take the following XML snippet as an example:
The canonical representation of this XML as a Ballerina record is:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
string[] author;
-|};
+};
```
### Controlling Which Elements to Convert
@@ -387,10 +431,10 @@ type Book record {|
However, if the rest field is utilized (or if the record type is defined as an open record), all elements in the XML data will be transformed into record fields:
```ballerina
-type Book record {|
+type Book record {
int id;
string title;
-|};
+};
```
In this instance, all other elements in the XML data, such as `author` and `price` along with their attributes, will be transformed into `string` type fields with the corresponding element name as the key.
diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index 72f618c6..1dc9f2b0 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -6,7 +6,7 @@ authors = ["Ballerina"]
keywords = ["xml"]
repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata"
license = ["Apache-2.0"]
-distribution = "2201.8.1"
+distribution = "2201.8.4"
export = ["data.xmldata"]
[[platform.java17.dependency]]
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index eb356073..5e8838a3 100644
--- a/ballerina/Dependencies.toml
+++ b/ballerina/Dependencies.toml
@@ -5,7 +5,7 @@
[ballerina]
dependencies-toml-version = "2"
-distribution-version = "2201.8.1"
+distribution-version = "2201.8.4"
[[package]]
org = "ballerina"
diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml
index c8803eef..aef7e4b8 100644
--- a/build-config/resources/Ballerina.toml
+++ b/build-config/resources/Ballerina.toml
@@ -6,7 +6,7 @@ authors = ["Ballerina"]
keywords = ["xml"]
repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata"
license = ["Apache-2.0"]
-distribution = "2201.8.1"
+distribution = "2201.8.4"
export = ["data.xmldata"]
[[platform.java17.dependency]]
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/Constants.java
index 8d7b6b5e..40eb3a16 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/Constants.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/Constants.java
@@ -1,5 +1,10 @@
package io.ballerina.stdlib.data.xmldata.compiler;
+/**
+ * Constants for XmlData's compiler plugin.
+ *
+ * @since 0.1.0
+ */
public class Constants {
static final String FROM_XML_STRING_WITH_TYPE = "fromXmlStringWithType";
static final String FROM_XML_WITH_TYPE = "fromXmlWithType";
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCodeAnalyzer.java
index 05c14e8b..caaa4428 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCodeAnalyzer.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCodeAnalyzer.java
@@ -26,6 +26,8 @@
/**
* Xmldata Code Analyzer.
+ *
+ * @since 0.1.0
*/
public class XmldataCodeAnalyzer extends CodeAnalyzer {
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCompilerPlugin.java
index 96e452a6..bda18070 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCompilerPlugin.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataCompilerPlugin.java
@@ -23,6 +23,8 @@
/**
* Compiler plugin for Xmldata's utils functions.
+ *
+ * @since 0.1.0
*/
public class XmldataCompilerPlugin extends CompilerPlugin {
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
index 17f03f6f..72b305cc 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
@@ -25,6 +25,8 @@
/**
* Enum class to hold xmldata module diagnostic codes.
+ *
+ * @since 0.1.0
*/
public enum XmldataDiagnosticCodes {
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
index 52521ca7..9473c319 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
@@ -62,6 +62,11 @@
import java.util.Map;
import java.util.Optional;
+/**
+ * Xmldata Record Field Validator.
+ *
+ * @since 0.1.0
+ */
public class XmldataRecordFieldValidator implements AnalysisTask {
private SemanticModel semanticModel;
@@ -99,15 +104,22 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini
}
VariableDeclarationNode variableDeclarationNode = (VariableDeclarationNode) node;
Optional initializer = variableDeclarationNode.initializer();
- if (initializer.isEmpty() || !isFromXmlFunctionFromXmldata(initializer.get())) {
+ if (initializer.isEmpty()) {
continue;
}
-
Optional symbol = semanticModel.symbol(variableDeclarationNode.typedBindingPattern());
if (symbol.isEmpty()) {
continue;
}
+
TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor();
+ if (!isFromXmlFunctionFromXmldata(initializer.get())) {
+ if (typeSymbol.typeKind() == TypeDescKind.RECORD) {
+ validateRecordFields((RecordTypeSymbol) typeSymbol, ctx);
+ }
+ continue;
+ }
+
if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) {
typeSymbol = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor();
if (typeSymbol.typeKind() != TypeDescKind.RECORD) {
@@ -173,8 +185,8 @@ private void validateRecordTypeDefinition(TypeDefinitionNode typeDefinitionNode,
private void validateRecordFields(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) {
List fieldMembers = new ArrayList<>();
for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) {
- detectDuplicateFields(entry.getKey(), entry.getValue(), fieldMembers, ctx);
RecordFieldSymbol fieldSymbol = entry.getValue();
+ detectDuplicateFields(entry.getKey(), fieldSymbol, fieldMembers, ctx);
if (fieldSymbol.typeDescriptor().typeKind() != TypeDescKind.TYPE_REFERENCE) {
continue;
}
diff --git a/gradle.properties b/gradle.properties
index 538c62af..b37a623d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
org.gradle.caching=true
group=io.ballerina.stdlib
version=0.1.0-SNAPSHOT
-ballerinaLangVersion=2201.8.1
+ballerinaLangVersion=2201.8.4
checkstyleToolVersion=10.12.0
puppycrawlCheckstyleVersion=10.12.0
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/FromString.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/FromString.java
index fbf73a3b..bfb6c51d 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/FromString.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/FromString.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com).
+ * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
@@ -20,10 +20,12 @@
import io.ballerina.runtime.api.PredefinedTypes;
import io.ballerina.runtime.api.TypeTags;
+import io.ballerina.runtime.api.creators.TypeCreator;
import io.ballerina.runtime.api.creators.ValueCreator;
import io.ballerina.runtime.api.types.ReferenceType;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.types.UnionType;
+import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.api.values.BDecimal;
import io.ballerina.runtime.api.values.BError;
import io.ballerina.runtime.api.values.BString;
@@ -31,16 +33,37 @@
import io.ballerina.stdlib.data.xmldata.utils.DiagnosticErrorCode;
import io.ballerina.stdlib.data.xmldata.utils.DiagnosticLog;
+import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/**
* Native implementation of data:fromStringWithType(string).
- *
+ *
* @since 0.1.0
*/
public class FromString {
+ private static final List TYPE_PRIORITY_ORDER = List.of(
+ TypeTags.INT_TAG,
+ TypeTags.FLOAT_TAG,
+ TypeTags.DECIMAL_TAG,
+ TypeTags.NULL_TAG,
+ TypeTags.BOOLEAN_TAG,
+ TypeTags.JSON_TAG,
+ TypeTags.STRING_TAG
+ );
+
+ private static final List BASIC_JSON_MEMBER_TYPES = List.of(
+ PredefinedTypes.TYPE_NULL,
+ PredefinedTypes.TYPE_BOOLEAN,
+ PredefinedTypes.TYPE_INT,
+ PredefinedTypes.TYPE_FLOAT,
+ PredefinedTypes.TYPE_DECIMAL,
+ PredefinedTypes.TYPE_STRING
+ );
+ private static final UnionType JSON_TYPE_WITH_BASIC_TYPES = TypeCreator.createUnionType(BASIC_JSON_MEMBER_TYPES);
+
public static Object fromStringWithType(BString string, BTypedesc typed) {
Type expType = typed.getDescribingType();
@@ -51,11 +74,7 @@ public static Object fromStringWithType(BString string, BTypedesc typed) {
}
}
- public static Object fromStringWithTypeInternal(BString string, Type expType) {
- return fromStringWithType(string, expType);
- }
-
- private static Object fromStringWithType(BString string, Type expType) {
+ public static Object fromStringWithType(BString string, Type expType) {
String value = string.getValue();
try {
switch (expType.getTag()) {
@@ -73,6 +92,8 @@ private static Object fromStringWithType(BString string, Type expType) {
return stringToNull(value);
case TypeTags.UNION_TAG:
return stringToUnion(string, (UnionType) expType);
+ case TypeTags.JSON_TAG:
+ return stringToUnion(string, JSON_TYPE_WITH_BASIC_TYPES);
case TypeTags.TYPE_REFERENCED_TYPE_TAG:
return fromStringWithType(string, ((ReferenceType) expType).getReferredType());
default:
@@ -97,7 +118,7 @@ private static Double stringToFloat(String value) throws NumberFormatException {
private static BDecimal stringToDecimal(String value) throws NumberFormatException {
return ValueCreator.createDecimalValue(value);
}
-
+
private static Object stringToBoolean(String value) throws NumberFormatException {
if ("true".equalsIgnoreCase(value) || "1".equalsIgnoreCase(value)) {
return true;
@@ -117,16 +138,13 @@ private static Object stringToNull(String value) throws NumberFormatException {
}
private static Object stringToUnion(BString string, UnionType expType) throws NumberFormatException {
- List memberTypes = expType.getMemberTypes();
- memberTypes.sort(Comparator.comparingInt(t -> t.getTag()));
- boolean isStringExpType = false;
+ List memberTypes = new ArrayList<>(expType.getMemberTypes());
+ memberTypes.sort(Comparator.comparingInt(t -> TYPE_PRIORITY_ORDER.indexOf(
+ TypeUtils.getReferredType(t).getTag())));
for (Type memberType : memberTypes) {
try {
Object result = fromStringWithType(string, memberType);
- if (result instanceof BString) {
- isStringExpType = true;
- continue;
- } else if (result instanceof BError) {
+ if (result instanceof BError) {
continue;
}
return result;
@@ -134,10 +152,6 @@ private static Object stringToUnion(BString string, UnionType expType) throws Nu
// Skip
}
}
-
- if (isStringExpType) {
- return string;
- }
return returnError(string.getValue(), expType.toString());
}
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
index b8946ac6..6674d87a 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
@@ -201,9 +201,9 @@ public static Object convertStringToExpType(BString value, Type expType) {
Object result;
switch (expType.getTag()) {
case TypeTags.ANYDATA_TAG, TypeTags.ANY_TAG, TypeTags.JSON_TAG ->
- result = FromString.fromStringWithTypeInternal(value, PredefinedTypes.TYPE_STRING);
+ result = FromString.fromStringWithType(value, PredefinedTypes.TYPE_STRING);
case TypeTags.ARRAY_TAG -> result = convertStringToExpType(value, ((ArrayType) expType).getElementType());
- default -> result = FromString.fromStringWithTypeInternal(value, expType);
+ default -> result = FromString.fromStringWithType(value, expType);
}
if (result instanceof BError) {
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index 0806b870..f00a2527 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -338,7 +338,7 @@ private Object convertStringToExpType(BString value, Type expType) {
if (expType.getTag() == TypeTags.ARRAY_TAG) {
expType = ((ArrayType) expType).getElementType();
}
- Object result = FromString.fromStringWithTypeInternal(value, expType);
+ Object result = FromString.fromStringWithType(value, expType);
if (result instanceof BError) {
throw (BError) result;
}
@@ -420,6 +420,14 @@ private void validateRequiredFields(XmlParserData xmlParserData) {
private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) {
QualifiedName elemQName = getElementName(xmlStreamReader);
+ // Assume type of record field `name` is `string[]` and
+ // relevant source position is `11`
+ for (QualifiedName key : xmlParserData.fieldHierarchy.peek().keySet()) {
+ if (key.equals(elemQName)) {
+ elemQName = key;
+ break;
+ }
+ }
Field currentField = xmlParserData.fieldHierarchy.peek().get(elemQName);
xmlParserData.currentField = currentField;
if (xmlParserData.currentField == null) {
From 8cd7cdeb3f5619365abb5da541589aa7a1ccf866 Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Mon, 26 Feb 2024 07:58:17 +0530
Subject: [PATCH 3/8] Refactor code
---
.../stdlib/data/xmldata/xml/XmlParser.java | 42 ++++++++++++-------
1 file changed, 26 insertions(+), 16 deletions(-)
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index f00a2527..765fd565 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -280,16 +280,7 @@ private void readText(XMLStreamReader xmlStreamReader,
case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType, bText,
xmlParserData);
case TypeTags.ARRAY_TAG -> {
- int elementTypeTag = ((ArrayType) fieldType).getElementType().getTag();
- if (elementTypeTag == TypeTags.RECORD_TYPE_TAG) {
- handleContentFieldInRecordType((RecordType) ((ArrayType) fieldType).getElementType(), bText,
- xmlParserData);
- } else if (elementTypeTag == TypeTags.ANYDATA_TAG || elementTypeTag == TypeTags.JSON_TAG) {
- BArray tempArr = (BArray) ((BMap) xmlParserData.nodesStack.peek()).get(bFieldName);
- tempArr.add(tempArr.getLength() - 1, convertStringToRestExpType(bText, fieldType));
- } else {
- xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
- }
+ addTextToCurrentNodeIfExpTypeIsArray((ArrayType) fieldType, bFieldName, bText, xmlParserData);
}
case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.peek();
@@ -299,6 +290,20 @@ private void readText(XMLStreamReader xmlStreamReader,
}
}
+ private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString bFieldName, BString bText,
+ XmlParserData xmlParserData) {
+ int elementTypeTag = fieldType.getElementType().getTag();
+ switch (elementTypeTag) {
+ case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType.getElementType(),
+ bText, xmlParserData);
+ case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
+ BArray tempArr = (BArray) ((BMap) xmlParserData.nodesStack.peek()).get(bFieldName);
+ tempArr.add(tempArr.getLength() - 1, convertStringToRestExpType(bText, fieldType));
+ }
+ default -> xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
+ }
+ }
+
private void handleTruncatedCharacters(XMLStreamReader xmlStreamReader, TextValue textValue)
throws XMLStreamException {
StringBuilder textBuilder = new StringBuilder();
@@ -461,18 +466,23 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse
ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(referredType)));
}
- switch (referredType.getTag()) {
- case TypeTags.RECORD_TYPE_TAG -> updateNextRecord(xmlStreamReader, xmlParserData, fieldName,
- fieldType, (RecordType) referredType);
- case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
- updateNextMap(xmlParserData, fieldName, referredType);
- }
+ updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, referredType);
}
case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
updateNextMap(xmlParserData, fieldName, fieldType);
}
}
+ private void updateNextArrayMember(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData,
+ String fieldName, Type fieldType, Type type) {
+ switch (type.getTag()) {
+ case TypeTags.RECORD_TYPE_TAG -> updateNextRecord(xmlStreamReader, xmlParserData, fieldName,
+ fieldType, (RecordType) type);
+ case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
+ updateNextMap(xmlParserData, fieldName, type);
+ }
+ }
+
private void updateNextMap(XmlParserData xmlParserData, String fieldName, Type fieldType) {
xmlParserData.parents.push(xmlParserData.siblings);
xmlParserData.siblings = new LinkedHashMap<>();
From 9c9b207c80dddcdc4f05fe5aa07c5303664cba6a Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Fri, 1 Mar 2024 21:37:14 +0530
Subject: [PATCH 4/8] Add tests and fix identified bugs
---
README.md | 22 +--
ballerina/Ballerina.toml | 2 +-
ballerina/Dependencies.toml | 2 +-
ballerina/tests/fromXml_test.bal | 165 ++++++++++++------
ballerina/xml_api.bal | 11 +-
build-config/resources/Ballerina.toml | 2 +-
.../xmldata/compiler/CompilerPluginTest.java | 42 ++++-
.../sample_package_7/Ballerina.toml | 4 +
.../sample_package_7/main.bal | 18 ++
.../sample_package_8/main.bal | 47 ++++-
.../sample_package_9/Ballerina.toml | 4 +
.../sample_package_9/main.bal | 37 ++++
.../compiler/XmldataDiagnosticCodes.java | 10 +-
.../compiler/XmldataRecordFieldValidator.java | 120 +++++++++----
gradle.properties | 2 +-
.../stdlib/data/xmldata/utils/DataUtils.java | 2 +-
.../xmldata/utils/DiagnosticErrorCode.java | 32 ++--
.../stdlib/data/xmldata/xml/XmlParser.java | 2 +-
.../stdlib/data/xmldata/xml/XmlTraversal.java | 2 +-
19 files changed, 382 insertions(+), 144 deletions(-)
create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml
create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal
create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml
create mode 100644 compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal
diff --git a/README.md b/README.md
index a1ebca89..41dfa7d9 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,7 @@ Take for instance the following XML snippet:
```xml
- 0
+ 601970
string
string
@@ -99,7 +99,7 @@ Consider the XML snippet:
```xml
- 0
+ 601970
string
string
@@ -139,7 +139,7 @@ Consider the following XML snippet:
```xml
- 0
+ 601970
string
string
@@ -167,7 +167,7 @@ Examine the XML snippet below:
```xml
- 0
+ 601970
string
string
@@ -216,7 +216,7 @@ Consider the XML snippet below:
```xml
- 0
+ 601970
string
string
true
@@ -242,7 +242,7 @@ For instance, examine this XML:
```xml
- 0
+ 601970
string
10.5
@@ -273,7 +273,7 @@ Examine the XML snippet below with default namespaces:
```xml
- 0
+ 601970
string
string
@@ -308,7 +308,7 @@ Here is the same XML snippet with a namespace prefix:
```xml
- 0
+ 601970
string
string
@@ -342,7 +342,7 @@ Here is the same XML snippet with a namespace prefix:
```xml
- 0
+ 601970
string
string
@@ -386,7 +386,7 @@ Take the following XML snippet as an example:
```xml
- 0
+ 601970
string
string
string
@@ -412,7 +412,7 @@ Take this XML snippet as an example:
```xml
- 0
+ 601970
string
string
10.5
diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index 1dc9f2b0..713a8ca3 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -6,7 +6,7 @@ authors = ["Ballerina"]
keywords = ["xml"]
repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata"
license = ["Apache-2.0"]
-distribution = "2201.8.4"
+distribution = "2201.8.5"
export = ["data.xmldata"]
[[platform.java17.dependency]]
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index 5e8838a3..866c4d7c 100644
--- a/ballerina/Dependencies.toml
+++ b/ballerina/Dependencies.toml
@@ -5,7 +5,7 @@
[ballerina]
dependencies-toml-version = "2"
-distribution-version = "2201.8.4"
+distribution-version = "2201.8.5"
[[package]]
org = "ballerina"
diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal
index 58e75af2..6c1d2275 100644
--- a/ballerina/tests/fromXml_test.bal
+++ b/ballerina/tests/fromXml_test.bal
@@ -154,9 +154,9 @@ function testXmlStringToRecord3() returns error? {
string xmlStr3 = "123";
Data5 rec3 = check fromXmlStringWithType(xmlStr3);
test:assertEquals(rec3.A.length(), 3);
- test:assertEquals(rec3.A[0].B.get("#content"), "1");
- test:assertEquals(rec3.A[1].B.get("#content"), "2");
- test:assertEquals(rec3.A[2].B.get("#content"), "3");
+ test:assertEquals(rec3.A[0].B.get("#content"), 1);
+ test:assertEquals(rec3.A[1].B.get("#content"), 2);
+ test:assertEquals(rec3.A[2].B.get("#content"), 3);
}
@test:Config {
@@ -176,9 +176,9 @@ function testXmlToRecord3() returns error? {
xml xmlVal3 = xml `123`;
Data5 rec3 = check fromXmlWithType(xmlVal3);
test:assertEquals(rec3.A.length(), 3);
- test:assertEquals(rec3.A[0].B.get("#content"), "1");
- test:assertEquals(rec3.A[1].B.get("#content"), "2");
- test:assertEquals(rec3.A[2].B.get("#content"), "3");
+ test:assertEquals(rec3.A[0].B.get("#content"), 1);
+ test:assertEquals(rec3.A[1].B.get("#content"), 2);
+ test:assertEquals(rec3.A[2].B.get("#content"), 3);
}
type Data6 record {|
@@ -326,7 +326,7 @@ function testXmlStringToRecord6() returns error? {
2.0
`;
Rec1 rec1 = check fromXmlStringWithType(xmlStr1);
- test:assertEquals(rec1.A.get("#content"), "1");
+ test:assertEquals(rec1.A.get("#content"), 1);
test:assertEquals(rec1.B.length(), 2);
test:assertEquals(rec1.B[0].get("#content"), 1.0);
test:assertEquals(rec1.B[1].get("#content"), 2.0);
@@ -342,7 +342,7 @@ function testXmlToRecord6() returns error? {
2.0
`;
Rec1 rec1 = check fromXmlWithType(xmlVal1);
- test:assertEquals(rec1.A.get("#content"), "1");
+ test:assertEquals(rec1.A.get("#content"), 1);
test:assertEquals(rec1.B.length(), 2);
test:assertEquals(rec1.B[0].get("#content"), 1.0);
test:assertEquals(rec1.B[1].get("#content"), 2.0);
@@ -419,7 +419,7 @@ function testXmlStringToRecord21() returns error? {
string xmlStr1 = "132";
RecRest1 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.A.C, 1);
- test:assertEquals(rec1.A.get("D"), "3");
+ test:assertEquals(rec1.A.get("D"), 3);
test:assertEquals(rec1.B, 2);
}
@@ -430,7 +430,7 @@ function testXmlToRecord21() returns error? {
xml xmlVal1 = xml `132`;
RecRest1 rec1 = check fromXmlWithType(xmlVal1);
test:assertEquals(rec1.A.C, 1);
- test:assertEquals(rec1.A.get("D"), "3");
+ test:assertEquals(rec1.A.get("D"), 3);
test:assertEquals(rec1.B, 2);
}
@@ -452,7 +452,7 @@ function testXmlStringToRecord22() returns error? {
RecRest2 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.A.C, 1);
test:assertEquals(rec1.A.D.E, 3);
- test:assertEquals(rec1.A.D.get("F"), "4");
+ test:assertEquals(rec1.A.D.get("F"), 4);
test:assertEquals(rec1.B, 2);
}
@@ -464,7 +464,7 @@ function testXmlToRecord22() returns error? {
RecRest2 rec1 = check fromXmlWithType(xmlVal1);
test:assertEquals(rec1.A.C, 1);
test:assertEquals(rec1.A.D.E, 3);
- test:assertEquals(rec1.A.D.get("F"), "4");
+ test:assertEquals(rec1.A.D.get("F"), 4);
test:assertEquals(rec1.B, 2);
}
@@ -487,9 +487,9 @@ function testXmlStringToRecord23() returns error? {
test:assertEquals(rec1.A.C, 1);
test:assertEquals(rec1.A.D.length(), 2);
test:assertEquals(rec1.A.D[0].E, 3);
- test:assertEquals(rec1.A.D[0].get("F"), "4");
+ test:assertEquals(rec1.A.D[0].get("F"), 4);
test:assertEquals(rec1.A.D[1].E, 5);
- test:assertEquals(rec1.A.D[1].get("F"), "6");
+ test:assertEquals(rec1.A.D[1].get("F"), 6);
test:assertEquals(rec1.B, 2);
}
@@ -502,9 +502,9 @@ function testXmlToRecord23() returns error? {
test:assertEquals(rec1.A.C, 1);
test:assertEquals(rec1.A.D.length(), 2);
test:assertEquals(rec1.A.D[0].E, 3);
- test:assertEquals(rec1.A.D[0].get("F"), "4");
+ test:assertEquals(rec1.A.D[0].get("F"), 4);
test:assertEquals(rec1.A.D[1].E, 5);
- test:assertEquals(rec1.A.D[1].get("F"), "6");
+ test:assertEquals(rec1.A.D[1].get("F"), 6);
test:assertEquals(rec1.B, 2);
}
@@ -663,9 +663,9 @@ function testXmlStringToRecord28() returns error? {
`;
record {} rec = check fromXmlStringWithType(xmlStr);
- test:assertEquals(rec.get("D"), "4");
- test:assertEquals(rec.get("A"), [{B: "1"}, {B: "2"}, {B: "3"}]);
- test:assertEquals(rec.get("C"), {B: "4"});
+ test:assertEquals(rec.get("D"), 4);
+ test:assertEquals(rec.get("A"), [{B: 1}, {B: 2}, {B: 3}]);
+ test:assertEquals(rec.get("C"), {B: 4});
}
@test:Config {
@@ -683,9 +683,9 @@ function testXmlToRecord28() returns error? {
`;
record {} rec = check fromXmlWithType(xmlVal);
- test:assertEquals(rec.get("D"), "4");
- test:assertEquals(rec.get("A"), [{B: "1"}, {B: "2"}, {B: "3"}]);
- test:assertEquals(rec.get("C"), {B: "4"});
+ test:assertEquals(rec.get("D"), 4);
+ test:assertEquals(rec.get("A"), [{B: 1}, {B: 2}, {B: 3}]);
+ test:assertEquals(rec.get("C"), {B: 4});
}
// test namespace and attributes annotations
@@ -1002,7 +1002,7 @@ function testXmlStringToRecord38() returns error? {
test:assertEquals(rec.A, "1");
RecAtt4 rec2 = check fromXmlStringWithType(xmlStr);
- test:assertEquals(rec2.A.get("#content"), "1");
+ test:assertEquals(rec2.A.get("#content"), 1);
RecAtt5 rec3 = check fromXmlStringWithType(xmlStr);
test:assertEquals(rec3.A, "name");
@@ -1017,7 +1017,7 @@ function testXmlToRecord38() returns error? {
test:assertEquals(rec.A, "1");
RecAtt4 rec2 = check fromXmlWithType(xmlVal);
- test:assertEquals(rec2.A.get("#content"), "1");
+ test:assertEquals(rec2.A.get("#content"), 1);
RecAtt5 rec3 = check fromXmlWithType(xmlVal);
test:assertEquals(rec3.A, "name");
@@ -1492,7 +1492,7 @@ function testXmlWithAttributesAgainstOpenRecord2() returns error? {
},
"price": {
"currency": "USD",
- "#content": "19.99"
+ "#content": 19.99
}
},
{
@@ -1504,7 +1504,7 @@ function testXmlWithAttributesAgainstOpenRecord2() returns error? {
},
"price": {
"currency": "EUR",
- "#content": "29.95"
+ "#content": 29.95
}
}
]);
@@ -1541,7 +1541,7 @@ function testXmlWithAttributesAgainstOpenRecord2() returns error? {
},
"price": {
"currency": "USD",
- "#content": "19.99"
+ "#content": 19.99
}
},
{
@@ -1553,7 +1553,7 @@ function testXmlWithAttributesAgainstOpenRecord2() returns error? {
},
"price": {
"currency": "EUR",
- "#content": "29.95"
+ "#content": 29.95
}
}
]);
@@ -1569,9 +1569,9 @@ function testXmlWithAttributesAgainstOpenRecord3() returns error? {
record {} rec5 = check fromXmlStringWithType(xmlStr3);
test:assertEquals(rec5.length(), 1);
test:assertEquals(rec5.get("A"), [
- {"B": {"value": "name", "#content": "1"}},
- {"B": {"value": "name", "#content": "2"}},
- {"B": {"value": "name", "#content": "3"}}
+ {"B": {"value": "name", "#content": 1}},
+ {"B": {"value": "name", "#content": 2}},
+ {"B": {"value": "name", "#content": 3}}
]);
xml xmlVal3 = xml `
@@ -1582,9 +1582,9 @@ function testXmlWithAttributesAgainstOpenRecord3() returns error? {
record {} rec6 = check fromXmlWithType(xmlVal3);
test:assertEquals(rec6.length(), 1);
test:assertEquals(rec6.get("A"), [
- {"B": {"value": "name", "#content": "1"}},
- {"B": {"value": "name", "#content": "2"}},
- {"B": {"value": "name", "#content": "3"}}
+ {"B": {"value": "name", "#content": 1}},
+ {"B": {"value": "name", "#content": 2}},
+ {"B": {"value": "name", "#content": 3}}
]);
}
@@ -1634,7 +1634,7 @@ function testAnydataAsFieldTypeWiThFromXmlStringWithType() returns error? {
test:assertEquals(rec.length(), 1);
test:assertEquals(rec.Employee, {
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
});
string xmlStr2 = string `
@@ -1655,11 +1655,11 @@ function testAnydataAsFieldTypeWiThFromXmlStringWithType() returns error? {
[
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]
);
@@ -1688,7 +1688,7 @@ function testAnydataAsFieldTypeWiThFromXmlWithType() returns error? {
test:assertEquals(rec.length(), 1);
test:assertEquals(rec.Employee, {
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
});
xml xmlVal2 = xml `
@@ -1709,11 +1709,11 @@ function testAnydataAsFieldTypeWiThFromXmlWithType() returns error? {
[
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]
);
@@ -1742,7 +1742,7 @@ function testJsonAsFieldTypeWiThFromXmlStringWithType() returns error? {
test:assertEquals(rec.length(), 1);
test:assertEquals(rec.Employee, {
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
});
string xmlStr2 = string `
@@ -1763,11 +1763,11 @@ function testJsonAsFieldTypeWiThFromXmlStringWithType() returns error? {
[
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]
);
@@ -1796,7 +1796,7 @@ function testJsonAsFieldTypeWiThFromXmlWithType() returns error? {
test:assertEquals(rec.length(), 1);
test:assertEquals(rec.Employee, {
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
});
xml xmlVal2 = xml `
@@ -1817,11 +1817,11 @@ function testJsonAsFieldTypeWiThFromXmlWithType() returns error? {
[
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]
);
@@ -1855,11 +1855,11 @@ function testAnydataArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
test:assertEquals(rec.Employee, [
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]);
@@ -1893,11 +1893,11 @@ function testJsonArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
test:assertEquals(rec.Employee, [
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]);
@@ -1931,11 +1931,11 @@ function testAnydataArrayAsFieldTypeWiThFromXmlWithType() returns error? {
test:assertEquals(rec.Employee, [
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]);
@@ -1969,11 +1969,11 @@ function testJsonArrayAsFieldTypeWiThFromXmlWithType() returns error? {
test:assertEquals(rec.Employee, [
{
"Name": "John Doe",
- "Age": "30"
+ "Age": 30
},
{
"Name": "Kanth Kevin",
- "Age": "26"
+ "Age": 26
}
]);
@@ -1988,6 +1988,63 @@ function testJsonArrayAsFieldTypeWiThFromXmlWithType() returns error? {
test:assertEquals(rec2.name, ["WSO2", "Apple"]);
}
+@test:Config
+function testMapArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+
+ record {|
+ map[] Employee;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+}
+
+@test:Config
+function testMapArrayAsFieldTypeWithFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ map[] Employee;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 1);
+ test:assertEquals(rec.Employee, [
+ {
+ "Name": "John Doe",
+ "Age": "30"
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": "26"
+ }
+ ]);
+}
+
// Negative cases
type DataN1 record {|
int A;
diff --git a/ballerina/xml_api.bal b/ballerina/xml_api.bal
index f1c41c06..ed635a18 100644
--- a/ballerina/xml_api.bal
+++ b/ballerina/xml_api.bal
@@ -128,25 +128,18 @@ isolated function getModifiedRecord(map mapValue, typedesc<(map allNamespaces = {};
if !isSingleNode(jsonValue) {
diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml
index aef7e4b8..ea975f8e 100644
--- a/build-config/resources/Ballerina.toml
+++ b/build-config/resources/Ballerina.toml
@@ -6,7 +6,7 @@ authors = ["Ballerina"]
keywords = ["xml"]
repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata"
license = ["Apache-2.0"]
-distribution = "2201.8.4"
+distribution = "2201.8.5"
export = ["data.xmldata"]
[[platform.java17.dependency]]
diff --git a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/xmldata/compiler/CompilerPluginTest.java b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/xmldata/compiler/CompilerPluginTest.java
index 2715587c..1e098dc8 100644
--- a/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/xmldata/compiler/CompilerPluginTest.java
+++ b/compiler-plugin-test/src/test/java/io/ballerina/stdlib/data/xmldata/compiler/CompilerPluginTest.java
@@ -104,7 +104,7 @@ public void testUnsupportedTypeNegative1() {
@Test
public void testUnsupportedTypeNegative2() {
DiagnosticResult diagnosticResult =
- CompilerPluginTestUtils.loadPackage("sample_package_8").getCompilation().diagnosticResult();
+ CompilerPluginTestUtils.loadPackage("sample_package_7").getCompilation().diagnosticResult();
List errorDiagnosticsList = diagnosticResult.diagnostics().stream()
.filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR))
.collect(Collectors.toList());
@@ -138,4 +138,44 @@ public void testChildRecordWithNameAnnotNegative() {
Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(),
"invalid annotation attachment: child record does not allow name annotation");
}
+
+ @Test
+ public void testDuplicateFieldInInlineRecordsNegative() {
+ DiagnosticResult diagnosticResult =
+ CompilerPluginTestUtils.loadPackage("sample_package_8").getCompilation().diagnosticResult();
+ List errorDiagnosticsList = diagnosticResult.diagnostics().stream()
+ .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR))
+ .collect(Collectors.toList());
+ Assert.assertEquals(errorDiagnosticsList.size(), 4);
+ Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ }
+
+ @Test
+ public void testUnionTypeNegative() {
+ DiagnosticResult diagnosticResult =
+ CompilerPluginTestUtils.loadPackage("sample_package_9").getCompilation().diagnosticResult();
+ List errorDiagnosticsList = diagnosticResult.diagnostics().stream()
+ .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR))
+ .collect(Collectors.toList());
+ Assert.assertEquals(errorDiagnosticsList.size(), 6);
+ Assert.assertEquals(errorDiagnosticsList.get(0).diagnosticInfo().messageFormat(),
+ "invalid type: expected a record type");
+ Assert.assertEquals(errorDiagnosticsList.get(1).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(2).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(3).diagnosticInfo().messageFormat(),
+ "invalid type: expected a record type");
+ Assert.assertEquals(errorDiagnosticsList.get(4).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ Assert.assertEquals(errorDiagnosticsList.get(5).diagnosticInfo().messageFormat(),
+ "invalid field: duplicate field found");
+ }
}
diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml
new file mode 100644
index 00000000..c7355a4f
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/Ballerina.toml
@@ -0,0 +1,4 @@
+[package]
+org = "xmldata_test"
+name = "sample_7"
+version = "0.1.0"
diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal
new file mode 100644
index 00000000..6ae9fe3f
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_7/main.bal
@@ -0,0 +1,18 @@
+import ballerina/data.xmldata;
+
+string xmlStr = string `12`;
+record {|
+ string? A;
+ () B;
+ ()[] C;
+ record {||}?[] D;
+|} _ = check xmldata:fromXmlStringWithType(xmlStr);
+
+function testFunction() returns error? {
+ record {|
+ string? A;
+ () B;
+ ()[] C;
+ record {||}?[] D;
+ |} _ = check xmldata:fromXmlStringWithType(xmlStr);
+}
diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal
index 6ae9fe3f..84d1401e 100644
--- a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal
+++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_8/main.bal
@@ -1,18 +1,49 @@
import ballerina/data.xmldata;
string xmlStr = string `12`;
+
record {|
- string? A;
- () B;
- ()[] C;
- record {||}?[] D;
+ @xmldata:Name {
+ value: "A"
+ }
+ string a;
+ @xmldata:Name {
+ value: "A"
+ }
+ string b;
|} _ = check xmldata:fromXmlStringWithType(xmlStr);
+record {|
+ @xmldata:Name {
+ value: "A"
+ }
+ string a;
+ @xmldata:Name {
+ value: "A"
+ }
+ string b;
+|} _ = {a: "1", b: "2"};
+
function testFunction() returns error? {
record {|
- string? A;
- () B;
- ()[] C;
- record {||}?[] D;
+ @xmldata:Name {
+ value: "A"
+ }
+ string a;
+ @xmldata:Name {
+ value: "A"
+ }
+ string b;
|} _ = check xmldata:fromXmlStringWithType(xmlStr);
+
+ record {|
+ @xmldata:Name {
+ value: "A"
+ }
+ string a;
+ @xmldata:Name {
+ value: "A"
+ }
+ string b;
+ |} _ = {a: "1", b: "2"};
}
diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml
new file mode 100644
index 00000000..5fc2223b
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/Ballerina.toml
@@ -0,0 +1,4 @@
+[package]
+org = "xmldata_test"
+name = "sample_9"
+version = "0.1.0"
diff --git a/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal
new file mode 100644
index 00000000..2d55f07e
--- /dev/null
+++ b/compiler-plugin-test/src/test/resources/ballerina_sources/sample_package_9/main.bal
@@ -0,0 +1,37 @@
+import ballerina/data.xmldata;
+
+string xmlStr = string `12`;
+
+record {|
+ @xmldata:Name {
+ value: "B"
+ }
+ string A;
+ string B;
+|}|map|error rec1 = xmldata:fromXmlStringWithType(xmlStr);
+
+record {|
+ @xmldata:Name {
+ value: "B"
+ }
+ string A;
+ string B;
+|}|map|error rec2 = {A: "1", B: "2"};
+
+public function main() {
+ record {|
+ @xmldata:Name {
+ value: "B"
+ }
+ string A;
+ string B;
+ |}|map|error rec3 = xmldata:fromXmlStringWithType(xmlStr);
+
+ record {|
+ @xmldata:Name {
+ value: "B"
+ }
+ string A;
+ string B;
+ |}|map|error rec4 = {A: "1", B: "2"};
+}
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
index 72b305cc..af702333 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataDiagnosticCodes.java
@@ -30,12 +30,12 @@
*/
public enum XmldataDiagnosticCodes {
- DUPLICATE_FIELD("BDE201", "invalid field: duplicate field found", ERROR),
- UNSUPPORTED_UNION_TYPE("BDE202",
+ DUPLICATE_FIELD("XML_ERROR_201", "invalid field: duplicate field found", ERROR),
+ UNSUPPORTED_UNION_TYPE("XML_ERROR_202",
"unsupported union type: union type does not support multiple non-primitive record types", ERROR),
- UNSUPPORTED_TYPE("BDE203", "unsupported type: the record field does not support the expected type", ERROR),
- EXPECTED_RECORD_TYPE("BDE204", "invalid type: expected a record type", ERROR),
- NAME_ANNOTATION_NOT_ALLOWED("BDE204",
+ UNSUPPORTED_TYPE("XML_ERROR_203", "unsupported type: the record field does not support the expected type", ERROR),
+ EXPECTED_RECORD_TYPE("XML_ERROR_204", "invalid type: expected a record type", ERROR),
+ NAME_ANNOTATION_NOT_ALLOWED("XML_ERROR_205",
"invalid annotation attachment: child record does not allow name annotation", WARNING);
private final String code;
diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
index 9473c319..df668b61 100644
--- a/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
+++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/data/xmldata/compiler/XmldataRecordFieldValidator.java
@@ -113,34 +113,83 @@ private void processFunctionDefinitionNode(FunctionDefinitionNode functionDefini
}
TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor();
- if (!isFromXmlFunctionFromXmldata(initializer.get())) {
- if (typeSymbol.typeKind() == TypeDescKind.RECORD) {
- validateRecordFields((RecordTypeSymbol) typeSymbol, ctx);
- }
+ if (!isNotFromXmlFunctionFromXmldata(initializer.get())) {
+ validateAnnotationUsageInAllInlineExpectedTypes(typeSymbol, ctx);
continue;
}
+ validateExpectedType(typeSymbol, symbol.get().getLocation(), ctx);
+ }
+ }
- if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) {
- typeSymbol = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor();
- if (typeSymbol.typeKind() != TypeDescKind.RECORD) {
- reportDiagnosticInfo(ctx, symbol.get().getLocation(), XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
- continue;
+ private void validateAnnotationUsageInAllInlineExpectedTypes(TypeSymbol typeSymbol, SyntaxNodeAnalysisContext ctx) {
+ switch (typeSymbol.typeKind()) {
+ case RECORD -> validateRecordFieldNames((RecordTypeSymbol) typeSymbol, ctx);
+ case UNION -> {
+ for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) {
+ validateAnnotationUsageInAllInlineExpectedTypes(memberTSymbol, ctx);
}
- processRecordFieldsType((RecordTypeSymbol) typeSymbol, ctx);
- } else if (typeSymbol.typeKind() != TypeDescKind.RECORD) {
- reportDiagnosticInfo(ctx, symbol.get().getLocation(), XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
- continue;
}
- RecordTypeSymbol recordSymbol = (RecordTypeSymbol) typeSymbol;
- validateRecordFields(recordSymbol, ctx);
- processRecordFieldsType(recordSymbol, ctx);
}
}
+ private void validateExpectedType(TypeSymbol typeSymbol, Optional location,
+ SyntaxNodeAnalysisContext ctx) {
+ if (isNotValidExpectedType(typeSymbol)) {
+ reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
+ }
+
+ switch (typeSymbol.typeKind()) {
+ case RECORD -> {
+ RecordTypeSymbol recordSymbol = (RecordTypeSymbol) typeSymbol;
+ validateRecordFieldNames(recordSymbol, ctx);
+ processRecordFieldsType(recordSymbol, ctx);
+ }
+ case TYPE_REFERENCE -> validateExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(),
+ location, ctx);
+ case UNION -> {
+ int nonErrorTypeCount = 0;
+ for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) {
+ if (memberTSymbol.typeKind() == TypeDescKind.ERROR) {
+ continue;
+ }
+ nonErrorTypeCount++;
+ validateExpectedType(memberTSymbol, location, ctx);
+ }
+ if (nonErrorTypeCount > 1) {
+ reportDiagnosticInfo(ctx, location, XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
+ }
+ }
+ }
+ }
+
+ private boolean isNotValidExpectedType(TypeSymbol typeSymbol) {
+ switch (typeSymbol.typeKind()) {
+ case RECORD -> {
+ return false;
+ }
+ case TYPE_REFERENCE -> {
+ return isNotValidExpectedType(((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor());
+ }
+ case UNION -> {
+ for (TypeSymbol memberTSymbol : ((UnionTypeSymbol) typeSymbol).memberTypeDescriptors()) {
+ if (memberTSymbol.typeKind() == TypeDescKind.ERROR) {
+ continue;
+ }
+
+ if (isNotValidExpectedType(memberTSymbol)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode moduleVariableDeclarationNode,
SyntaxNodeAnalysisContext ctx) {
Optional initializer = moduleVariableDeclarationNode.initializer();
- if (initializer.isEmpty() || !isFromXmlFunctionFromXmldata(initializer.get())) {
+ if (initializer.isEmpty()) {
return;
}
@@ -149,20 +198,12 @@ private void processModuleVariableDeclarationNode(ModuleVariableDeclarationNode
return;
}
TypeSymbol typeSymbol = ((VariableSymbol) symbol.get()).typeDescriptor();
- if (typeSymbol.typeKind() == TypeDescKind.TYPE_REFERENCE) {
- typeSymbol = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor();
- if (typeSymbol.typeKind() != TypeDescKind.RECORD) {
- reportDiagnosticInfo(ctx, symbol.get().getLocation(),
- XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
- return;
- }
- processRecordFieldsType((RecordTypeSymbol) typeSymbol, ctx);
- } else if (typeSymbol.typeKind() != TypeDescKind.RECORD) {
- reportDiagnosticInfo(ctx, symbol.get().getLocation(), XmldataDiagnosticCodes.EXPECTED_RECORD_TYPE);
+
+ if (!isNotFromXmlFunctionFromXmldata(initializer.get())) {
+ validateAnnotationUsageInAllInlineExpectedTypes(typeSymbol, ctx);
return;
}
- validateRecordFields((RecordTypeSymbol) typeSymbol, ctx);
- processRecordFieldsType((RecordTypeSymbol) typeSymbol, ctx);
+ validateExpectedType(typeSymbol, symbol.get().getLocation(), ctx);
}
private void processTypeDefinitionNode(TypeDefinitionNode typeDefinitionNode, SyntaxNodeAnalysisContext ctx) {
@@ -179,10 +220,10 @@ private void validateRecordTypeDefinition(TypeDefinitionNode typeDefinitionNode,
return;
}
TypeDefinitionSymbol typeDefinitionSymbol = (TypeDefinitionSymbol) symbol.get();
- validateRecordFields((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx);
+ validateRecordFieldNames((RecordTypeSymbol) typeDefinitionSymbol.typeDescriptor(), ctx);
}
- private void validateRecordFields(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) {
+ private void validateRecordFieldNames(RecordTypeSymbol recordTypeSymbol, SyntaxNodeAnalysisContext ctx) {
List fieldMembers = new ArrayList<>();
for (Map.Entry entry : recordTypeSymbol.fieldDescriptors().entrySet()) {
RecordFieldSymbol fieldSymbol = entry.getValue();
@@ -296,7 +337,11 @@ private QualifiedName getQNameFromAnnotation(String fieldName,
String name = fieldName;
String prefix = "";
for (AnnotationAttachmentSymbol annotAttSymbol : annotationAttachments) {
- Optional nameAnnot = annotAttSymbol.typeDescriptor().getName();
+ AnnotationSymbol annotation = annotAttSymbol.typeDescriptor();
+ if (!getAnnotModuleName(annotation).contains(Constants.XMLDATA)) {
+ continue;
+ }
+ Optional nameAnnot = annotation.getName();
if (nameAnnot.isEmpty()) {
continue;
}
@@ -314,7 +359,16 @@ private QualifiedName getQNameFromAnnotation(String fieldName,
return new QualifiedName(uri, name, prefix);
}
- private boolean isFromXmlFunctionFromXmldata(ExpressionNode expressionNode) {
+ private String getAnnotModuleName(AnnotationSymbol annotation) {
+ Optional moduleSymbol = annotation.getModule();
+ if (moduleSymbol.isEmpty()) {
+ return "";
+ }
+ Optional moduleName = moduleSymbol.get().getName();
+ return moduleName.orElse("");
+ }
+
+ private boolean isNotFromXmlFunctionFromXmldata(ExpressionNode expressionNode) {
if (expressionNode.kind() == SyntaxKind.CHECK_EXPRESSION) {
expressionNode = ((CheckExpressionNode) expressionNode).expression();
}
diff --git a/gradle.properties b/gradle.properties
index b37a623d..8c79c393 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
org.gradle.caching=true
group=io.ballerina.stdlib
version=0.1.0-SNAPSHOT
-ballerinaLangVersion=2201.8.4
+ballerinaLangVersion=2201.8.5
checkstyleToolVersion=10.12.0
puppycrawlCheckstyleVersion=10.12.0
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
index 6674d87a..10936e28 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
@@ -201,7 +201,7 @@ public static Object convertStringToExpType(BString value, Type expType) {
Object result;
switch (expType.getTag()) {
case TypeTags.ANYDATA_TAG, TypeTags.ANY_TAG, TypeTags.JSON_TAG ->
- result = FromString.fromStringWithType(value, PredefinedTypes.TYPE_STRING);
+ result = FromString.fromStringWithType(value, PredefinedTypes.TYPE_JSON);
case TypeTags.ARRAY_TAG -> result = convertStringToExpType(value, ((ArrayType) expType).getElementType());
default -> result = FromString.fromStringWithType(value, expType);
}
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DiagnosticErrorCode.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DiagnosticErrorCode.java
index ff1ee55b..39fa57c7 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DiagnosticErrorCode.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DiagnosticErrorCode.java
@@ -25,22 +25,22 @@
*/
public enum DiagnosticErrorCode {
- INVALID_TYPE("BDE_0001", "invalid.type"),
- XML_ROOT_MISSING("BDE_0002", "xml.root.missing"),
- INVALID_REST_TYPE("BDE_0003", "invalid.rest.type"),
- ARRAY_SIZE_MISMATCH("BDE_0004", "array.size.mismatch"),
- REQUIRED_FIELD_NOT_PRESENT("BDE_0005", "required.field.not.present"),
- REQUIRED_ATTRIBUTE_NOT_PRESENT("BDE_0006", "required.attribute.not.present"),
- DUPLICATE_FIELD("BDE_0007", "duplicate.field"),
- FOUND_ARRAY_FOR_NON_ARRAY_TYPE("BDE_0008", "found.array.for.non.array.type"),
- EXPECTED_ANYDATA_OR_JSON("BDE_0009", "expected.anydata.or.json"),
- NAMESPACE_MISMATCH("BDE_0010", "namespace.mismatch"),
- TYPE_NAME_MISMATCH_WITH_XML_ELEMENT("BDE_0011", "type.name.mismatch.with.xml.element"),
- CAN_NOT_READ_STREAM("BDE_0012", "error.cannot.read.stream"),
- CANNOT_CONVERT_TO_EXPECTED_TYPE("BDE_0013", "cannot.convert.to.expected.type"),
- UNSUPPORTED_TYPE("BDE_0014", "unsupported.type"),
- STREAM_BROKEN("BDE_0015", "stream.broken"),
- XML_PARSE_ERROR("BDE_0016", "xml.parse.error");
+ INVALID_TYPE("XML_ERROR_001", "invalid.type"),
+ XML_ROOT_MISSING("XML_ERROR_002", "xml.root.missing"),
+ INVALID_REST_TYPE("XML_ERROR_003", "invalid.rest.type"),
+ ARRAY_SIZE_MISMATCH("XML_ERROR_004", "array.size.mismatch"),
+ REQUIRED_FIELD_NOT_PRESENT("XML_ERROR_005", "required.field.not.present"),
+ REQUIRED_ATTRIBUTE_NOT_PRESENT("XML_ERROR_006", "required.attribute.not.present"),
+ DUPLICATE_FIELD("XML_ERROR_007", "duplicate.field"),
+ FOUND_ARRAY_FOR_NON_ARRAY_TYPE("XML_ERROR_008", "found.array.for.non.array.type"),
+ EXPECTED_ANYDATA_OR_JSON("XML_ERROR_009", "expected.anydata.or.json"),
+ NAMESPACE_MISMATCH("XML_ERROR_010", "namespace.mismatch"),
+ TYPE_NAME_MISMATCH_WITH_XML_ELEMENT("XML_ERROR_011", "type.name.mismatch.with.xml.element"),
+ CAN_NOT_READ_STREAM("XML_ERROR_012", "error.cannot.read.stream"),
+ CANNOT_CONVERT_TO_EXPECTED_TYPE("XML_ERROR_013", "cannot.convert.to.expected.type"),
+ UNSUPPORTED_TYPE("XML_ERROR_014", "unsupported.type"),
+ STREAM_BROKEN("XML_ERROR_015", "stream.broken"),
+ XML_PARSE_ERROR("XML_ERROR_016", "xml.parse.error");
String diagnosticId;
String messageKey;
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index 765fd565..b9a26953 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -360,7 +360,7 @@ private Object convertStringToRestExpType(BString value, Type expType) {
return convertStringToExpType(value, expType);
}
case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
- return convertStringToExpType(value, PredefinedTypes.TYPE_STRING);
+ return convertStringToExpType(value, PredefinedTypes.TYPE_JSON);
}
case TypeTags.TYPE_REFERENCED_TYPE_TAG -> {
return convertStringToExpType(value, TypeUtils.getReferredType(expType));
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
index 203f3da1..3d8c7b57 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
@@ -193,7 +193,7 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) {
return;
} else if (elementTypeTag == TypeTags.MAP_TAG) {
updateNextMap(elementType, analyzerData);
- currentNode = updateNextValue(currentFieldType, fieldName, currentFieldType, mapValue,
+ currentNode = updateNextValue(elementType, fieldName, currentFieldType, mapValue,
analyzerData);
traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData);
DataUtils.validateRequiredFields((BMap) currentNode, analyzerData);
From 63150f2367c5a6a998b7e8ae052c3771a7280ca9 Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Thu, 7 Mar 2024 23:06:28 +0530
Subject: [PATCH 5/8] Handle anydata or json as expected type properly
---
ballerina/tests/fromXml_test.bal | 598 +++++++++++++++++-
.../stdlib/data/xmldata/utils/DataUtils.java | 11 +
.../data/xmldata/xml/QualifiedName.java | 4 +-
.../stdlib/data/xmldata/xml/XmlParser.java | 123 +++-
.../stdlib/data/xmldata/xml/XmlTraversal.java | 19 +-
5 files changed, 712 insertions(+), 43 deletions(-)
diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal
index 6c1d2275..72120f35 100644
--- a/ballerina/tests/fromXml_test.bal
+++ b/ballerina/tests/fromXml_test.bal
@@ -13,7 +13,6 @@
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
-
import ballerina/test;
type Data record {|
@@ -1620,6 +1619,156 @@ function testCommentMiddleInContent2() returns error? {
test:assertEquals(rec2.A, "John Doe");
}
+@test:Config
+function testRegexAsFieldTypeWithFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+ 1
+ 2
+ Code
+
+ Kanth
+
+ `;
+ record {|
+ string:RegExp[] A;
+ string B;
+ record {|
+ string name;
+ |} C;
+ |} rec1 = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec1.length(), 3);
+ test:assertEquals(rec1.A, [1, 2]);
+ test:assertEquals(rec1.B, "Code");
+ test:assertEquals(rec1.C.name, "Kanth");
+}
+
+@test:Config
+function testRegexAsFieldTypeWithFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+ 1
+ 2
+ Code
+
+ Kanth
+
+ `;
+ record {|
+ string:RegExp[] A;
+ string B;
+ record {|
+ string name;
+ |} C;
+ |} rec1 = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec1.length(), 3);
+ test:assertEquals(rec1.A, [1, 2]);
+ test:assertEquals(rec1.B, "Code");
+ test:assertEquals(rec1.C.name, "Kanth");
+}
+
+@test:Config
+function testAnydataAsRestFieldWithFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata...;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), [1, 2]);
+ test:assertEquals(rec.get("B"), ["Code_1", "Code_2"]);
+
+ string xmlStr2 = string `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata...;
+ |} rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 2);
+ test:assertEquals(rec2.get("A"), [{C: 1}, {C: 2}]);
+ test:assertEquals(rec2.get("B"), [{C: "Code_1"}, {C: "Code_2"}]);
+
+ string xmlStr3 = string `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata...;
+ |} rec3 = check fromXmlStringWithType(xmlStr3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.get("Employee"), [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]);
+}
+
+@test:Config
+function testAnydataAsRestFieldWithFromXmlWithType() returns error? {
+ xml xmlVal = xml `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata...;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), [1, 2]);
+ test:assertEquals(rec.get("B"), ["Code_1", "Code_2"]);
+
+ xml xmlVal2 = xml `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata...;
+ |} rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 2);
+ test:assertEquals(rec2.get("A"), [{C: 1}, {C: 2}]);
+ test:assertEquals(rec2.get("B"), [{C: "Code_1"}, {C: "Code_2"}]);
+
+ xml xmlVal3 = xml `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata...;
+ |} rec3 = check fromXmlWithType(xmlVal3);
+ test:assertEquals(rec3.length(), 1);
+ test:assertEquals(rec3.get("Employee"), [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]);
+}
+
@test:Config
function testAnydataAsFieldTypeWiThFromXmlStringWithType() returns error? {
string xmlStr = string `
@@ -1672,6 +1821,104 @@ function testAnydataAsFieldTypeWiThFromXmlStringWithType() returns error? {
} rec3 = check fromXmlStringWithType(xmlStr3);
test:assertEquals(rec3.length(), 1);
test:assertEquals(rec3.name, "WSO2");
+
+ string xmlStr4 = string `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec4 = check fromXmlStringWithType(xmlStr4);
+ test:assertEquals(rec4.length(), 1);
+ test:assertEquals(rec4.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ );
+
+ string xmlStr5 = string `
+ WSO2
+ Sanjeeva
+ Paul Fremantle
+ `;
+ record {
+ anydata name;
+ anydata founder;
+ } rec5 = check fromXmlStringWithType(xmlStr5);
+ test:assertEquals(rec5.length(), 2);
+ test:assertEquals(rec5.name, "WSO2");
+ test:assertEquals(rec5.founder, [
+ {
+ "age": 55,
+ "#content": "Sanjeeva"
+ },
+ {
+ "age": 58,
+ "#content": "Paul Fremantle"
+ }
+ ]);
+
+ string xmlStr6 = string `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec6 = check fromXmlStringWithType(xmlStr6);
+ test:assertEquals(rec6.length(), 1);
+ test:assertEquals(rec6, {
+ Employee: [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ });
+
+ string xmlStr7 = string `
+ 1
+ Code_1
+ `;
+ record {|
+ anydata A;
+ anydata B;
+ |} rec7 = check fromXmlStringWithType(xmlStr7);
+ test:assertEquals(rec7.length(), 2);
+ test:assertEquals(rec7.A, 1);
+ test:assertEquals(rec7.B, "Code_1");
+
+ string xmlStr8 = string `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata A;
+ anydata B;
+ |} rec8 = check fromXmlStringWithType(xmlStr8);
+ test:assertEquals(rec8.length(), 2);
+ test:assertEquals(rec8.A, [1, 2]);
+ test:assertEquals(rec8.B, ["Code_1", "Code_2"]);
}
@test:Config
@@ -1726,6 +1973,104 @@ function testAnydataAsFieldTypeWiThFromXmlWithType() returns error? {
} rec3 = check fromXmlWithType(xmlVal3);
test:assertEquals(rec3.length(), 1);
test:assertEquals(rec3.name, "WSO2");
+
+ xml xmlVal4 = xml `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec4 = check fromXmlWithType(xmlVal4);
+ test:assertEquals(rec4.length(), 1);
+ test:assertEquals(rec4.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ );
+
+ xml xmlVal5 = xml `
+ WSO2
+ Sanjeeva
+ Paul Fremantle
+ `;
+ record {
+ anydata name;
+ anydata founder;
+ } rec5 = check fromXmlWithType(xmlVal5);
+ test:assertEquals(rec5.length(), 2);
+ test:assertEquals(rec5.name, "WSO2");
+ test:assertEquals(rec5.founder, [
+ {
+ "age": 55,
+ "#content": "Sanjeeva"
+ },
+ {
+ "age": 58,
+ "#content": "Paul Fremantle"
+ }
+ ]);
+
+ xml xmlVal6 = xml `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ anydata Employee;
+ |} rec6 = check fromXmlWithType(xmlVal6);
+ test:assertEquals(rec6.length(), 1);
+ test:assertEquals(rec6, {
+ Employee: [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ });
+
+ xml xmlVal7 = xml `
+ 1
+ Code_1
+ `;
+ record {|
+ anydata A;
+ anydata B;
+ |} rec7 = check fromXmlWithType(xmlVal7);
+ test:assertEquals(rec7.length(), 2);
+ test:assertEquals(rec7.A, 1);
+ test:assertEquals(rec7.B, "Code_1");
+
+ xml xmlVal8 = xml `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata A;
+ anydata B;
+ |} rec8 = check fromXmlWithType(xmlVal8);
+ test:assertEquals(rec8.length(), 2);
+ test:assertEquals(rec8.A, [1, 2]);
+ test:assertEquals(rec8.B, ["Code_1", "Code_2"]);
}
@test:Config
@@ -1780,6 +2125,57 @@ function testJsonAsFieldTypeWiThFromXmlStringWithType() returns error? {
} rec3 = check fromXmlStringWithType(xmlStr3);
test:assertEquals(rec3.length(), 1);
test:assertEquals(rec3.name, "WSO2");
+
+ string xmlStr6 = string `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ json Employee;
+ |} rec6 = check fromXmlStringWithType(xmlStr6);
+ test:assertEquals(rec6.length(), 1);
+ test:assertEquals(rec6, {
+ Employee: [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ });
+
+ string xmlStr7 = string `
+ 1
+ Code_1
+ `;
+ record {|
+ json A;
+ json B;
+ |} rec7 = check fromXmlStringWithType(xmlStr7);
+ test:assertEquals(rec7.length(), 2);
+ test:assertEquals(rec7.A, 1);
+ test:assertEquals(rec7.B, "Code_1");
+
+ string xmlStr8 = string `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ json A;
+ json B;
+ |} rec8 = check fromXmlStringWithType(xmlStr8);
+ test:assertEquals(rec8.length(), 2);
+ test:assertEquals(rec8.A, [1, 2]);
+ test:assertEquals(rec8.B, ["Code_1", "Code_2"]);
}
@test:Config
@@ -1834,6 +2230,104 @@ function testJsonAsFieldTypeWiThFromXmlWithType() returns error? {
} rec3 = check fromXmlWithType(xmlVal3);
test:assertEquals(rec3.length(), 1);
test:assertEquals(rec3.name, "WSO2");
+
+ xml xmlVal4 = xml `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ json Employee;
+ |} rec4 = check fromXmlWithType(xmlVal4);
+ test:assertEquals(rec4.length(), 1);
+ test:assertEquals(rec4.Employee,
+ [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ );
+
+ xml xmlVal5 = xml `
+ WSO2
+ Sanjeeva
+ Paul Fremantle
+ `;
+ record {
+ json name;
+ json founder;
+ } rec5 = check fromXmlWithType(xmlVal5);
+ test:assertEquals(rec5.length(), 2);
+ test:assertEquals(rec5.name, "WSO2");
+ test:assertEquals(rec5.founder, [
+ {
+ "age": 55,
+ "#content": "Sanjeeva"
+ },
+ {
+ "age": 58,
+ "#content": "Paul Fremantle"
+ }
+ ]);
+
+ xml xmlVal6 = xml `
+
+ John Doe
+
+
+ Kanth Kevin
+
+ `;
+ record {|
+ json Employee;
+ |} rec6 = check fromXmlWithType(xmlVal6);
+ test:assertEquals(rec6.length(), 1);
+ test:assertEquals(rec6, {
+ Employee: [
+ {
+ "Name": "John Doe",
+ "age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "age": 26
+ }
+ ]
+ });
+
+ xml xmlVal7 = xml `
+ 1
+ Code_1
+ `;
+ record {|
+ json A;
+ json B;
+ |} rec7 = check fromXmlWithType(xmlVal7);
+ test:assertEquals(rec7.length(), 2);
+ test:assertEquals(rec7.A, 1);
+ test:assertEquals(rec7.B, "Code_1");
+
+ xml xmlVal8 = xml `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ json A;
+ json B;
+ |} rec8 = check fromXmlWithType(xmlVal8);
+ test:assertEquals(rec8.length(), 2);
+ test:assertEquals(rec8.A, [1, 2]);
+ test:assertEquals(rec8.B, ["Code_1", "Code_2"]);
}
@test:Config
@@ -1866,12 +2360,16 @@ function testAnydataArrayAsFieldTypeWiThFromXmlStringWithType() returns error? {
string xmlStr2 = string `
WSO2
Apple
+ Sri Lanka
+ India
`;
record {
anydata[] name;
+ anydata[] location;
} rec2 = check fromXmlStringWithType(xmlStr2);
- test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.length(), 2);
test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+ test:assertEquals(rec2.location, ["Sri Lanka", "India"]);
}
@test:Config
@@ -1942,12 +2440,16 @@ function testAnydataArrayAsFieldTypeWiThFromXmlWithType() returns error? {
xml xmlVal2 = xml `
WSO2
Apple
+ Sri Lanka
+ India
`;
record {
anydata[] name;
+ anydata[] location;
} rec2 = check fromXmlWithType(xmlVal2);
- test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.length(), 2);
test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+ test:assertEquals(rec2.location, ["Sri Lanka", "India"]);
}
@test:Config
@@ -1977,15 +2479,19 @@ function testJsonArrayAsFieldTypeWiThFromXmlWithType() returns error? {
}
]);
- xml xmlVal2 = xml `
+ xml xmlVal2 = xml `
WSO2
Apple
- `;
+ Sri Lanka
+ India
+ `;
record {
json[] name;
+ json[] location;
} rec2 = check fromXmlWithType(xmlVal2);
- test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.length(), 2);
test:assertEquals(rec2.name, ["WSO2", "Apple"]);
+ test:assertEquals(rec2.location, ["Sri Lanka", "India"]);
}
@test:Config
@@ -2045,6 +2551,86 @@ function testMapArrayAsFieldTypeWithFromXmlWithType() returns error? {
]);
}
+@test:Config
+function testAnydataArrayAsRestTypeWithFromJsonStringWithType() returns error? {
+ string xmlStr = string `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata[]...;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), [1, 2]);
+
+ string xmlStr2 = string `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata[]...;
+ |} rec2 = check fromXmlStringWithType(xmlStr2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.get("Employee"), [
+ {
+ "Name": "John Doe",
+ "Age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": 26
+ }
+ ]);
+}
+
+@test:Config
+function testAnydataArrayAsRestTypeWithFromJsonWithType() returns error? {
+ xml xmlVal = xml `
+ 1
+ 2
+ Code_1
+ Code_2
+ `;
+ record {|
+ anydata[]...;
+ |} rec = check fromXmlWithType(xmlVal);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), [1, 2]);
+
+ xml xmlVal2 = xml `
+
+ John Doe
+ 30
+
+
+ Kanth Kevin
+ 26
+
+ `;
+ record {|
+ anydata[]...;
+ |} rec2 = check fromXmlWithType(xmlVal2);
+ test:assertEquals(rec2.length(), 1);
+ test:assertEquals(rec2.get("Employee"), [
+ {
+ "Name": "John Doe",
+ "Age": 30
+ },
+ {
+ "Name": "Kanth Kevin",
+ "Age": 26
+ }
+ ]);
+}
+
// Negative cases
type DataN1 record {|
int A;
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
index 10936e28..747c9fd5 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/DataUtils.java
@@ -297,6 +297,17 @@ public static void removeExpectedTypeStacks(XmlAnalyzerData analyzerData) {
analyzerData.restTypes.pop();
}
+ public static boolean isAnydataOrJson(int typeTag) {
+ return typeTag == TypeTags.ANYDATA_TAG || typeTag == TypeTags.JSON_TAG;
+ }
+
+ public static boolean isAnydataOrJsonArray(Type type) {
+ if (type.getTag() != TypeTags.ARRAY_TAG) {
+ return false;
+ }
+ return isAnydataOrJson(((ArrayType) type).getElementType().getTag());
+ }
+
@SuppressWarnings("unchecked")
public static Object getModifiedRecord(BMap input, BTypedesc type) {
Type describingType = type.getDescribingType();
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/QualifiedName.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/QualifiedName.java
index c0b26e28..f3a2b35a 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/QualifiedName.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/QualifiedName.java
@@ -68,12 +68,10 @@ public boolean equals(Object objectToTest) {
return true;
}
- if (objectToTest == null || !(objectToTest instanceof QualifiedName)) {
+ if (!(objectToTest instanceof QualifiedName qName)) {
return false;
}
- QualifiedName qName = (QualifiedName) objectToTest;
-
if (qName.namespaceURI.equals(NS_ANNOT_NOT_DEFINED) || namespaceURI.equals(NS_ANNOT_NOT_DEFINED)) {
return localPart.equals(qName.localPart);
}
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index b9a26953..11f9a81f 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -262,7 +262,7 @@ private void readText(XMLStreamReader xmlStreamReader,
throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, fieldType, PredefinedTypes.TYPE_STRING);
}
- if (xmlParserData.currentNode.containsKey(bFieldName)) {
+ if (xmlParserData.currentNode.containsKey(bFieldName) && !DataUtils.isAnydataOrJson(fieldType.getTag())) {
if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) {
throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName);
}
@@ -279,20 +279,46 @@ private void readText(XMLStreamReader xmlStreamReader,
switch (fieldType.getTag()) {
case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType, bText,
xmlParserData);
- case TypeTags.ARRAY_TAG -> {
+ case TypeTags.ARRAY_TAG ->
addTextToCurrentNodeIfExpTypeIsArray((ArrayType) fieldType, bFieldName, bText, xmlParserData);
- }
- case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
- xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.peek();
- xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
- }
+ case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
+ convertTextAndUpdateCurrentNode(xmlParserData.currentNode,
+ (BMap) xmlParserData.nodesStack.pop(),
+ bFieldName, bText, fieldType, xmlParserData);
default -> xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType));
}
}
+ @SuppressWarnings("unchecked")
+ private void convertTextAndUpdateCurrentNode(BMap currentNode,
+ BMap parent,
+ BString currentFieldName,
+ BString bText, Type restType,
+ XmlParserData xmlParserData) {
+ Object currentElement = currentNode.get(currentFieldName);
+ Object result = convertStringToRestExpType(bText, restType);
+
+ if (currentElement == null && !currentNode.isEmpty()) { // Add text to the #content field
+ currentNode.put(StringUtils.fromString(Constants.CONTENT), result);
+ } else if (parent.get(currentFieldName) instanceof BArray bArray) {
+ bArray.add(bArray.getLength() - 1, result);
+ } else if (parent instanceof BArray) {
+ ((BArray) parent).append(result);
+ } else {
+ parent.put(currentFieldName, result);
+ }
+
+ xmlParserData.currentNode = parent;
+ xmlParserData.fieldHierarchy.pop();
+ xmlParserData.restTypes.pop();
+ xmlParserData.attributeHierarchy.pop();
+ xmlParserData.recordTypeStack.pop();
+ xmlParserData.siblings = xmlParserData.parents.pop();
+ }
+
private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString bFieldName, BString bText,
XmlParserData xmlParserData) {
- int elementTypeTag = fieldType.getElementType().getTag();
+ int elementTypeTag = TypeUtils.getReferredType(fieldType.getElementType()).getTag();
switch (elementTypeTag) {
case TypeTags.RECORD_TYPE_TAG -> handleContentFieldInRecordType((RecordType) fieldType.getElementType(),
bText, xmlParserData);
@@ -363,7 +389,7 @@ private Object convertStringToRestExpType(BString value, Type expType) {
return convertStringToExpType(value, PredefinedTypes.TYPE_JSON);
}
case TypeTags.TYPE_REFERENCED_TYPE_TAG -> {
- return convertStringToExpType(value, TypeUtils.getReferredType(expType));
+ return convertStringToRestExpType(value, TypeUtils.getReferredType(expType));
}
}
throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_REST_TYPE, expType.getName());
@@ -468,8 +494,9 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse
updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, referredType);
}
- case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG ->
- updateNextMap(xmlParserData, fieldName, fieldType);
+ case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> {
+ updateNextMap(xmlParserData, fieldName, fieldType);
+ }
}
}
@@ -486,8 +513,9 @@ private void updateNextArrayMember(XMLStreamReader xmlStreamReader, XmlParserDat
private void updateNextMap(XmlParserData xmlParserData, String fieldName, Type fieldType) {
xmlParserData.parents.push(xmlParserData.siblings);
xmlParserData.siblings = new LinkedHashMap<>();
- xmlParserData.currentNode = updateNextMapValue(xmlParserData, fieldName, fieldType);
- handleAttributes(xmlStreamReader, xmlParserData);
+ BMap nextMapValue = updateNextMapValue(xmlParserData, fieldName, fieldType);
+ handleAttributesRest(xmlStreamReader, fieldType, nextMapValue);
+ xmlParserData.currentNode = nextMapValue;
}
private BMap updateNextMapValue(XmlParserData xmlParserData, String fieldName, Type fieldType) {
@@ -609,6 +637,15 @@ private Object parseRestField(XmlParserData xmlParserData) {
return xmlParserData.nodesStack.pop();
}
+ private void updateExpectedTypeStacksOfRestType(Type restType, XmlParserData xmlParserData) {
+ if (restType.getTag() == TypeTags.ARRAY_TAG) {
+ updateExpectedTypeStacksOfRestType(((ArrayType) restType).getElementType(), xmlParserData);
+ } else if (restType.getTag() == TypeTags.ANYDATA_TAG || restType.getTag() == TypeTags.JSON_TAG) {
+ xmlParserData.fieldHierarchy.push(new HashMap<>());
+ xmlParserData.restTypes.push(restType);
+ }
+ }
+
@SuppressWarnings("unchecked")
private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) {
QualifiedName elemQName = getElementName(xmlStreamReader);
@@ -620,16 +657,25 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x
&& !xmlParserData.siblings.getOrDefault(lastElement, true)) {
xmlParserData.parents.push(xmlParserData.siblings);
xmlParserData.siblings = new LinkedHashMap<>();
+ updateExpectedTypeStacksOfRestType(restType, xmlParserData);
xmlParserData.siblings.put(elemQName, false);
- BMap temp =
- (BMap) xmlParserData.currentNode.get(
- StringUtils.fromString(lastElement.getLocalPart()));
BMap next =
ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType));
- temp.put(currentFieldName, next);
- xmlParserData.nodesStack.add(xmlParserData.currentNode);
- xmlParserData.currentNode = temp;
handleAttributesRest(xmlStreamReader, restType, next);
+
+ Object temp = xmlParserData.currentNode.get(
+ StringUtils.fromString(lastElement.getLocalPart()));
+ BMap mapValue;
+ if (temp instanceof BArray) {
+ mapValue = ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType));
+ mapValue.put(currentFieldName, next);
+ ((BArray) temp).append(mapValue);
+ } else {
+ mapValue = (BMap) temp;
+ mapValue.put(currentFieldName, next);
+ }
+ xmlParserData.nodesStack.add(xmlParserData.currentNode);
+ xmlParserData.currentNode = mapValue;
return currentFieldName;
}
@@ -647,14 +693,15 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x
return currentFieldName;
}
- xmlParserData.parents.push(xmlParserData.siblings);
- xmlParserData.siblings = new LinkedHashMap<>();
Object currentElement = xmlParserData.currentNode.get(currentFieldName);
xmlParserData.nodesStack.add(xmlParserData.currentNode);
if (currentElement instanceof BArray) {
int elemTypeTag = ((BArray) currentElement).getElementType().getTag();
if (elemTypeTag == TypeTags.ANYDATA_TAG || elemTypeTag == TypeTags.JSON_TAG) {
+ xmlParserData.parents.push(xmlParserData.siblings);
+ xmlParserData.siblings = new LinkedHashMap<>();
+ updateExpectedTypeStacksOfRestType(restType, xmlParserData);
xmlParserData.currentNode = updateNextArrayMemberForRestType((BArray) currentElement, restType);
}
return currentFieldName;
@@ -670,6 +717,9 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x
int elemTypeTag = tempArray.getElementType().getTag();
if (elemTypeTag == TypeTags.ANYDATA_TAG || elemTypeTag == TypeTags.JSON_TAG) {
+ xmlParserData.parents.push(xmlParserData.siblings);
+ xmlParserData.siblings = new LinkedHashMap<>();
+ updateExpectedTypeStacksOfRestType(restType, xmlParserData);
xmlParserData.currentNode = updateNextArrayMemberForRestType(tempArray, restType);
}
return currentFieldName;
@@ -695,14 +745,14 @@ private void endElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlPa
xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.pop();
xmlParserData.siblings = xmlParserData.parents.pop();
- if (xmlParserData.siblings.containsKey(elemQName) && xmlParserData.restFieldsPoints.remove(elemQName)) {
+ if (xmlParserData.siblings.containsKey(elemQName)) {
xmlParserData.fieldHierarchy.pop();
xmlParserData.restTypes.pop();
}
+ xmlParserData.restFieldsPoints.remove(elemQName);
xmlParserData.siblings.put(elemQName, true);
}
- @SuppressWarnings("unchecked")
private void readTextRest(XMLStreamReader xmlStreamReader,
BString currentFieldName,
boolean isCData,
@@ -727,19 +777,36 @@ private void readTextRest(XMLStreamReader xmlStreamReader,
throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, restType, PredefinedTypes.TYPE_STRING);
}
- Object currentElement = xmlParserData.currentNode.get(currentFieldName);
- BMap parent = (BMap) xmlParserData.nodesStack.peek();
+ convertTextRestAndUpdateCurrentNodeForRestType(xmlParserData.currentNode,
+ (BMap) xmlParserData.nodesStack.peek(), currentFieldName, bText,
+ restType, xmlParserData);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void convertTextRestAndUpdateCurrentNodeForRestType(BMap currentNode,
+ BMap parent,
+ BString currentFieldName,
+ BString bText, Type restType,
+ XmlParserData xmlParserData) {
+ Object currentElement = currentNode.get(currentFieldName);
Object result = convertStringToRestExpType(bText, restType);
- if (currentElement == null && !xmlParserData.currentNode.isEmpty()) { // Add text to the #content field
- xmlParserData.currentNode.put(StringUtils.fromString(Constants.CONTENT), result);
+ if (currentElement == null &&
+ (DataUtils.isAnydataOrJson(restType.getTag()) || DataUtils.isAnydataOrJsonArray(restType)) &&
+ parent != null && parent.get(currentFieldName) instanceof BArray bArray) {
+ bArray.add(bArray.getLength() - 1, result);
+ return;
+ }
+
+ if (currentElement == null && !currentNode.isEmpty()) { // Add text to the #content field
+ currentNode.put(StringUtils.fromString(Constants.CONTENT), result);
xmlParserData.currentNode = parent;
} else if (currentElement instanceof BArray) {
((BArray) currentElement).append(result);
} else if (currentElement instanceof BMap && !((BMap) currentElement).isEmpty()) {
((BMap) currentElement).put(StringUtils.fromString(Constants.CONTENT), result);
} else {
- xmlParserData.currentNode.put(currentFieldName, result);
+ currentNode.put(currentFieldName, result);
}
}
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
index 3d8c7b57..bbb20de6 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlTraversal.java
@@ -327,11 +327,7 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat
BMap nextValue =
ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType));
handleAttributesRest(xmlItem, nextValue, restType);
- if (currentElement instanceof BArray) {
- arrayValue.append(nextValue);
- } else {
- mapValue.put(bElementName, nextValue);
- }
+ arrayValue.append(nextValue);
if (!nextValue.isEmpty()) {
analyzerData.currentField =
@@ -352,7 +348,18 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat
analyzerData.nodesStack.push(currentNode);
currentNode = nextValue;
handleAttributesRest(xmlItem, nextValue, restType);
- traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData);
+
+ analyzerData.fieldHierarchy.push(new HashMap<>());
+ if (restType.getTag() == TypeTags.ARRAY_TAG) {
+ Type memberType = ((ArrayType) restType).getElementType();
+ analyzerData.restTypes.push(memberType);
+ traverseXml(xmlItem.getChildrenSeq(), memberType, analyzerData);
+ } else {
+ analyzerData.restTypes.push(restType);
+ traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData);
+ }
+ analyzerData.fieldHierarchy.pop();
+ analyzerData.restTypes.pop();
currentNode = analyzerData.nodesStack.pop();
return;
}
From a5cc1d5957b925f39ec6540816bab3a4db45be6e Mon Sep 17 00:00:00 2001
From: prakanth <50439067+prakanth97@users.noreply.github.com>
Date: Fri, 8 Mar 2024 15:19:05 +0530
Subject: [PATCH 6/8] Fix wrong behaviour when open record as rest type
---
ballerina/tests/fromXml_test.bal | 44 +++++++++++++++++++
.../stdlib/data/xmldata/utils/Constants.java | 1 +
.../stdlib/data/xmldata/xml/XmlParser.java | 16 +++++++
3 files changed, 61 insertions(+)
diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal
index 72120f35..8c2cece7 100644
--- a/ballerina/tests/fromXml_test.bal
+++ b/ballerina/tests/fromXml_test.bal
@@ -2631,6 +2631,50 @@ function testAnydataArrayAsRestTypeWithFromJsonWithType() returns error? {
]);
}
+@test:Config
+function testRecordAsRestTypeForFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+ A_C_1
+ B_C_1
+ `;
+ record {|
+ record {}...;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), {C: "A_C_1"});
+ test:assertEquals(rec.get("B"), {C: "B_C_1"});
+}
+
+@test:Config
+function testRecordArrayAsRestTypeForFromXmlStringWithType() returns error? {
+ string xmlStr = string `
+
+ A_C_1
+ A_D_1
+
+
+ A_C_2
+ A_D_2
+
+
+ B_C_1
+ B_D_1
+
+
+ B_C_2
+ B_D_2
+
+ `;
+ record {|
+ record {
+ string C;
+ }[]...;
+ |} rec = check fromXmlStringWithType(xmlStr);
+ test:assertEquals(rec.length(), 2);
+ test:assertEquals(rec.get("A"), [{C:"A_C_1", D:"A_D_1"},{C:"A_C_2", D:"A_D_2"}]);
+ test:assertEquals(rec.get("B"), [{C:"B_C_1", D:"B_D_1"},{C:"B_C_2", D:"B_D_2"}]);
+}
+
// Negative cases
type DataN1 record {|
int A;
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/Constants.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/Constants.java
index e860faea..257a53e5 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/Constants.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/utils/Constants.java
@@ -55,4 +55,5 @@ private Constants() {}
public static final String RECORD = "record";
public static final String RECORD_OR_MAP = "record or map";
public static final String ANON_TYPE = "$anonType$";
+ public static final QualifiedName EXIT_REST_POINT = new QualifiedName("", "$exitRestPoint$", "");
}
diff --git a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
index 11f9a81f..88bea058 100644
--- a/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
+++ b/native/src/main/java/io/ballerina/stdlib/data/xmldata/xml/XmlParser.java
@@ -609,6 +609,11 @@ private Object parseRestField(XmlParserData xmlParserData) {
try {
boolean readNext = false;
while (!xmlParserData.restFieldsPoints.isEmpty()) {
+ if (xmlParserData.restFieldsPoints.peek() == Constants.EXIT_REST_POINT) {
+ xmlParserData.restFieldsPoints.pop();
+ break;
+ }
+
switch (next) {
case START_ELEMENT -> currentFieldName = readElementRest(xmlStreamReader, xmlParserData);
case END_ELEMENT -> endElementRest(xmlStreamReader, xmlParserData);
@@ -921,6 +926,7 @@ private Optional