diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index efc9f44..6ed5ca5 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -1589,7 +1589,7 @@ function testXmlWithAttributesAgainstOpenRecord3() returns error? { } @test:Config -function testCommentMiddleInContent() returns error? { +function testCommentMiddleInContent1() returns error? { string xmlStr = string ` John Doe `; @@ -1604,6 +1604,22 @@ function testCommentMiddleInContent() returns error? { test:assertEquals(rec2.A, "John Doe"); } +@test:Config +function testCommentMiddleInContent2() returns error? { + xml xmlVal = xml ` + John Doe + `; + record {} rec = check fromXmlWithType(xmlVal); + test:assertEquals(rec.length(), 1); + test:assertEquals(rec.get("A"), "John Doe"); + + record {| + string A; + |} rec2 = check fromXmlWithType(xmlVal); + test:assertEquals(rec2.length(), 1); + test:assertEquals(rec2.A, "John Doe"); +} + // Negative cases type DataN1 record {| int A; @@ -1871,3 +1887,35 @@ function testXmlToRecordNegative12() { RecAtt6|error rec = fromXmlWithType(xmlVal); test:assertEquals((rec).message(), "required attribute 'data' not present in XML"); } + +@test:Config { + groups: ["fromXmlString"] +} +function testCommentMiddleInContentNegative1() { + string xmlStr = string `12`; + record {| + int A; + |}|error rec1 = fromXmlStringWithType(xmlStr); + test:assertEquals((rec1).message(), "invalid type expected 'int' but found 'string'"); + + record {| + int...; + |}|error rec2 = fromXmlStringWithType(xmlStr); + test:assertEquals((rec2).message(), "invalid type expected 'int' but found 'string'"); +} + +@test:Config { + groups: ["fromXml"] +} +function testCommentMiddleInContentNegative2() { + xml xmlVal = xml `12`; + record {| + int A; + |}|error rec1 = fromXmlWithType(xmlVal); + test:assertEquals((rec1).message(), "invalid type expected 'int' but found 'string'"); + + record {| + int...; + |}|error rec2 = fromXmlWithType(xmlVal); + test:assertEquals((rec2).message(), "invalid type expected 'int' but found 'string'"); +} diff --git a/native/src/main/java/io/ballerina/stdlib/data/utils/DataUtils.java b/native/src/main/java/io/ballerina/stdlib/data/utils/DataUtils.java index f22d81e..3ca6ab2 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/utils/DataUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/data/utils/DataUtils.java @@ -247,6 +247,10 @@ public static boolean isArrayValueAssignable(int typeTag) { return typeTag == TypeTags.ARRAY_TAG || typeTag == TypeTags.ANYDATA_TAG || typeTag == TypeTags.JSON_TAG; } + public static boolean isStringValueAssignable(int typeTag) { + return typeTag == TypeTags.STRING_TAG || typeTag == TypeTags.ANYDATA_TAG || typeTag == TypeTags.JSON_TAG; + } + public static ArrayType getValidArrayType(Type type) { return switch (type.getTag()) { case TypeTags.ARRAY_TAG -> (ArrayType) type; @@ -707,7 +711,7 @@ private static String addAttributeToRecord(BString prefix, BString uri, String k } /** - * Holds data required for the parsing and traversing. + * Holds data required for the traversing. * * @since 0.1.0 */ diff --git a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java index 30b9307..1dc746c 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java +++ b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java @@ -232,7 +232,15 @@ private void readText(XMLStreamReader xmlStreamReader, boolean isCData, XmlParserData xmlParserData) throws XMLStreamException { Field currentField = xmlParserData.currentField; - String text = isCData ? xmlStreamReader.getText() : handleTruncatedCharacters(xmlStreamReader); + TextValue textValue = new TextValue(); + String text; + if (isCData) { + text = xmlStreamReader.getText(); + } else { + handleTruncatedCharacters(xmlStreamReader, textValue); + text = textValue.text; + } + if (text.strip().isBlank()) { return; } @@ -250,6 +258,11 @@ private void readText(XMLStreamReader xmlStreamReader, String fieldName = currentField.getFieldName(); BString bFieldName = StringUtils.fromString(fieldName); Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); + + if (textValue.isCommentInTheMiddle && !DataUtils.isStringValueAssignable(fieldType.getTag())) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, fieldType, PredefinedTypes.TYPE_STRING); + } + if (currentNode.containsKey(bFieldName)) { if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) { throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); @@ -274,15 +287,17 @@ private void readText(XMLStreamReader xmlStreamReader, } } - private String handleTruncatedCharacters(XMLStreamReader xmlStreamReader) throws XMLStreamException { + private void handleTruncatedCharacters(XMLStreamReader xmlStreamReader, TextValue textValue) + throws XMLStreamException { StringBuilder textBuilder = new StringBuilder(); while (xmlStreamReader.getEventType() == CHARACTERS) { textBuilder.append(xmlStreamReader.getText()); if (xmlStreamReader.next() == COMMENT) { + textValue.isCommentInTheMiddle = true; xmlStreamReader.next(); } } - return textBuilder.toString(); + textValue.text = textBuilder.toString(); } @SuppressWarnings("unchecked") @@ -623,13 +638,26 @@ private void readTextRest(XMLStreamReader xmlStreamReader, BString currentFieldName, boolean isCData, XmlParserData xmlParserData) throws XMLStreamException { - String text = isCData ? xmlStreamReader.getText() : handleTruncatedCharacters(xmlStreamReader); + TextValue textValue = new TextValue(); + String text; + if (isCData) { + text = xmlStreamReader.getText(); + } else { + handleTruncatedCharacters(xmlStreamReader, textValue); + text = textValue.text; + } + if (text.strip().isBlank()) { return; } BString bText = StringUtils.fromString(text); Type restType = TypeUtils.getReferredType(xmlParserData.restTypes.peek()); + + if (textValue.isCommentInTheMiddle && !DataUtils.isStringValueAssignable(restType.getTag())) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, restType, PredefinedTypes.TYPE_STRING); + } + Object currentElement = currentNode.get(currentFieldName); BMap parent = (BMap) xmlParserData.nodesStack.peek(); Object result = convertStringToRestExpType(bText, restType); @@ -788,6 +816,21 @@ private QualifiedName getElementName(XMLStreamReader xmlStreamReader) { return new QualifiedName(qName.getNamespaceURI(), qName.getLocalPart(), qName.getPrefix()); } + /** + * Represents the content of an XML element. + * + * @since 0.1.0 + */ + static class TextValue { + String text; + boolean isCommentInTheMiddle = false; + } + + /** + * Holds data required for the parsing. + * + * @since 0.1.0 + */ public static class XmlParserData { private final Stack nodesStack = new Stack<>(); private final Stack> fieldHierarchy = new Stack<>(); diff --git a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlTraversal.java index b1686c0..236b485 100644 --- a/native/src/main/java/io/ballerina/stdlib/data/xml/XmlTraversal.java +++ b/native/src/main/java/io/ballerina/stdlib/data/xml/XmlTraversal.java @@ -18,6 +18,7 @@ package io.ballerina.stdlib.data.xml; +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; @@ -397,6 +398,10 @@ private void convertSequence(BXmlSequence xmlSequence, Type type, XmlAnalyzerDat } private Object convertHeterogeneousSequence(List sequence, Type type, XmlAnalyzerData analyzerData) { + if (isAllChildrenText(sequence)) { + return handleCommentInMiddleOfText(sequence, type, analyzerData); + } + for (BXml bXml: sequence) { if (!isCommentOrPi(bXml)) { traverseXml(bXml, type, analyzerData); @@ -405,6 +410,28 @@ private Object convertHeterogeneousSequence(List sequence, Type type, XmlA return currentNode; } + private boolean isAllChildrenText(List sequence) { + for (BXml bXml: sequence) { + if (bXml.getNodeType() != XmlNodeType.TEXT) { + return false; + } + } + return true; + } + + private Object handleCommentInMiddleOfText(List sequence, Type type, XmlAnalyzerData analyzerData) { + if (!DataUtils.isStringValueAssignable(type.getTag())) { + throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, type, PredefinedTypes.TYPE_STRING); + } + + StringBuilder textBuilder = new StringBuilder(); + for (BXml bXml: sequence) { + textBuilder.append(bXml.toString()); + } + convertText(textBuilder.toString(), analyzerData); + return currentNode; + } + private static boolean isCommentOrPi(BXml bxml) { return bxml.getNodeType() == XmlNodeType.COMMENT || bxml.getNodeType() == XmlNodeType.PI; }