diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index 7896a5c..b424cf2 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,16 +1,16 @@ [package] org = "ballerina" name = "data.xmldata" -version = "0.1.4" +version = "1.0.0" authors = ["Ballerina"] keywords = ["xml"] repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata" license = ["Apache-2.0"] -distribution = "2201.9.0" +distribution = "2201.10.0" export = ["data.xmldata"] [[platform.java17.dependency]] groupId = "io.ballerina.lib" artifactId = "data-native" -version = "0.1.4" -path = "../native/build/libs/data.xmldata-native-0.1.4.jar" +version = "1.0.0" +path = "../native/build/libs/data.xmldata-native-1.0.0-SNAPSHOT.jar" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 133eede..e717227 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "constraint-compiler-plugin" class = "io.ballerina.lib.data.xmldata.compiler.XmldataCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-0.1.4.jar" +path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-1.0.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index e994f42..c3c6683 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,12 +5,12 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240724-114000-40e856f7" [[package]] org = "ballerina" name = "data.xmldata" -version = "0.1.4" +version = "1.0.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index 9785d5f..ea445bc 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -1620,52 +1620,6 @@ function testCommentMiddleInContent2() returns error? { test:assertEquals(rec2.A, "John Doe"); } -@test:Config -function testRegexAsFieldTypeWithParseString() returns error? { - string xmlStr = string ` - 1 - 2 - Code - - Kanth - - `; - record {| - string:RegExp[] A; - string B; - record {| - string name; - |} C; - |} rec1 = check parseString(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 testRegexAsFieldTypeWithParseAsType() returns error? { - xml xmlVal = xml ` - 1 - 2 - Code - - Kanth - - `; - record {| - string:RegExp[] A; - string B; - record {| - string name; - |} C; - |} rec1 = check parseAsType(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 testAnydataAsRestFieldWithParseString() returns error? { string xmlStr = string ` @@ -2812,6 +2766,128 @@ function testProjectionWithXmlAttributeForParseAsType() returns error? { test:assertEquals(rec.A, "2"); } +type DataN3 record {| + int[4] A; +|}; + +@test:Config { + groups: ["fromXmlString"] +} +function testXmlStringToRecordWithArrayAsFieldType() returns error? { + string xmlStr1 = "123"; + DataN3 rec1 = check parseString(xmlStr1); + test:assertEquals(rec1.A, [1, 2, 3, 0]); +} + +@test:Config { + groups: ["fromXml"] +} +function testXmlToRecordWithArrayAsFieldType() returns error? { + xml xmlVal1 = xml `123`; + DataN3 rec1 = check parseAsType(xmlVal1); + test:assertEquals(rec1.A, [1, 2, 3, 0]); +} + +type Student record {| + string name; + int age; + string code = "Admitted"; +|}; + +@test:Config +function testXmlToRecordWithDefaultValuesForParseString1() returns error? { + string xmlStr = string ` + + Walter + 27 + + `; + + Student student = check parseString(xmlStr); + test:assertEquals(student.name, "Walter"); + test:assertEquals(student.age, 27); + test:assertEquals(student.code, "Admitted"); +} + +@test:Config +function testXmlToRecordWithDefaultValuesForPasrseAsType1() returns error? { + xml xmlVal = xml ` + + Walter + 27 + + `; + + Student student = check parseAsType(xmlVal); + test:assertEquals(student.name, "Walter"); + test:assertEquals(student.age, 27); + test:assertEquals(student.code, "Admitted"); +} + +type University record {| + Student[] student; + string name; + string category = "State"; +|}; + +@test:Config +function testXmlToRecordWithDefaultValuesForParseString2() returns error? { + string xmlStr = string ` + + + Walter + 27 + + + Jessy + 18 + + Standford + + `; + + University university = check parseString(xmlStr); + test:assertEquals(university.student[0].name, "Walter"); + test:assertEquals(university.student[0].age, 27); + test:assertEquals(university.student[0].code, "Admitted"); + + test:assertEquals(university.student[1].name, "Jessy"); + test:assertEquals(university.student[1].age, 18); + test:assertEquals(university.student[1].code, "Admitted"); + + test:assertEquals(university.name, "Standford"); + test:assertEquals(university.category, "State"); +} + +@test:Config +function testXmlToRecordWithDefaultValuesForParseAsType2() returns error? { + xml xmlVal = xml ` + + + Walter + 27 + + + Jessy + 18 + + Standford + + `; + + University university = check parseAsType(xmlVal); + test:assertEquals(university.student[0].name, "Walter"); + test:assertEquals(university.student[0].age, 27); + test:assertEquals(university.student[0].code, "Admitted"); + + test:assertEquals(university.student[1].name, "Jessy"); + test:assertEquals(university.student[1].age, 18); + test:assertEquals(university.student[1].code, "Admitted"); + + test:assertEquals(university.name, "Standford"); + test:assertEquals(university.category, "State"); +} + type RecType1 record { string name; @Name { @@ -2838,15 +2914,15 @@ isolated function testElementAndAttributeInSameScopeHaveSameName() returns error Kanth - `; + `; RecType1 rec11 = check parseString(xmlStr); test:assertEquals(rec11.name, "Kanth"); test:assertEquals(rec11.duplicateName, "Kevin"); RecType2 rec12 = check parseString(xmlStr); test:assertEquals(rec12.name.\#content, "Kanth"); - test:assertEquals(rec12.duplicateName, "Kevin"); - + test:assertEquals(rec12.duplicateName, "Kevin"); + xml xmlVal = xml ` Kanth @@ -2858,7 +2934,7 @@ isolated function testElementAndAttributeInSameScopeHaveSameName() returns error RecType2 rec22 = check parseAsType(xmlVal); test:assertEquals(rec22.name.\#content, "Kanth"); - test:assertEquals(rec22.duplicateName, "Kevin"); + test:assertEquals(rec22.duplicateName, "Kevin"); } type RecNs3 record {| @@ -2888,7 +2964,7 @@ isolated function testElementWithDifferentNamespace() returns error? { RecNs3 rec = check parseString(xmlStr); test:assertEquals(rec.name, "Kevin"); test:assertEquals(rec.duplicateName, "Kanth"); - + xml xmlVal = xml ` Kevin @@ -2959,28 +3035,6 @@ function testXmlToRecordNegative4() { test:assertEquals((rec1).message(), "expected 'int' value for the field 'A' found 'array' value"); } -type DataN3 record {| - int[4] A; -|}; - -@test:Config { - groups: ["fromXmlString"] -} -function testXmlStringToRecordNegative5() { - string xmlStr1 = "123"; - DataN3|error rec1 = parseString(xmlStr1); - test:assertEquals((rec1).message(), "array size is not compatible with the expected size"); -} - -@test:Config { - groups: ["fromXml"] -} -function testXmlToRecordNegative5() { - xml xmlVal1 = xml `123`; - DataN3|error rec1 = parseAsType(xmlVal1); - test:assertEquals((rec1).message(), "array size is not compatible with the expected size"); -} - type DataN4 record {| string...; |}; @@ -3329,6 +3383,88 @@ function testInvalidNamespaceInOpenRecordForParseAsType2() { test:assertEquals((err).message(), "undefined field 'name' in record 'data.xmldata:AuthorOpen'"); } +@test:Config +function testRegexAsFieldTypeWithParseStringNegative1() { + string xmlStr = string ` + 1 + 2 + Code + + Kanth + + `; + record {| + string:RegExp[] A; + string B; + record {| + string name; + |} C; + |}|Error err = parseString(xmlStr); + test:assertTrue(err is error); + test:assertEquals((err).message(), "unsupported input type"); +} + +@test:Config +function testRegexAsFieldTypeWithParseStringNegative2() { + string xmlStr = string ` + 1 + Code + + Kanth + + `; + record {| + string:RegExp A; + string B; + record {| + string name; + |} C; + |}|Error err = parseString(xmlStr); + test:assertTrue(err is error); + test:assertEquals((err).message(), "unsupported input type"); +} + +@test:Config +function testRegexAsFieldTypeWithParseAsType() { + xml xmlVal = xml ` + 1 + 2 + Code + + Kanth + + `; + record {| + string:RegExp[] A; + string B; + record {| + string name; + |} C; + |}|error err = parseAsType(xmlVal); + test:assertTrue(err is error); + test:assertEquals((err).message(), "unsupported input type"); +} + +@test:Config +function testRegexAsFieldTypeWithParseAsType2() { + xml xmlVal = xml ` + 1 + Code + + Kanth + + `; + record {| + string:RegExp A; + string B; + record {| + string name; + |} C; + |}|error err = parseAsType(xmlVal); + test:assertTrue(err is error); + test:assertEquals((err).message(), "unsupported input type"); +} + type RecTypeDup1 record { string name; @Name { @@ -3377,7 +3513,7 @@ isolated function testDuplicateField() { RecTypeDup2|Error err3 = parseString(xmlStr2); test:assertTrue(err3 is Error); test:assertEquals(( err3).message(), "duplicate field 'name'"); - + xml xmlVal2 = xml ` Kanth diff --git a/ballerina/tests/from_xml_with_options.bal b/ballerina/tests/from_xml_with_options.bal index 93689fa..881bd41 100644 --- a/ballerina/tests/from_xml_with_options.bal +++ b/ballerina/tests/from_xml_with_options.bal @@ -294,7 +294,7 @@ function testDisableProjectionInArrayForParseStringNegative() { test:assertEquals((rec).message(), "array size is not compatible with the expected size"); } -@test:Config +@test:Config function testDisableProjectionInArrayForParseAsTypeNegative() { xml xmlVal1 = xml ` 1 diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 741191d..5f14962 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.9.0" +distribution = "2201.10.0" export = ["data.xmldata"] [[platform.java17.dependency]] diff --git a/gradle.properties b/gradle.properties index 78a2cb2..89a09c9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.lib -version=0.1.5-SNAPSHOT -ballerinaLangVersion=2201.9.0 +version=1.0.0-SNAPSHOT +ballerinaLangVersion=2201.10.0-20240724-114000-40e856f7 checkstyleToolVersion=10.12.0 puppycrawlCheckstyleVersion=10.12.0 diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java index 04830ac..41eaf87 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/Constants.java @@ -61,4 +61,6 @@ private Constants() {} public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); public static final String RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX = "[^a-zA-Z\\d\s_]"; public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$"; + public static final String REGEXP_MODULE_NAME = "lang.regexp"; + public static final String REGEXP_TYPE_NAME = "RegExp"; } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java index 86fd06f..153c16d 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/DataUtils.java @@ -21,6 +21,7 @@ import io.ballerina.lib.data.xmldata.FromString; import io.ballerina.lib.data.xmldata.xml.QualifiedName; import io.ballerina.lib.data.xmldata.xml.QualifiedNameMap; +import io.ballerina.runtime.api.Module; import io.ballerina.runtime.api.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.TypeCreator; @@ -30,6 +31,7 @@ import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; import io.ballerina.runtime.api.types.RecordType; +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.StringUtils; @@ -213,10 +215,6 @@ private static String getModifiedName(Map fieldAnnotation, Stri return attributeName; } - public static BArray createNewAnydataList(Type type) { - return ValueCreator.createArrayValue(getArrayTypeFromElementType(type)); - } - public static QualifiedName getElementName(QName qName) { return new QualifiedName(qName.getNamespaceURI(), qName.getLocalPart(), qName.getPrefix()); } @@ -238,33 +236,16 @@ public static Object convertStringToExpType(BString value, Type expType) { return result; } - public static void validateRequiredFields(BMap currentMapValue, XmlAnalyzerData analyzerData) { - Map fields = analyzerData.fieldHierarchy.peek().getMembers(); - for (QualifiedName key : fields.keySet()) { - // Validate required array size - Field field = fields.get(key); - String fieldName = field.getFieldName(); - if (field.getFieldType().getTag() == TypeTags.ARRAY_TAG) { - ArrayType arrayType = (ArrayType) field.getFieldType(); - if (arrayType.getSize() != -1 - && arrayType.getSize() != ((BArray) currentMapValue.get( - StringUtils.fromString(fieldName))).getLength()) { - throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); - } - } - - if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) - && !currentMapValue.containsKey(StringUtils.fromString(fieldName))) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, fieldName); + public static void validateRequiredFields(XmlAnalyzerData analyzerData) { + for (Field field : analyzerData.fieldHierarchy.peek().getMembers().values()) { + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, field.getFieldName()); } } - Map attributes = analyzerData.attributeHierarchy.peek().getMembers(); - for (QualifiedName key : attributes.keySet()) { - Field field = attributes.get(key); - String fieldName = field.getFieldName(); - if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, fieldName); + for (Field attribute : analyzerData.attributeHierarchy.peek().getMembers().values()) { + if (!SymbolFlags.isFlagOn(attribute.getFlags(), SymbolFlags.OPTIONAL)) { + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, attribute.getFieldName()); } } } @@ -277,27 +258,15 @@ 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; - case TypeTags.ANYDATA_TAG -> PredefinedTypes.TYPE_ANYDATA_ARRAY; - case TypeTags.JSON_TAG -> PredefinedTypes.TYPE_JSON_ARRAY; - default -> null; - }; - } - - public static ArrayType getArrayTypeFromElementType(Type type) { + public static BArray createArrayValue(Type type) { return switch (type.getTag()) { - case TypeTags.ARRAY_TAG -> TypeCreator.createArrayType(((ArrayType) type).getElementType()); - case TypeTags.JSON_TAG -> PredefinedTypes.TYPE_JSON_ARRAY; - case TypeTags.INT_TAG, TypeTags.FLOAT_TAG, TypeTags.STRING_TAG, TypeTags.BOOLEAN_TAG, TypeTags.BYTE_TAG, - TypeTags.DECIMAL_TAG, TypeTags.RECORD_TYPE_TAG, TypeTags.MAP_TAG, TypeTags.OBJECT_TYPE_TAG, - TypeTags.XML_TAG, TypeTags.NULL_TAG -> TypeCreator.createArrayType(type); - case TypeTags.TYPE_REFERENCED_TYPE_TAG -> getArrayTypeFromElementType(TypeUtils.getReferredType(type)); - default -> PredefinedTypes.TYPE_ANYDATA_ARRAY; + case TypeTags.ARRAY_TAG -> ValueCreator.createArrayValue((ArrayType) type); + case TypeTags.JSON_TAG -> ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); + case TypeTags.ANYDATA_TAG -> ValueCreator.createArrayValue(PredefinedTypes.TYPE_ANYDATA_ARRAY); + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> createArrayValue(TypeUtils.getReferredType(type)); + default -> throw new IllegalStateException("Unexpected value: " + type.getTag()); }; } - public static MapType getMapTypeFromConstraintType(Type constraintType) { return switch (constraintType.getTag()) { case TypeTags.MAP_TAG -> (MapType) constraintType; @@ -312,15 +281,18 @@ public static MapType getMapTypeFromConstraintType(Type constraintType) { } public static void updateExpectedTypeStacks(RecordType recordType, XmlAnalyzerData analyzerData) { - analyzerData.attributeHierarchy.push(new QualifiedNameMap(getAllAttributesInRecordType(recordType))); - analyzerData.fieldHierarchy.push(new QualifiedNameMap(getAllFieldsInRecordType(recordType, analyzerData))); + analyzerData.attributeHierarchy.push(new QualifiedNameMap<>(getAllAttributesInRecordType(recordType))); + analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(getAllFieldsInRecordType(recordType, analyzerData))); + analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.restTypes.push(recordType.getRestFieldType()); } - public static void removeExpectedTypeStacks(XmlAnalyzerData analyzerData) { - analyzerData.attributeHierarchy.pop(); + public static void popExpectedTypeStacks(XmlAnalyzerData analyzerData) { analyzerData.fieldHierarchy.pop(); + analyzerData.visitedFieldHierarchy.pop(); analyzerData.restTypes.pop(); + analyzerData.attributeHierarchy.pop(); + analyzerData.arrayIndexes.pop(); } public static boolean isAnydataOrJson(int typeTag) { @@ -850,6 +822,27 @@ private static String addAttributeToRecord(BString prefix, BString uri, String k return prefix.getValue().concat(Constants.COLON).concat(key); } + public static boolean isRegExpType(Type type) { + Module module = type.getPackage(); + if (module == null) { + return false; + } + + String moduleName = module.getName(); + String typeName = type.getName(); + if (typeName == null || moduleName == null) { + return false; + } + if (moduleName.equals(Constants.REGEXP_MODULE_NAME) && typeName.equals(Constants.REGEXP_TYPE_NAME)) { + return true; + } + + if (type.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { + return isRegExpType(((ReferenceType) type).getReferredType()); + } + return false; + } + private static boolean isNamespaceAnnotationKey(String key) { return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.NAMESPACE); } @@ -870,8 +863,10 @@ private static boolean isAttributeAnnotationKey(String key) { public static class XmlAnalyzerData { public final Stack nodesStack = new Stack<>(); public final Stack> fieldHierarchy = new Stack<>(); + public final Stack> visitedFieldHierarchy = new Stack<>(); public final Stack> attributeHierarchy = new Stack<>(); public final Stack restTypes = new Stack<>(); + public final Stack> arrayIndexes = new Stack<>(); public RecordType rootRecord; public Field currentField; public QualifiedName rootElement; diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java index a7e8084..22d8e54 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlParser.java @@ -132,6 +132,7 @@ public Object parse(Type type, XmlParserData xmlParserData) { private void reset(XmlParserData xmlParserData) { xmlParserData.fieldHierarchy.clear(); + xmlParserData.visitedFieldHierarchy.clear(); xmlParserData.attributeHierarchy.clear(); xmlParserData.restTypes.clear(); xmlParserData.nodesStack.clear(); @@ -139,6 +140,7 @@ private void reset(XmlParserData xmlParserData) { xmlParserData.siblings.clear(); xmlParserData.recordTypeStack.clear(); xmlParserData.restFieldsPoints.clear(); + xmlParserData.arrayIndexes.clear(); } public Object parse(XmlParserData xmlParserData) { @@ -200,9 +202,7 @@ public void parseRecordRest(String startElementName, XmlParserData xmlParserData QName endElement = xmlStreamReader.getName(); if (endElement.getLocalPart().equals(startElementName)) { validateRequiredFields(xmlParserData); - xmlParserData.fieldHierarchy.pop(); - xmlParserData.restTypes.pop(); - xmlParserData.attributeHierarchy.pop(); + popExpectedTypeStacks(xmlParserData); break; } } @@ -228,7 +228,7 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, } RecordType rootRecord = xmlParserData.rootRecord; - xmlParserData.currentNode = ValueCreator.createRecordValue(rootRecord); + xmlParserData.currentNode = ValueCreator.createRecordValue(rootRecord.getPackage(), rootRecord.getName()); QualifiedName elementQName = getElementName(xmlStreamReader); xmlParserData.rootElement = DataUtils.validateAndGetXmlNameFromRecordAnnotation(rootRecord, rootRecord.getName(), elementQName); @@ -237,6 +237,7 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, // Keep track of fields and attributes updateExpectedTypeStacks(rootRecord, xmlParserData); handleAttributes(xmlStreamReader, xmlParserData); + xmlParserData.arrayIndexes.push(new HashMap<>()); } @SuppressWarnings("unchecked") @@ -261,11 +262,14 @@ private void readText(XMLStreamReader xmlStreamReader, if (currentField == null) { QualifiedName contentQName = new QualifiedName("", textFieldName, ""); if (!xmlParserData.fieldHierarchy.peek().contains(contentQName)) { - if (!xmlParserData.allowDataProjection) { + if (xmlParserData.visitedFieldHierarchy.peek().contains(contentQName)) { + currentField = xmlParserData.visitedFieldHierarchy.peek().get(contentQName); + } else if (!xmlParserData.allowDataProjection) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, textFieldName, xmlParserData.rootRecord); + } else { + return; } - return; } else { currentField = xmlParserData.fieldHierarchy.peek().remove(contentQName); } @@ -280,23 +284,23 @@ private void readText(XMLStreamReader xmlStreamReader, throw DiagnosticLog.error(DiagnosticErrorCode.INVALID_TYPE, fieldType, PredefinedTypes.TYPE_STRING); } - if (xmlParserData.currentNode.containsKey(bFieldName) && !DataUtils.isAnydataOrJson(fieldType.getTag())) { + Object temp = xmlParserData.currentNode.get(bFieldName); + if (temp instanceof BArray && !DataUtils.isAnydataOrJson(fieldType.getTag())) { if (fieldName.equals(textFieldName)) { xmlParserData.currentNode.put(bFieldName, convertStringToRestExpType(bText, fieldType)); return; } - if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) { - throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); - } - - int arraySize = ((ArrayType) fieldType).getSize(); - if (arraySize != -1 && arraySize <= ((BArray) xmlParserData.currentNode.get(bFieldName)).getLength()) { + ArrayType arrayType = (ArrayType) fieldType; + int currentIndex = xmlParserData.arrayIndexes.peek().get(fieldName); + if (arrayType.getState() == ArrayType.ArrayState.CLOSED + && arrayType.getSize() <= currentIndex) { DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(xmlParserData.allowDataProjection); return; } - ((BArray) xmlParserData.currentNode.get(bFieldName)).append(convertStringToRestExpType(bText, fieldType)); + ((BArray) xmlParserData.currentNode.get(bFieldName)).add(currentIndex, + convertStringToRestExpType(bText, fieldType)); return; } @@ -321,20 +325,17 @@ private void convertTextAndUpdateCurrentNode(BMap currentNode, Object currentElement = currentNode.get(currentFieldName); Object result = convertStringToRestExpType(bText, restType); - if (currentElement == null && !currentNode.isEmpty()) { // Add text to the #content field + 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 { + } 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(); + popExpectedTypeStacks(xmlParserData); + updateSiblingAndRootRecord(xmlParserData); } @SuppressWarnings("unchecked") @@ -346,11 +347,14 @@ private void addTextToCurrentNodeIfExpTypeIsArray(ArrayType fieldType, BString b bText, xmlParserData); case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> { BArray tempArr = (BArray) ((BMap) xmlParserData.nodesStack.peek()).get(bFieldName); - Object nextValue = tempArr.get(tempArr.getLength() - 1); - if (!(nextValue instanceof BMap)) { + HashMap indexes + = xmlParserData.arrayIndexes.get(xmlParserData.arrayIndexes.size() - 2); + int currentIndex = indexes.get(bFieldName.getValue()); + if (fieldType.getState() == ArrayType.ArrayState.CLOSED && currentIndex >= fieldType.getSize()) { + DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(xmlParserData.allowDataProjection); return; } - tempArr.add(tempArr.getLength() - 1, convertStringToRestExpType(bText, fieldType)); + tempArr.add(currentIndex, convertStringToRestExpType(bText, fieldType)); } } } @@ -370,7 +374,8 @@ private void handleTruncatedCharacters(XMLStreamReader xmlStreamReader, TextValu @SuppressWarnings("unchecked") private void handleContentFieldInRecordType(RecordType recordType, BString text, XmlParserData xmlParserData) { - popStacks(xmlParserData); + popExpectedTypeStacks(xmlParserData); + updateSiblingAndRootRecord(xmlParserData); for (String key : recordType.getFields().keySet()) { if (key.contains(xmlParserData.textFieldName)) { xmlParserData.currentNode.put(StringUtils.fromString(key), @@ -439,36 +444,22 @@ private void endElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParser validateRequiredFields(xmlParserData); xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.pop(); - popStacks(xmlParserData); + popExpectedTypeStacks(xmlParserData); + updateSiblingAndRootRecord(xmlParserData); } private void validateRequiredFields(XmlParserData xmlParserData) { - BMap currentMapValue = xmlParserData.currentNode; - Map fields = xmlParserData.fieldHierarchy.peek().getMembers(); - for (QualifiedName key : fields.keySet()) { - // Validate required array size - Field field = fields.get(key); - String fieldName = field.getFieldName(); - if (field.getFieldType().getTag() == TypeTags.ARRAY_TAG) { - ArrayType arrayType = (ArrayType) field.getFieldType(); - if (arrayType.getSize() != -1 - && arrayType.getSize() != ((BArray) currentMapValue.get( - StringUtils.fromString(fieldName))).getLength()) { - throw DiagnosticLog.error(DiagnosticErrorCode.ARRAY_SIZE_MISMATCH); - } - } - - if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL) - && !currentMapValue.containsKey(StringUtils.fromString(fieldName))) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, fieldName); + Map remainingFields = xmlParserData.fieldHierarchy.peek().getMembers(); + for (Field field : remainingFields.values()) { + if (SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.REQUIRED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_FIELD_NOT_PRESENT, field.getFieldName()); } } - Map attributes = xmlParserData.attributeHierarchy.peek().getMembers(); - for (QualifiedName key : attributes.keySet()) { - Field field = attributes.get(key); - if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) { - throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, field.getFieldName()); + Map remainingAttributes = xmlParserData.attributeHierarchy.peek().getMembers(); + for (Field attribute : remainingAttributes.values()) { + if (!SymbolFlags.isFlagOn(attribute.getFlags(), SymbolFlags.OPTIONAL)) { + throw DiagnosticLog.error(DiagnosticErrorCode.REQUIRED_ATTRIBUTE_NOT_PRESENT, attribute.getFieldName()); } } } @@ -476,7 +467,13 @@ private void validateRequiredFields(XmlParserData xmlParserData) { private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader); QualifiedNameMap fieldMap = xmlParserData.fieldHierarchy.peek(); - Field currentField = fieldMap.get(elemQName); + Field currentField = null; + if (xmlParserData.visitedFieldHierarchy.peek().contains(elemQName)) { + currentField = xmlParserData.visitedFieldHierarchy.peek().get(elemQName); + } else if (fieldMap.contains(elemQName)) { + elemQName = fieldMap.getMatchedQualifiedName(elemQName); + currentField = fieldMap.remove(elemQName); + } xmlParserData.currentField = currentField; if (currentField == null) { String elemName = elemQName.getLocalPart(); @@ -493,45 +490,72 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse return; } - elemQName = fieldMap.getMatchedQualifiedName(elemQName); + xmlParserData.visitedFieldHierarchy.peek().put(elemQName, currentField); BMap currentNode = xmlParserData.currentNode; String fieldName = currentField.getFieldName(); - Object temp = currentNode.get(StringUtils.fromString(fieldName)); BString bFieldName = StringUtils.fromString(fieldName); - Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); + Object temp = currentNode.get(bFieldName); + Type fieldType = currentField.getFieldType(); + Type referredFieldType = TypeUtils.getReferredType(fieldType); if (!xmlParserData.siblings.contains(elemQName)) { xmlParserData.siblings.put(elemQName, false); - currentNode.remove(bFieldName); // This handles attribute and element with same name. Removes attribute. - } else if (!(temp instanceof BArray)) { - BArray tempArray = ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(fieldType)); - tempArray.append(temp); - currentNode.put(bFieldName, tempArray); + } else { + if (DataUtils.isAnydataOrJson(referredFieldType.getTag()) && !(temp instanceof BArray)) { + BArray tempArray = DataUtils.createArrayValue(referredFieldType); + tempArray.append(temp); + currentNode.put(bFieldName, tempArray); + } else if (referredFieldType.getTag() != TypeTags.ARRAY_TAG) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); + } + } + + if (DataUtils.isRegExpType(fieldType)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE); } + initializeNextValueBasedOnExpectedType(fieldName, fieldType, temp, currentNode, xmlParserData); + } + + private void initializeNextValueBasedOnExpectedType(String fieldName, Type fieldType, Object temp, + BMap currentNode, + XmlParserData xmlParserData) { switch (fieldType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> updateNextRecord(xmlStreamReader, xmlParserData, fieldName, fieldType, (RecordType) fieldType); case TypeTags.ARRAY_TAG -> { - Type referredType = TypeUtils.getReferredType(((ArrayType) fieldType).getElementType()); - if (!currentNode.containsKey(bFieldName)) { + if (temp == null) { + xmlParserData.arrayIndexes.peek().put(fieldName, 0); currentNode.put(StringUtils.fromString(fieldName), - ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(referredType))); + ValueCreator.createArrayValue((ArrayType) fieldType)); + } else { + HashMap indexes = xmlParserData.arrayIndexes.peek(); + indexes.put(fieldName, indexes.get(fieldName) + 1); } - - updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, referredType); + updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, + ((ArrayType) fieldType).getElementType()); } case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> + initializeNextValueBasedOnExpectedType(fieldName, TypeUtils.getReferredType(fieldType), temp, + currentNode, xmlParserData); } } private void updateNextArrayMember(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData, String fieldName, Type fieldType, Type type) { + if (DataUtils.isRegExpType(type)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_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 -> initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> + updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, + TypeUtils.getReferredType(type)); } } @@ -562,20 +586,27 @@ private BMap updateNextMappingValue(XmlParserData xmlParserData xmlParserData.attributeHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.recordTypeStack.push(null); BMap currentNode = xmlParserData.currentNode; Object temp = currentNode.get(StringUtils.fromString(fieldName)); if (temp instanceof BArray) { - int arraySize = getArraySize(fieldType, temp); - if (arraySize > ((BArray) temp).getLength() || arraySize == -1) { + if (DataUtils.isAnydataOrJson(fieldType.getTag())) { ((BArray) temp).append(nextValue); } else { - DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(xmlParserData.allowDataProjection); + int arraySize = getArraySize(fieldType, temp); + int currentIndex = xmlParserData.arrayIndexes.peek().get(fieldName); + if (currentIndex < arraySize || arraySize == -1) { + ((BArray) temp).add(currentIndex, nextValue); + } else { + DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(xmlParserData.allowDataProjection); + } } } else { currentNode.put(StringUtils.fromString(fieldName), nextValue); } xmlParserData.nodesStack.push(currentNode); + xmlParserData.arrayIndexes.push(new HashMap<>()); return nextValue; } @@ -598,14 +629,15 @@ private void updateNextRecord(XMLStreamReader xmlStreamReader, XmlParserData xml private BMap updateNextValue(RecordType rootRecord, String fieldName, Type fieldType, XmlParserData xmlParserData) { - BMap nextValue = ValueCreator.createRecordValue(rootRecord); + BMap nextValue = ValueCreator.createRecordValue(rootRecord.getPackage(), rootRecord.getName()); updateExpectedTypeStacks(rootRecord, xmlParserData); BMap currentNode = xmlParserData.currentNode; Object temp = currentNode.get(StringUtils.fromString(fieldName)); if (temp instanceof BArray) { - int arraySize = ((ArrayType) fieldType).getSize(); - if (arraySize > ((BArray) temp).getLength() || arraySize == -1) { - ((BArray) temp).append(nextValue); + ArrayType arrayType = (ArrayType) fieldType; + int currentIndex = xmlParserData.arrayIndexes.peek().get(fieldName); + if (arrayType.getState() == ArrayType.ArrayState.OPEN || currentIndex < arrayType.getSize()) { + ((BArray) temp).add(currentIndex, nextValue); } else { DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(xmlParserData.allowDataProjection); } @@ -613,19 +645,30 @@ private BMap updateNextValue(RecordType rootRecord, String fiel currentNode.put(StringUtils.fromString(fieldName), nextValue); } xmlParserData.nodesStack.push(currentNode); + xmlParserData.arrayIndexes.push(new HashMap<>()); return nextValue; } private void updateExpectedTypeStacks(RecordType recordType, XmlParserData xmlParserData) { xmlParserData.attributeHierarchy.push(new QualifiedNameMap<>(getAllAttributesInRecordType(recordType))); xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(getAllFieldsInRecordType(recordType, xmlParserData))); + xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.restTypes.push(recordType.getRestFieldType()); } - private void popStacks(XmlParserData xmlParserData) { + private void popExpectedTypeStacks(XmlParserData xmlParserData) { + popMappingTypeStacks(xmlParserData); + xmlParserData.attributeHierarchy.pop(); + xmlParserData.arrayIndexes.pop(); + } + + private void popMappingTypeStacks(XmlParserData xmlParserData) { xmlParserData.fieldHierarchy.pop(); + xmlParserData.visitedFieldHierarchy.pop(); xmlParserData.restTypes.pop(); - xmlParserData.attributeHierarchy.pop(); + } + + private void updateSiblingAndRootRecord(XmlParserData xmlParserData) { xmlParserData.siblings = xmlParserData.parents.pop(); xmlParserData.rootRecord = xmlParserData.recordTypeStack.pop(); } @@ -688,7 +731,9 @@ private void updateExpectedTypeStacksOfRestType(Type restType, XmlParserData xml updateExpectedTypeStacksOfRestType(((ArrayType) restType).getElementType(), xmlParserData); } else if (restType.getTag() == TypeTags.ANYDATA_TAG || restType.getTag() == TypeTags.JSON_TAG) { xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + xmlParserData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.restTypes.push(restType); + xmlParserData.arrayIndexes.push(new HashMap<>()); } } @@ -728,7 +773,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x if (!xmlParserData.siblings.contains(elemQName)) { xmlParserData.siblings.put(elemQName, false); if (restType.getTag() == TypeTags.ARRAY_TAG) { - BArray tempArray = ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(restType)); + BArray tempArray = DataUtils.createArrayValue(restType); xmlParserData.currentNode.put(currentFieldName, tempArray); } else { BMap next = @@ -757,7 +802,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x throw DiagnosticLog.error( DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, restType, elemQName.getLocalPart()); } - BArray tempArray = ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(restType)); + BArray tempArray = DataUtils.createArrayValue(restType); tempArray.append(currentElement); xmlParserData.currentNode.put(currentFieldName, tempArray); @@ -793,9 +838,10 @@ private void endElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlPa xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.pop(); xmlParserData.siblings = xmlParserData.parents.pop(); if (xmlParserData.siblings.contains(elemQName)) { - // TODO: This is duplicated in several places. Remove the duplication. - xmlParserData.fieldHierarchy.pop(); - xmlParserData.restTypes.pop(); + // TODO: This place behaviour is strange need to check and fix it, Properly. + popMappingTypeStacks(xmlParserData); +// xmlParserData.attributeHierarchy.pop(); + xmlParserData.arrayIndexes.pop(); } removeQNameFromRestFieldsPoints(elemQName, xmlParserData); xmlParserData.siblings.put(elemQName, true); @@ -961,6 +1007,8 @@ private void handleAttributes(XMLStreamReader xmlStreamReader, XmlParserData xml ATTRIBUTE); Field field = xmlParserData.attributeHierarchy.peek().remove(attQName); if (field == null) { + // Here attQName state is set to NOT_DEFINED since it accessed from field hierarchy. + attQName.setAttributeState(NOT_DEFINED); Optional f = getFieldFromFieldHierarchy(attQName, xmlParserData); if (f.isEmpty()) { continue; @@ -978,7 +1026,14 @@ private void handleAttributes(XMLStreamReader xmlStreamReader, XmlParserData xml } private Optional getFieldFromFieldHierarchy(QualifiedName attQName, XmlParserData xmlParserData) { - Field field = xmlParserData.fieldHierarchy.peek().get(attQName); + Field field; + if (xmlParserData.visitedFieldHierarchy.peek().contains(attQName)) { + field = xmlParserData.visitedFieldHierarchy.peek().get(attQName); + } else { + field = xmlParserData.fieldHierarchy.peek().remove(attQName); + xmlParserData.visitedFieldHierarchy.peek().put(attQName, field); + } + if (field != null) { return Optional.of(field); } @@ -1022,12 +1077,19 @@ private Optional handleRecordRestType(XmlParserData xmlParserData, XMLSt case TypeTags.ARRAY_TAG -> { ArrayType arrayType = (ArrayType) restType; Type elemType = TypeUtils.getReferredType(arrayType.getElementType()); + HashMap indexes = xmlParserData.arrayIndexes.peek(); + if (indexes.containsKey(fieldName)) { + indexes.put(fieldName, indexes.get(fieldName) + 1); + } else { + indexes.put(fieldName, 0); + } + if (elemType.getTag() == TypeTags.RECORD_TYPE_TAG) { updateStacksWhenRecordAsRestType(elementQName, xmlParserData); // Create an array value since expected type is an array. if (!xmlParserData.currentNode.containsKey(StringUtils.fromString(fieldName))) { xmlParserData.currentNode.put(StringUtils.fromString(fieldName), - ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(elemType))); + DataUtils.createArrayValue(arrayType)); } xmlParserData.currentNode = updateNextValue((RecordType) elemType, fieldName, arrayType, xmlParserData); @@ -1073,10 +1135,12 @@ static class TextValue { public static class XmlParserData { private final Stack nodesStack = new Stack<>(); private final Stack> fieldHierarchy = new Stack<>(); + Stack> visitedFieldHierarchy = new Stack<>(); private final Stack> attributeHierarchy = new Stack<>(); private final Stack restTypes = new Stack<>(); private final Stack restFieldsPoints = new Stack<>(); private final Stack recordTypeStack = new Stack<>(); + Stack> arrayIndexes = new Stack<>(); private RecordType rootRecord; private Field currentField; private QualifiedName rootElement; diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java index 0e7d500..7daca26 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java @@ -77,10 +77,10 @@ public Object traverseXml(BXml xml, BMap options, Type type) { XmlAnalyzerData analyzerData = new XmlAnalyzerData(); DataUtils.updateOptions(options, analyzerData); RecordType recordType = (RecordType) referredType; - currentNode = ValueCreator.createRecordValue(recordType); + currentNode = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); BXml nextXml = validateRootElement(xml, recordType, analyzerData); Object resultRecordValue = traverseXml(nextXml, recordType, analyzerData); - DataUtils.validateRequiredFields((BMap) resultRecordValue, analyzerData); + DataUtils.validateRequiredFields(analyzerData); return resultRecordValue; } case TypeTags.MAP_TAG -> { @@ -115,6 +115,8 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { QualifiedName contentQName = new QualifiedName("", textFieldName, ""); if (analyzerData.fieldHierarchy.peek().contains(contentQName)) { currentField = analyzerData.fieldHierarchy.peek().remove(contentQName); + } else if (analyzerData.visitedFieldHierarchy.peek().contains(contentQName)) { + currentField = analyzerData.visitedFieldHierarchy.peek().get(contentQName); } else if (analyzerData.restTypes.peek() != null) { currentField = TypeCreator.createField(analyzerData.restTypes.peek(), analyzerData.textFieldName, SymbolFlags.REQUIRED); @@ -130,29 +132,25 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); Object convertedValue = DataUtils.convertStringToExpType(StringUtils.fromString(text), fieldType); - if (mapValue.containsKey(fieldName)) { + Object value = mapValue.get(fieldName); + if (value instanceof BArray) { if (fieldName.getValue().equals(textFieldName)) { mapValue.put(fieldName, convertedValue); return; } - if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) { - throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, fieldName); - } - Object value = mapValue.get(fieldName); - int arraySize = (DataUtils.getValidArrayType(fieldType)).getSize(); - if (value instanceof BArray) { - if (arraySize != -1 && arraySize <= ((BArray) value).getLength()) { - DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(analyzerData.allowDataProjection); - return; - } + if (DataUtils.isAnydataOrJson(fieldType.getTag())) { ((BArray) value).append(convertedValue); - } else { - BArray array = DataUtils.createNewAnydataList(fieldType); - array.append(value); - array.append(convertedValue); - mapValue.put(fieldName, array); + return; + } + + ArrayType arrayType = (ArrayType) fieldType; + int currentIndex = analyzerData.arrayIndexes.peek().get(fieldName.getValue()); + if (arrayType.getState() == ArrayType.ArrayState.CLOSED && arrayType.getSize() <= currentIndex) { + DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(analyzerData.allowDataProjection); + return; } + ((BArray) value).add(currentIndex, convertedValue); } else { mapValue.put(fieldName, convertedValue); } @@ -162,7 +160,18 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { QualifiedName elementQName = DataUtils.getElementName(xmlItem.getQName()); QualifiedNameMap fieldsMap = analyzerData.fieldHierarchy.peek(); - Field currentField = fieldsMap.get(elementQName); + Field currentField; + if (analyzerData.visitedFieldHierarchy.peek().contains(elementQName)) { + currentField = analyzerData.visitedFieldHierarchy.peek().get(elementQName); + Type fieldType = TypeUtils.getReferredType(currentField.getFieldType()); + if (!DataUtils.isArrayValueAssignable(fieldType.getTag())) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, fieldType, + currentField.getFieldName()); + } + } else { + currentField = fieldsMap.remove(elementQName); + } + analyzerData.currentField = currentField; if (currentField == null) { @@ -173,7 +182,7 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName, analyzerData.rootRecord); } - convertWithRestType(xmlItem, restType, analyzerData); + convertToRestType(xmlItem, restType, analyzerData); } else if (!analyzerData.allowDataProjection) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName, analyzerData.rootRecord); @@ -181,80 +190,107 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { return; } - BMap mapValue = (BMap) currentNode; - Type currentFieldType = TypeUtils.getReferredType(currentField.getFieldType()); + analyzerData.visitedFieldHierarchy.peek().put(elementQName, currentField); + Type currentFieldType = currentField.getFieldType(); if (!DataUtils.isSupportedType(currentFieldType)) { throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE, currentFieldType); } - String fieldName = currentField.getFieldName(); - BString bCurrentFieldName = StringUtils.fromString(fieldName); + if (DataUtils.isRegExpType(currentFieldType)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE); + } + + convertToFieldType(xmlItem, currentField, currentField.getFieldName(), currentFieldType, + (BMap) currentNode, analyzerData); + } + + private void convertToFieldType(BXmlItem xmlItem, Field currentField, String fieldName, Type currentFieldType, + BMap mapValue, XmlAnalyzerData analyzerData) { switch (currentFieldType.getTag()) { - case TypeTags.RECORD_TYPE_TAG -> { - handleRecordType(xmlItem, currentFieldType, fieldName, (RecordType) currentFieldType, mapValue, - analyzerData); - return; - } - case TypeTags.ARRAY_TAG -> { - if (!mapValue.containsKey(bCurrentFieldName)) { - BArray array = DataUtils.createNewAnydataList(currentFieldType); - mapValue.put(bCurrentFieldName, array); - } - Type elementType = TypeUtils.getReferredType(((ArrayType) currentFieldType).getElementType()); - int elementTypeTag = elementType.getTag(); - if (elementTypeTag == TypeTags.RECORD_TYPE_TAG) { - handleRecordType(xmlItem, currentFieldType, fieldName, (RecordType) elementType, mapValue, - analyzerData); - return; - } else if (elementTypeTag == TypeTags.MAP_TAG) { - updateNextMap(elementType, analyzerData); - currentNode = updateNextValue(elementType, 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 -> { - 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.RECORD_TYPE_TAG -> convertToRecordType(xmlItem, currentFieldType, fieldName, + (RecordType) currentFieldType, mapValue, analyzerData); + case TypeTags.ARRAY_TAG -> convertToArrayType(xmlItem, currentField, mapValue, + StringUtils.fromString(fieldName), (ArrayType) currentFieldType, analyzerData); + case TypeTags.MAP_TAG -> convertToMapType(xmlItem, currentFieldType, currentFieldType, + fieldName, mapValue, analyzerData); case TypeTags.JSON_TAG, TypeTags.ANYDATA_TAG -> { updateNextMap(currentFieldType, analyzerData); - convertWithRestType(xmlItem, currentFieldType, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); - return; - } + analyzerData.arrayIndexes.push(new HashMap<>()); + convertToRestType(xmlItem, currentFieldType, analyzerData); + DataUtils.popExpectedTypeStacks(analyzerData); + } + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> + convertToFieldType(xmlItem, currentField, fieldName, TypeUtils.getReferredType(currentFieldType), + mapValue, analyzerData); + default -> traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); } - traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); } - private void handleRecordType(BXmlItem xmlItem, Type currentFieldType, String fieldName, RecordType elementType, - BMap mapValue, XmlAnalyzerData analyzerData) { + private void convertToArrayType(BXmlItem xmlItem, Field field, BMap mapValue, + BString bCurrentFieldName, ArrayType arrayType, XmlAnalyzerData analyzerData) { + Object temp = mapValue.get(bCurrentFieldName); + Type elementType = arrayType.getElementType(); + + if (DataUtils.isRegExpType(elementType)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNSUPPORTED_TYPE); + } + + String fieldName = field.getFieldName(); + int referredElementTypeTag = TypeUtils.getReferredType(elementType).getTag(); + if (referredElementTypeTag == TypeTags.JSON_TAG || referredElementTypeTag == TypeTags.ANYDATA_TAG) { + convertToRestType(xmlItem, arrayType, analyzerData); + return; + } + + if (temp == null) { + analyzerData.arrayIndexes.peek().put(fieldName, 0); + mapValue.put(bCurrentFieldName, DataUtils.createArrayValue(arrayType)); + } else { + HashMap indexes = analyzerData.arrayIndexes.peek(); + indexes.put(fieldName, indexes.get(fieldName) + 1); + } + + convertToArrayMemberType(xmlItem, fieldName, arrayType, elementType, mapValue, analyzerData); + } + + private void convertToArrayMemberType(BXmlItem xmlItem, String fieldName, ArrayType fieldType, Type elementType, + BMap mapValue, XmlAnalyzerData analyzerData) { + switch (elementType.getTag()) { + case TypeTags.RECORD_TYPE_TAG -> convertToRecordType(xmlItem, fieldType, fieldName, + (RecordType) elementType, mapValue, analyzerData); + case TypeTags.MAP_TAG -> + convertToMapType(xmlItem, fieldType, elementType, fieldName, mapValue, analyzerData); + case TypeTags.TYPE_REFERENCED_TYPE_TAG -> + convertToArrayMemberType(xmlItem, fieldName, fieldType, TypeUtils.getReferredType(elementType), + mapValue, analyzerData); + default -> traverseXml(xmlItem.getChildrenSeq(), fieldType, analyzerData); + } + } + + private void convertToRecordType(BXmlItem xmlItem, Type currentFieldType, String fieldName, + RecordType elementType, BMap mapValue, + XmlAnalyzerData analyzerData) { currentNode = updateNextRecord(xmlItem, elementType, fieldName, currentFieldType, mapValue, analyzerData); RecordType prevRecord = analyzerData.rootRecord; analyzerData.rootRecord = elementType; traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); - DataUtils.validateRequiredFields((BMap) currentNode, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); + DataUtils.validateRequiredFields(analyzerData); + DataUtils.popExpectedTypeStacks(analyzerData); analyzerData.rootRecord = prevRecord; currentNode = analyzerData.nodesStack.pop(); } + private void convertToMapType(BXmlItem xmlItem, Type fieldType, Type elementType, String fieldName, + BMap mapValue, XmlAnalyzerData analyzerData) { + updateNextMap(elementType, analyzerData); + currentNode = updateNextMappingValue(elementType, fieldName, fieldType, mapValue, analyzerData); + traverseXml(xmlItem.getChildrenSeq(), fieldType, analyzerData); + DataUtils.validateRequiredFields(analyzerData); + DataUtils.popExpectedTypeStacks(analyzerData); + currentNode = analyzerData.nodesStack.pop(); + } + private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { if (fieldType.getTag() == TypeTags.MAP_TAG) { analyzerData.restTypes.push(((MapType) fieldType).getConstrainedType()); @@ -262,6 +298,7 @@ private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { analyzerData.restTypes.push(fieldType); } analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); analyzerData.attributeHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); } @@ -270,20 +307,24 @@ private BMap updateNextRecord(BXmlItem xmlItem, RecordType reco XmlAnalyzerData analyzerData) { DataUtils.updateExpectedTypeStacks(recordType, analyzerData); BMap nextValue = - updateNextValue(recordType, fieldName, fieldType, currentMapValue, analyzerData); + updateNextMappingValue(recordType, fieldName, fieldType, currentMapValue, analyzerData); QName qName = xmlItem.getQName(); DataUtils.validateTypeNamespace(qName.getPrefix(), qName.getNamespaceURI(), recordType); handleAttributes(xmlItem, nextValue, analyzerData); return nextValue; } - private BMap updateNextValue(Type type, String fieldName, Type fieldType, - BMap currentMapValue, XmlAnalyzerData analyzerData) { + private BMap updateNextMappingValue(Type type, String fieldName, Type fieldType, + BMap currentMapValue, + XmlAnalyzerData analyzerData) { analyzerData.currentField = null; BMap nextValue; switch (type.getTag()) { - case TypeTags.RECORD_TYPE_TAG -> nextValue = ValueCreator.createRecordValue((RecordType) type); + case TypeTags.RECORD_TYPE_TAG -> { + RecordType recordType = (RecordType) type; + nextValue = ValueCreator.createRecordValue(recordType.getPackage(), recordType.getName()); + } case TypeTags.MAP_TAG -> nextValue = ValueCreator.createMapValue((MapType) type); case TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> nextValue = ValueCreator.createMapValue(TypeCreator.createMapType(type)); @@ -292,9 +333,10 @@ private BMap updateNextValue(Type type, String fieldName, Type Object temp = currentMapValue.get(StringUtils.fromString(fieldName)); if (temp instanceof BArray) { - int arraySize = ((ArrayType) fieldType).getSize(); - if (arraySize > ((BArray) temp).getLength() || arraySize == -1) { - ((BArray) temp).append(nextValue); + ArrayType arrayType = (ArrayType) fieldType; + int currentIndex = analyzerData.arrayIndexes.peek().get(fieldName); + if (arrayType.getState() == ArrayType.ArrayState.OPEN || currentIndex < arrayType.getSize()) { + ((BArray) temp).add(currentIndex, nextValue); } else { DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(analyzerData.allowDataProjection); } @@ -302,87 +344,71 @@ private BMap updateNextValue(Type type, String fieldName, Type currentMapValue.put(StringUtils.fromString(fieldName), nextValue); } analyzerData.nodesStack.push(currentMapValue); + analyzerData.arrayIndexes.push(new HashMap<>()); return nextValue; } @SuppressWarnings("unchecked") - private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerData analyzerData) { + private void convertToRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerData analyzerData) { String elemName = xmlItem.getQName().getLocalPart(); analyzerData.currentField = TypeCreator.createField(restType, elemName, SymbolFlags.PUBLIC); BMap mapValue = (BMap) currentNode; + checkRestTypeAndConvert(xmlItem, elemName, restType, restType, mapValue, analyzerData); + } - switch (restType.getTag()) { - case TypeTags.RECORD_TYPE_TAG -> { - handleRecordType(xmlItem, restType, elemName, (RecordType) restType, mapValue, analyzerData); - return; - } + private void checkRestTypeAndConvert(BXmlItem xmlItem, String elemName, Type restType, Type elementType, + BMap mapValue, XmlAnalyzerData analyzerData) { + switch (elementType.getTag()) { + case TypeTags.RECORD_TYPE_TAG -> + convertToRecordType(xmlItem, restType, elemName, (RecordType) elementType, mapValue, analyzerData); case TypeTags.ARRAY_TAG -> { + HashMap indexes = analyzerData.arrayIndexes.peek(); + if (!indexes.containsKey(elemName)) { + indexes.put(elemName, 0); + mapValue.put(StringUtils.fromString(elemName), DataUtils.createArrayValue(restType)); + } else { + indexes.put(elemName, indexes.get(elemName) + 1); + } + + checkRestTypeAndConvert(xmlItem, elemName, restType, ((ArrayType) restType).getElementType(), + mapValue, analyzerData); + } + default -> { BString bElementName = StringUtils.fromString(elemName); - if (!mapValue.containsKey(bElementName)) { - BArray array = DataUtils.createNewAnydataList(restType); - mapValue.put(bElementName, array); + if (mapValue.containsKey(bElementName) && mapValue.get(bElementName) != null) { + handleArrayValueForRestType(xmlItem, elemName, restType, mapValue, analyzerData); + return; } - Type elementType = ((ArrayType) restType).getElementType(); - if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { - handleRecordType(xmlItem, restType, elemName, (RecordType) elementType, mapValue, - analyzerData); + + if (isNextElementContent(xmlItem)) { + convertContentToRestType(xmlItem, bElementName, restType, mapValue, analyzerData); return; } + + if (restType.getTag() != TypeTags.ANYDATA_TAG && restType.getTag() != TypeTags.JSON_TAG) { + throw DiagnosticLog.error(DiagnosticErrorCode.EXPECTED_ANYDATA_OR_JSON); + } + + convertToJsonOrAnydataAsRestType(xmlItem, bElementName, restType, mapValue, analyzerData); } } + } + private void handleArrayValueForRestType(BXmlItem xmlItem, String elemName, Type restType, + BMap mapValue, XmlAnalyzerData analyzerData) { BString bElementName = StringUtils.fromString(elemName); + Object currentElement = mapValue.get(bElementName); BArray arrayValue; - if (mapValue.containsKey(bElementName)) { - Object currentElement = mapValue.get(bElementName); - if (!(currentElement instanceof BArray)) { - arrayValue = DataUtils.createNewAnydataList(restType); - arrayValue.append(currentElement); - mapValue.put(bElementName, arrayValue); - } else { - arrayValue = (BArray) currentElement; - } - - if (isNextElementContent(xmlItem)) { - if (isElementHasAttributes(xmlItem)) { - BMap nextValue = - ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); - handleAttributesRest(xmlItem, nextValue, restType); - arrayValue.append(nextValue); - - if (!nextValue.isEmpty()) { - analyzerData.currentField = - TypeCreator.createField(restType, analyzerData.textFieldName, SymbolFlags.REQUIRED); - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; - traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - currentNode = analyzerData.nodesStack.pop(); - return; - } - } - traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - return; - } - BMap nextValue = - ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); - arrayValue.append(nextValue); - analyzerData.nodesStack.push(currentNode); - currentNode = nextValue; - handleAttributesRest(xmlItem, nextValue, restType); - - analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(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; + if (!(currentElement instanceof BArray)) { + if (!DataUtils.isArrayValueAssignable(restType.getTag())) { + throw DiagnosticLog.error(DiagnosticErrorCode.FOUND_ARRAY_FOR_NON_ARRAY_TYPE, restType, + elemName); + } + arrayValue = DataUtils.createArrayValue(restType); + arrayValue.append(currentElement); + mapValue.put(bElementName, arrayValue); + } else { + arrayValue = (BArray) currentElement; } if (isNextElementContent(xmlItem)) { @@ -390,7 +416,7 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat BMap nextValue = ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); handleAttributesRest(xmlItem, nextValue, restType); - mapValue.put(bElementName, nextValue); + arrayValue.append(nextValue); if (!nextValue.isEmpty()) { analyzerData.currentField = @@ -404,9 +430,66 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat } traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); return; - } else if (restType.getTag() != TypeTags.ANYDATA_TAG && restType.getTag() != TypeTags.JSON_TAG) { - throw DiagnosticLog.error(DiagnosticErrorCode.EXPECTED_ANYDATA_OR_JSON); } + BMap nextValue = + ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); + if (DataUtils.isAnydataOrJson(restType.getTag())) { + arrayValue.append(nextValue); + } else { + ArrayType arrayType = (ArrayType) restType; + int currentIndex = analyzerData.arrayIndexes.peek().get(elemName); + if (arrayType.getState() == ArrayType.ArrayState.CLOSED + && arrayType.getSize() <= currentIndex) { + DataUtils.logArrayMismatchErrorIfProjectionNotAllowed(analyzerData.allowDataProjection); + } else { + arrayValue.add(currentIndex, nextValue); + } + } + analyzerData.nodesStack.push(currentNode); + currentNode = nextValue; + handleAttributesRest(xmlItem, nextValue, restType); + + analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.visitedFieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.arrayIndexes.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.visitedFieldHierarchy.pop(); + analyzerData.restTypes.pop(); + analyzerData.arrayIndexes.pop(); + currentNode = analyzerData.nodesStack.pop(); + } + + private void convertContentToRestType(BXmlItem xmlItem, BString bElementName, Type restType, + BMap mapValue, XmlAnalyzerData analyzerData) { + if (isElementHasAttributes(xmlItem)) { + BMap nextValue = + ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); + handleAttributesRest(xmlItem, nextValue, restType); + mapValue.put(bElementName, nextValue); + + if (!nextValue.isEmpty()) { + analyzerData.currentField = + TypeCreator.createField(restType, analyzerData.textFieldName, SymbolFlags.REQUIRED); + analyzerData.nodesStack.push(currentNode); + currentNode = nextValue; + traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); + currentNode = analyzerData.nodesStack.pop(); + return; + } + } + traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); + } + + private void convertToJsonOrAnydataAsRestType(BXmlItem xmlItem, BString bElementName, Type restType, + BMap mapValue, XmlAnalyzerData analyzerData) { BMap nextValue = ValueCreator.createMapValue(DataUtils.getMapTypeFromConstraintType(restType)); mapValue.put(bElementName, nextValue); @@ -524,6 +607,7 @@ private BXml validateRootElement(BXml xml, RecordType recordType, XmlAnalyzerDat // Keep track of fields and attributes DataUtils.updateExpectedTypeStacks(recordType, analyzerData); handleAttributes(xmlItem, (BMap) currentNode, analyzerData); + analyzerData.arrayIndexes.push(new HashMap<>()); return xmlItem.getChildrenSeq(); } @@ -545,7 +629,12 @@ private void handleAttributes(BXmlItem xmlItem, BMap currentNod // Element and Attribute have same name. Priority given to element. continue; } - field = analyzerData.fieldHierarchy.peek().get(attribute); + if (analyzerData.visitedFieldHierarchy.peek().contains(attribute)) { + field = analyzerData.visitedFieldHierarchy.peek().get(attribute); + } else { + field = analyzerData.fieldHierarchy.peek().remove(attribute); + analyzerData.visitedFieldHierarchy.peek().put(attribute, field); + } } if (field == null) {