diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 866c4d7..5e8838a 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.8.5" +distribution-version = "2201.8.4" [[package]] org = "ballerina" diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index 8c2cece..59ec2f2 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -2647,7 +2647,7 @@ function testRecordAsRestTypeForFromXmlStringWithType() returns error? { @test:Config function testRecordArrayAsRestTypeForFromXmlStringWithType() returns error? { - string xmlStr = string ` + string xmlStr1 = string ` A_C_1 A_D_1 @@ -2669,10 +2669,88 @@ function testRecordArrayAsRestTypeForFromXmlStringWithType() returns error? { record { string C; }[]...; - |} rec = check fromXmlStringWithType(xmlStr); + |} rec1 = check fromXmlStringWithType(xmlStr1); + test:assertEquals(rec1.length(), 2); + test:assertEquals(rec1.get("A"), [{C:"A_C_1", D:"A_D_1"},{C:"A_C_2", D:"A_D_2"}]); + test:assertEquals(rec1.get("B"), [{C:"B_C_1", D:"B_D_1"},{C:"B_C_2", D:"B_D_2"}]); + + string xmlStr2 = string ` + + + 1 + 2 + 2 + 5 + + 3 + + `; + + record {| + string value; + record {| + int[]...; + |}...; + |} rec2 = check fromXmlStringWithType(xmlStr2); + test:assertEquals(rec2.length(), 2); + test:assertEquals(rec2.get("Depth1"), {A: [1, 2], B: [2, 5]}); + test:assertEquals(rec2.get("value"), "3"); + + record {| + int value; + record {| + string[]...; + |}...; + |} rec3 = check fromXmlStringWithType(xmlStr2); + test:assertEquals(rec3.length(), 2); + test:assertEquals(rec3.get("Depth1"), {A: ["1", "2"], B: ["2", "5"]}); + test:assertEquals(rec3.get("value"), 3); +} + +@test:Config +function testAddingContentFieldWhenRestTypeAsExpTypeForFromXmlStringWithType() returns error? { + string xmlStr = string ` + + 2 + 3 + + `; + + record {} 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"}]); + test:assertEquals(rec.get("A"), {\#content: 2, a: "attribute_a"}); + test:assertEquals(rec.get("B"), {C: {\#content: 3, c: "attribute_c"}}); + + record {| + string A; + record {} B; + |} val2 = check fromXmlStringWithType(xmlStr); + test:assertEquals(val2.length(), 2); + test:assertEquals(val2.A, "2"); + test:assertEquals(val2.B, {C: {\#content: 3, c: "attribute_c"}}); +} + +@test:Config +function testAddingContentFieldWhenRestTypeAsExpTypeForFromXmlWithType() returns error? { + xml xmlVal = xml ` + + 2 + 3 + + `; + + record {} rec = check fromXmlWithType(xmlVal); + test:assertEquals(rec.length(), 2); + test:assertEquals(rec.get("A"), {\#content: 2, a: "attribute_a"}); + test:assertEquals(rec.get("B"), {C: {\#content: 3, c: "attribute_c"}}); + + record {| + string A; + record {} B; + |} val2 = check fromXmlWithType(xmlVal); + test:assertEquals(val2.length(), 2); + test:assertEquals(val2.A, "2"); + test:assertEquals(val2.B, {C: {\#content: 3, c: "attribute_c"}}); } // Negative cases @@ -2974,3 +3052,36 @@ function testCommentMiddleInContentNegative2() { |}|error rec2 = fromXmlWithType(xmlVal); test:assertEquals((rec2).message(), "invalid type expected 'int' but found 'string'"); } + +@test:Config { + groups: ["fromXml"] +} +function testUnsupportedTypeNegative() { + xml xmlVal1 = xml ` + + 1 + 2 + + `; + record {| + xml[] A; + |}|error err1 = fromXmlWithType(xmlVal1); + test:assertEquals((err1).message(), "unsupported input type"); + + xml xmlVal2 = xml ` + + + 1 + + + `; + record {| + record {|string a;|}|record {|string b;|} A; + |}|error err2 = fromXmlWithType(xmlVal2); + test:assertEquals((err2).message(), "unsupported input type"); + + record {| + record {|string a;|}? A; + |}|error err3 = fromXmlWithType(xmlVal2); + test:assertEquals((err3).message(), "unsupported input type"); +} 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 747c9fd..db0c32b 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 @@ -301,11 +301,39 @@ 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; + public static boolean isSupportedType(Type type) { + switch (type.getTag()) { + case TypeTags.NULL_TAG, TypeTags.INT_TAG, TypeTags.BYTE_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, + TypeTags.BOOLEAN_TAG, TypeTags.STRING_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.MAP_TAG, + TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { + return true; + } + case TypeTags.ARRAY_TAG -> { + return isSupportedType(((ArrayType) type).getElementType()); + } + case TypeTags.UNION_TAG -> { + return isSupportedUnionType((UnionType) type); + } + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> { + return isSupportedType(TypeUtils.getReferredType(type)); + } + } + return false; + } + + private static boolean isSupportedUnionType(UnionType type) { + for (Type memberType : type.getMemberTypes()) { + switch (memberType.getTag()) { + case TypeTags.RECORD_TYPE_TAG, TypeTags.OBJECT_TYPE_TAG, TypeTags.MAP_TAG, TypeTags.JSON_TAG, + TypeTags.ANYDATA_TAG, TypeTags.XML_TAG -> { + return false; + } + case TypeTags.UNION_TAG -> { + return !isSupportedUnionType(type); + } + } } - return isAnydataOrJson(((ArrayType) type).getElementType().getTag()); + return true; } @SuppressWarnings("unchecked") 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 88bea05..2ea4771 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 @@ -227,6 +227,7 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, handleAttributes(xmlStreamReader, xmlParserData); } + @SuppressWarnings("unchecked") private void readText(XMLStreamReader xmlStreamReader, boolean isCData, XmlParserData xmlParserData) throws XMLStreamException { @@ -289,7 +290,6 @@ private void readText(XMLStreamReader xmlStreamReader, } } - @SuppressWarnings("unchecked") private void convertTextAndUpdateCurrentNode(BMap currentNode, BMap parent, BString currentFieldName, @@ -302,9 +302,7 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, 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 { + } else { parent.put(currentFieldName, result); } @@ -316,6 +314,7 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, xmlParserData.siblings = xmlParserData.parents.pop(); } + @SuppressWarnings("unchecked") private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString bFieldName, BString bText, XmlParserData xmlParserData) { int elementTypeTag = TypeUtils.getReferredType(fieldType.getElementType()).getTag(); @@ -326,7 +325,6 @@ private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString b 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)); } } @@ -395,9 +393,8 @@ private Object convertStringToRestExpType(BString value, Type expType) { throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_REST_TYPE, expType.getName()); } - private Object buildDocument(XmlParserData xmlParserData) { + private void buildDocument(XmlParserData xmlParserData) { validateRequiredFields(xmlParserData); - return xmlParserData.currentNode; } @SuppressWarnings("unchecked") @@ -494,9 +491,8 @@ 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); } } @@ -699,11 +695,11 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x } 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.nodesStack.add(xmlParserData.currentNode); xmlParserData.parents.push(xmlParserData.siblings); xmlParserData.siblings = new LinkedHashMap<>(); updateExpectedTypeStacksOfRestType(restType, xmlParserData); @@ -722,6 +718,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x int elemTypeTag = tempArray.getElementType().getTag(); if (elemTypeTag == TypeTags.ANYDATA_TAG || elemTypeTag == TypeTags.JSON_TAG) { + xmlParserData.nodesStack.add(xmlParserData.currentNode); xmlParserData.parents.push(xmlParserData.siblings); xmlParserData.siblings = new LinkedHashMap<>(); updateExpectedTypeStacksOfRestType(restType, xmlParserData); @@ -758,6 +755,7 @@ private void endElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlPa xmlParserData.siblings.put(elemQName, true); } + @SuppressWarnings("unchecked") private void readTextRest(XMLStreamReader xmlStreamReader, BString currentFieldName, boolean isCData, @@ -783,30 +781,24 @@ private void readTextRest(XMLStreamReader xmlStreamReader, } convertTextRestAndUpdateCurrentNodeForRestType(xmlParserData.currentNode, - (BMap) xmlParserData.nodesStack.peek(), currentFieldName, bText, - restType, xmlParserData); + (BMap) xmlParserData.nodesStack.peek(), currentFieldName, bText, restType); } @SuppressWarnings("unchecked") private void convertTextRestAndUpdateCurrentNodeForRestType(BMap currentNode, BMap parent, BString currentFieldName, - BString bText, Type restType, - XmlParserData xmlParserData) { + BString bText, Type restType) { Object currentElement = currentNode.get(currentFieldName); Object result = convertStringToRestExpType(bText, restType); - if (currentElement == null && - (DataUtils.isAnydataOrJson(restType.getTag()) || DataUtils.isAnydataOrJsonArray(restType)) && + if (currentElement == null && DataUtils.isAnydataOrJson(restType.getTag()) && 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) { + if (currentElement instanceof BArray) { ((BArray) currentElement).append(result); } else if (currentElement instanceof BMap && !((BMap) currentElement).isEmpty()) { ((BMap) currentElement).put(StringUtils.fromString(Constants.CONTENT), 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 bbb20de..f3d0753 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 @@ -164,6 +164,10 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { BMap mapValue = (BMap) currentNode; Type currentFieldType = TypeUtils.getReferredType(currentField.getFieldType()); + if (!DataUtils.isSupportedType(currentFieldType)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, currentFieldType); + } + String fieldName = currentField.getFieldName(); BString bCurrentFieldName = StringUtils.fromString(fieldName); switch (currentFieldType.getTag()) { @@ -245,7 +249,7 @@ private BMap updateNextRecord(BXmlItem xmlItem, RecordType reco updateNextValue(recordType, fieldName, fieldType, currentMapValue, analyzerData); QName qName = xmlItem.getQName(); DataUtils.validateTypeNamespace(qName.getPrefix(), qName.getNamespaceURI(), recordType); - handleAttributes(xmlItem, nextValue, recordType, analyzerData); + handleAttributes(xmlItem, nextValue, analyzerData); return nextValue; } @@ -502,11 +506,11 @@ private BXml validateRootElement(BXml xml, RecordType recordType, XmlAnalyzerDat // Keep track of fields and attributes DataUtils.updateExpectedTypeStacks(recordType, analyzerData); - handleAttributes(xmlItem, (BMap) currentNode, recordType, analyzerData); + handleAttributes(xmlItem, (BMap) currentNode, analyzerData); return xmlItem.getChildrenSeq(); } - private void handleAttributes(BXmlItem xmlItem, BMap currentNode, RecordType recordType, + private void handleAttributes(BXmlItem xmlItem, BMap currentNode, XmlAnalyzerData analyzerData) { HashSet innerElements = findAllInnerElement(xmlItem); BMap attributeMap = xmlItem.getAttributesMap();