From 6bd6bf7118f070d26cd090b0a389d490282e83af Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Wed, 24 Apr 2024 09:35:31 +0530 Subject: [PATCH 1/8] Fix the mistake in converting xml to open record --- .../lib/data/xmldata/xml/XmlParser.java | 19 ++++++++++++++++--- .../lib/data/xmldata/xml/XmlTraversal.java | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 4 deletions(-) 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 f095afc..e42e736 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 @@ -471,18 +471,31 @@ private void validateRequiredFields(XmlParserData xmlParserData) { private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader); + Map fieldMap = xmlParserData.fieldHierarchy.peek(); // Assume type of record field `name` is `string[]` and // relevant source position is `11` - for (QualifiedName key : xmlParserData.fieldHierarchy.peek().keySet()) { + Field currentField = null; + String localPartName = null; + for (QualifiedName key : fieldMap.keySet()) { if (key.equals(elemQName)) { elemQName = key; + currentField = fieldMap.get(key); + localPartName = currentField.getFieldName(); break; } + + if (key.getLocalPart().equals(elemQName.getLocalPart())) { + localPartName = key.getLocalPart(); + } } - Field currentField = xmlParserData.fieldHierarchy.peek().get(elemQName); + xmlParserData.currentField = currentField; - if (xmlParserData.currentField == null) { + if (currentField == null) { if (xmlParserData.restTypes.peek() != null) { + if (localPartName != null) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemQName.getLocalPart(), + xmlParserData.rootRecord); + } xmlParserData.currentNode = handleRestField(xmlParserData); } else if (!xmlParserData.allowDataProjection) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemQName.getLocalPart(), 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 28878d5..82d137c 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 @@ -162,12 +162,25 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { @SuppressWarnings("unchecked") private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { QualifiedName elementName = DataUtils.getElementName(xmlItem.getQName()); - Field currentField = analyzerData.fieldHierarchy.peek().get(elementName); + Map fieldMap = analyzerData.fieldHierarchy.peek(); + String localPartName = null; + for (QualifiedName key : fieldMap.keySet()) { + if (key.getLocalPart().equals(elementName.getLocalPart())) { + localPartName = key.getLocalPart(); + break; + } + } + + Field currentField = fieldMap.get(elementName); analyzerData.currentField = currentField; if (currentField == null) { Type restType = analyzerData.restTypes.peek(); if (restType != null) { + if (localPartName != null) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName.getLocalPart(), + analyzerData.rootRecord); + } convertWithRestType(xmlItem, restType, analyzerData); } else if (!analyzerData.allowDataProjection) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName.getLocalPart(), From e4de5ad29c2c3d7bba0cd4035cef10ddf4255f41 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:41:36 +0530 Subject: [PATCH 2/8] Refactor code to handle QName as key in map type --- .../lib/data/xmldata/utils/Constants.java | 1 + .../lib/data/xmldata/utils/DataUtils.java | 49 ++++-- .../lib/data/xmldata/xml/QualifiedName.java | 6 +- .../data/xmldata/xml/QualifiedNameMap.java | 137 ++++++++++++++++ .../lib/data/xmldata/xml/XmlParser.java | 148 ++++++++++-------- .../lib/data/xmldata/xml/XmlTraversal.java | 34 ++-- 6 files changed, 266 insertions(+), 109 deletions(-) create mode 100644 native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java 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 ace7fa3..2e3e477 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 @@ -59,4 +59,5 @@ private Constants() {} public static final BString TEXT_FIELD_NAME = StringUtils.fromString("textFieldName"); public static final BString ALLOW_DATA_PROJECTION = StringUtils.fromString("allowDataProjection"); public static final String NON_NUMERIC_STRING_REGEX = "[^a-zA-Z\\d\s]"; + public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$"; } 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 65ac6f7..dfe0d70 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 @@ -20,6 +20,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.PredefinedTypes; import io.ballerina.runtime.api.TypeTags; import io.ballerina.runtime.api.creators.TypeCreator; @@ -89,7 +90,7 @@ public static QualifiedName validateAndGetXmlNameFromRecordAnnotation(RecordType prefix == null ? "" : prefix.getValue()); } } - return new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, localName, ""); + return new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, localName, ""); } public static void validateTypeNamespace(String prefix, String uri, RecordType recordType) { @@ -134,19 +135,33 @@ public static Map getAllFieldsInRecordType(RecordType reco } } - Map fields = new HashMap<>(); + Map fieldMap = new HashMap<>(); + Map> fieldNames = new HashMap<>(); Map recordFields = recordType.getFields(); for (String key : recordFields.keySet()) { - QualifiedName modifiedQName = modifiedNames.getOrDefault(key, - new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, key, "")); - if (fields.containsKey(modifiedQName)) { - throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, modifiedQName.getLocalPart()); - } else if (analyzerData.attributeHierarchy.peek().containsKey(modifiedQName)) { - continue; + QualifiedName modifiedQName = + modifiedNames.getOrDefault(key, new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, key, "")); + String localName = modifiedQName.getLocalPart(); + if (fieldMap.containsKey(modifiedQName)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } else if (fieldNames.containsKey(localName)) { + if (modifiedQName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } + List qNames = fieldNames.get(localName); + qNames.forEach(qName -> { + if (qName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } + }); + fieldMap.put(modifiedQName, recordFields.get(key)); + fieldNames.get(localName).add(modifiedQName); + } else if (!analyzerData.attributeHierarchy.peek().contains(modifiedQName)) { + fieldMap.put(modifiedQName, recordFields.get(key)); + fieldNames.put(localName, new ArrayList<>(List.of(modifiedQName))); } - fields.put(modifiedQName, recordFields.get(key)); } - return fields; + return fieldMap; } @SuppressWarnings("unchecked") @@ -177,7 +192,7 @@ public static QualifiedName getFieldNameFromRecord(Map fieldAnn prefix == null ? "" : prefix.getValue()); } } - return new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, fieldName, ""); + return new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, fieldName, ""); } @SuppressWarnings("unchecked") @@ -216,7 +231,7 @@ public static Object convertStringToExpType(BString value, Type expType) { } public static void validateRequiredFields(BMap currentMapValue, XmlAnalyzerData analyzerData) { - Map fields = analyzerData.fieldHierarchy.peek(); + Map fields = analyzerData.fieldHierarchy.peek().getMembers(); for (QualifiedName key : fields.keySet()) { // Validate required array size Field field = fields.get(key); @@ -236,7 +251,7 @@ public static void validateRequiredFields(BMap currentMapValue, } } - Map attributes = analyzerData.attributeHierarchy.peek(); + Map attributes = analyzerData.attributeHierarchy.peek().getMembers(); for (QualifiedName key : attributes.keySet()) { Field field = attributes.get(key); String fieldName = field.getFieldName(); @@ -289,8 +304,8 @@ public static MapType getMapTypeFromConstraintType(Type constraintType) { } public static void updateExpectedTypeStacks(RecordType recordType, XmlAnalyzerData analyzerData) { - analyzerData.attributeHierarchy.push(new HashMap<>(getAllAttributesInRecordType(recordType))); - analyzerData.fieldHierarchy.push(new HashMap<>(getAllFieldsInRecordType(recordType, analyzerData))); + analyzerData.attributeHierarchy.push(new QualifiedNameMap(getAllAttributesInRecordType(recordType))); + analyzerData.fieldHierarchy.push(new QualifiedNameMap(getAllFieldsInRecordType(recordType, analyzerData))); analyzerData.restTypes.push(recordType.getRestFieldType()); } @@ -774,8 +789,8 @@ private static String addAttributeToRecord(BString prefix, BString uri, String k */ public static class XmlAnalyzerData { public final Stack nodesStack = new Stack<>(); - public final Stack> fieldHierarchy = new Stack<>(); - public final Stack> attributeHierarchy = new Stack<>(); + public final Stack> fieldHierarchy = new Stack<>(); + public final Stack> attributeHierarchy = new Stack<>(); public final Stack restTypes = new Stack<>(); public RecordType rootRecord; public Field currentField; diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedName.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedName.java index edabac0..77e0dc9 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedName.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedName.java @@ -24,7 +24,6 @@ * @since 0.1.0 */ public class QualifiedName { - public static final String NS_ANNOT_NOT_DEFINED = "$$ns_annot_not_defined$$"; private String localPart; private String namespaceURI; private String prefix; @@ -59,7 +58,7 @@ public String getPrefix() { @Override public int hashCode() { - return localPart.hashCode(); + return prefix.hashCode() ^ namespaceURI.hashCode() ^ localPart.hashCode(); } @Override @@ -72,9 +71,6 @@ public boolean equals(Object objectToTest) { return false; } - if (qName.namespaceURI.equals(NS_ANNOT_NOT_DEFINED) || namespaceURI.equals(NS_ANNOT_NOT_DEFINED)) { - return localPart.equals(qName.localPart); - } return localPart.equals(qName.localPart) && namespaceURI.equals(qName.namespaceURI) && prefix.equals(qName.prefix); } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java new file mode 100644 index 0000000..ed0665f --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java @@ -0,0 +1,137 @@ +package io.ballerina.lib.data.xmldata.xml; + +import io.ballerina.lib.data.xmldata.utils.Constants; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class QualifiedNameMap { + private final Map members; + private final Map> stringToQNameMap; + + public QualifiedNameMap(Map fields) { + this.members = fields; + this.stringToQNameMap = getStringToQNamesMap(fields.keySet()); + } + + public Map getMembers() { + return members; + } + + public V removeElement(QualifiedName qName) { + V field = members.remove(qName); + if (field == null) { + Map> fields = stringToQNameMap; + String localName = qName.getLocalPart(); + if (!fields.containsKey(localName)) { + return null; + } + for (QualifiedName qualifiedName : fields.get(localName)) { + if (qualifiedName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + field = this.members.remove(qualifiedName); + break; + } + } + } + return field; + } + + public boolean contains(QualifiedName qName) { + if (members.containsKey(qName)) { + return true; + } + + String localName = qName.getLocalPart(); + if (!stringToQNameMap.containsKey(localName)) { + return false; + } + for (QualifiedName qualifiedName : stringToQNameMap.get(localName)) { + if (qualifiedName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + return true; + } + } + return false; + } + + public boolean contains(String localName) { + return stringToQNameMap.containsKey(localName); + } + + public void put(QualifiedName qName, V value) { + members.put(qName, value); + String localName = qName.getLocalPart(); + if (stringToQNameMap.containsKey(localName)) { + stringToQNameMap.get(localName).add(qName); + } else { + List qNames = new ArrayList<>(); + qNames.add(qName); + stringToQNameMap.put(localName, qNames); + } + } + + public V get(QualifiedName qName) { + if (members.containsKey(qName)) { + return members.get(qName); + } + + String localName = qName.getLocalPart(); + if (!stringToQNameMap.containsKey(localName)) { + return null; + } + for (QualifiedName qualifiedName : stringToQNameMap.get(localName)) { + if (qualifiedName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + return members.get(qualifiedName); + } + } + return null; + } + + public V getOrDefault(QualifiedName qName, V defaultValue) { + V value = get(qName); + return value != null ? value : defaultValue; + } + + public boolean isEmpty() { + return members.isEmpty(); + } + + public QualifiedName getMatchedQualifiedName(QualifiedName elementQName) { + if (members.containsKey(elementQName)) { + return elementQName; + } + + String localName = elementQName.getLocalPart(); + if (!stringToQNameMap.containsKey(localName)) { + return null; + } + for (QualifiedName qualifiedName : stringToQNameMap.get(localName)) { + if (qualifiedName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + return qualifiedName; + } + } + return null; + } + + public void clear() { + members.clear(); + stringToQNameMap.clear(); + } + + private Map> getStringToQNamesMap(Set fieldQNames) { + Map> fields = new HashMap<>(); + for (QualifiedName qName : fieldQNames) { + String localName = qName.getLocalPart(); + if (fields.containsKey(localName)) { + fields.get(localName).add(qName); + } else { + List qNames = new ArrayList<>(); + qNames.add(qName); + fields.put(localName, qNames); + } + } + return fields; + } +} 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 e42e736..d2a6530 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 @@ -41,8 +41,10 @@ import io.ballerina.runtime.api.values.BString; import java.io.Reader; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Stack; @@ -255,15 +257,14 @@ private void readText(XMLStreamReader xmlStreamReader, String textFieldName = xmlParserData.textFieldName; if (currentField == null) { QualifiedName contentQName = new QualifiedName("", textFieldName, ""); - Map currentFieldMap = xmlParserData.fieldHierarchy.peek(); - if (!currentFieldMap.containsKey(contentQName)) { + if (!xmlParserData.fieldHierarchy.peek().contains(contentQName)) { if (!xmlParserData.allowDataProjection) { throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, textFieldName, xmlParserData.rootRecord); } return; } else { - currentField = currentFieldMap.remove(contentQName); + currentField = xmlParserData.fieldHierarchy.peek().removeElement(contentQName); } } @@ -424,12 +425,12 @@ private void buildDocument(XmlParserData xmlParserData) { private void endElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { xmlParserData.currentField = null; QualifiedName elemQName = getElementName(xmlStreamReader); - LinkedHashMap siblings = xmlParserData.siblings; - Stack> parents = xmlParserData.parents; - if (siblings.containsKey(elemQName) && !siblings.get(elemQName)) { + QualifiedNameMap siblings = xmlParserData.siblings; + Stack> parents = xmlParserData.parents; + if (siblings.contains(elemQName) && !siblings.get(elemQName)) { siblings.put(elemQName, true); } - if (parents.isEmpty() || !parents.peek().containsKey(elemQName)) { + if (parents.isEmpty() || !parents.peek().contains(elemQName)) { return; } @@ -440,7 +441,7 @@ private void endElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParser private void validateRequiredFields(XmlParserData xmlParserData) { BMap currentMapValue = xmlParserData.currentNode; - Map fields = xmlParserData.fieldHierarchy.peek(); + Map fields = xmlParserData.fieldHierarchy.peek().getMembers(); for (QualifiedName key : fields.keySet()) { // Validate required array size Field field = fields.get(key); @@ -460,7 +461,7 @@ private void validateRequiredFields(XmlParserData xmlParserData) { } } - Map attributes = xmlParserData.attributeHierarchy.peek(); + Map attributes = xmlParserData.attributeHierarchy.peek().getMembers(); for (QualifiedName key : attributes.keySet()) { Field field = attributes.get(key); if (!SymbolFlags.isFlagOn(field.getFlags(), SymbolFlags.OPTIONAL)) { @@ -471,45 +472,31 @@ private void validateRequiredFields(XmlParserData xmlParserData) { private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader); - Map fieldMap = xmlParserData.fieldHierarchy.peek(); - // Assume type of record field `name` is `string[]` and - // relevant source position is `11` - Field currentField = null; - String localPartName = null; - for (QualifiedName key : fieldMap.keySet()) { - if (key.equals(elemQName)) { - elemQName = key; - currentField = fieldMap.get(key); - localPartName = currentField.getFieldName(); - break; - } - - if (key.getLocalPart().equals(elemQName.getLocalPart())) { - localPartName = key.getLocalPart(); - } - } - + QualifiedNameMap fieldMap = xmlParserData.fieldHierarchy.peek(); + Field currentField = fieldMap.get(elemQName); xmlParserData.currentField = currentField; if (currentField == null) { + String elemName = elemQName.getLocalPart(); if (xmlParserData.restTypes.peek() != null) { - if (localPartName != null) { - throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemQName.getLocalPart(), + if (fieldMap.contains(elemName)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemName, xmlParserData.rootRecord); } xmlParserData.currentNode = handleRestField(xmlParserData); } else if (!xmlParserData.allowDataProjection) { - throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemQName.getLocalPart(), + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elemName, xmlParserData.rootRecord); } return; } + elemQName = fieldMap.getMatchedQualifiedName(elemQName); 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()); - if (!xmlParserData.siblings.containsKey(elemQName)) { + 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)) { @@ -531,7 +518,7 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse updateNextArrayMember(xmlStreamReader, xmlParserData, fieldName, fieldType, referredType); } case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> - intializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); + initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); } } @@ -541,13 +528,14 @@ private void updateNextArrayMember(XMLStreamReader xmlStreamReader, XmlParserDat case TypeTags.RECORD_TYPE_TAG -> updateNextRecord(xmlStreamReader, xmlParserData, fieldName, fieldType, (RecordType) type); case TypeTags.MAP_TAG, TypeTags.ANYDATA_TAG, TypeTags.JSON_TAG -> - intializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); + initializeAttributesForNextMappingValue(xmlParserData, fieldName, fieldType); } } - private void intializeAttributesForNextMappingValue(XmlParserData xmlParserData, String fieldName, Type fieldType) { + private void initializeAttributesForNextMappingValue(XmlParserData xmlParserData, String fieldName, + Type fieldType) { xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); BMap nextMapValue = updateNextMappingValue(xmlParserData, fieldName, fieldType); handleAttributesRest(xmlStreamReader, fieldType, nextMapValue); xmlParserData.currentNode = nextMapValue; @@ -569,8 +557,8 @@ private BMap updateNextMappingValue(XmlParserData xmlParserData xmlParserData.restTypes.push(type); } - xmlParserData.attributeHierarchy.push(new HashMap<>()); - xmlParserData.fieldHierarchy.push(new HashMap<>()); + xmlParserData.attributeHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.recordTypeStack.push(null); BMap currentNode = xmlParserData.currentNode; Object temp = currentNode.get(StringUtils.fromString(fieldName)); @@ -596,7 +584,7 @@ private int getArraySize(Type type, Object temp) { private void updateNextRecord(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData, String fieldName, Type fieldType, RecordType recordType) { xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); xmlParserData.recordTypeStack.push(xmlParserData.rootRecord); xmlParserData.rootRecord = recordType; xmlParserData.currentNode = updateNextValue(recordType, fieldName, fieldType, xmlParserData); @@ -626,8 +614,8 @@ private BMap updateNextValue(RecordType rootRecord, String fiel } private void updateExpectedTypeStacks(RecordType recordType, XmlParserData xmlParserData) { - xmlParserData.attributeHierarchy.push(new HashMap<>(getAllAttributesInRecordType(recordType))); - xmlParserData.fieldHierarchy.push(new HashMap<>(getAllFieldsInRecordType(recordType, xmlParserData))); + xmlParserData.attributeHierarchy.push(new QualifiedNameMap<>(getAllAttributesInRecordType(recordType))); + xmlParserData.fieldHierarchy.push(new QualifiedNameMap<>(getAllFieldsInRecordType(recordType, xmlParserData))); xmlParserData.restTypes.push(recordType.getRestFieldType()); } @@ -642,7 +630,7 @@ private void popStacks(XmlParserData xmlParserData) { @SuppressWarnings("unchecked") private BMap handleRestField(XmlParserData xmlParserData) { QualifiedName restStartPoint = xmlParserData.parents.isEmpty() ? - xmlParserData.rootElement : getLastElementInSiblings(xmlParserData.parents.peek()); + xmlParserData.rootElement : getLastElementInSiblings(xmlParserData.parents.peek().getMembers()); xmlParserData.restFieldsPoints.push(restStartPoint); xmlParserData.nodesStack.push(xmlParserData.currentNode); return (BMap) parseRestField(xmlParserData); @@ -696,7 +684,7 @@ private void updateExpectedTypeStacksOfRestType(Type restType, XmlParserData xml 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.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); xmlParserData.restTypes.push(restType); } } @@ -705,13 +693,13 @@ private void updateExpectedTypeStacksOfRestType(Type restType, XmlParserData xml private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader); BString currentFieldName = StringUtils.fromString(elemQName.getLocalPart()); - QualifiedName lastElement = getLastElementInSiblings(xmlParserData.siblings); + QualifiedName lastElement = getLastElementInSiblings(xmlParserData.siblings.getMembers()); Type restType = TypeUtils.getReferredType(xmlParserData.restTypes.peek()); if (!xmlParserData.siblings.isEmpty() && lastElement != null && !xmlParserData.siblings.getOrDefault(lastElement, true)) { xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); updateExpectedTypeStacksOfRestType(restType, xmlParserData); xmlParserData.siblings.put(elemQName, false); BMap next = @@ -734,7 +722,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x return currentFieldName; } - if (!xmlParserData.siblings.containsKey(elemQName)) { + if (!xmlParserData.siblings.contains(elemQName)) { xmlParserData.siblings.put(elemQName, false); if (restType.getTag() == TypeTags.ARRAY_TAG) { BArray tempArray = ValueCreator.createArrayValue(DataUtils.getArrayTypeFromElementType(restType)); @@ -755,7 +743,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x if (elemTypeTag == TypeTags.ANYDATA_TAG || elemTypeTag == TypeTags.JSON_TAG) { xmlParserData.nodesStack.add(xmlParserData.currentNode); xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); updateExpectedTypeStacksOfRestType(restType, xmlParserData); xmlParserData.currentNode = updateNextArrayMemberForRestType((BArray) currentElement, restType); } @@ -774,7 +762,7 @@ private BString readElementRest(XMLStreamReader xmlStreamReader, XmlParserData x if (elemTypeTag == TypeTags.ANYDATA_TAG || elemTypeTag == TypeTags.JSON_TAG) { xmlParserData.nodesStack.add(xmlParserData.currentNode); xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); updateExpectedTypeStacksOfRestType(restType, xmlParserData); xmlParserData.currentNode = updateNextArrayMemberForRestType(tempArray, restType); } @@ -791,24 +779,37 @@ private BMap updateNextArrayMemberForRestType(BArray tempArray, @SuppressWarnings("unchecked") private void endElementRest(XMLStreamReader xmlStreamReader, XmlParserData xmlParserData) { QualifiedName elemQName = getElementName(xmlStreamReader); - if (xmlParserData.siblings.containsKey(elemQName) && !xmlParserData.siblings.get(elemQName)) { + if (xmlParserData.siblings.contains(elemQName) && !xmlParserData.siblings.get(elemQName)) { xmlParserData.siblings.put(elemQName, true); } - if (xmlParserData.parents.isEmpty() || !xmlParserData.parents.peek().containsKey(elemQName)) { + if (xmlParserData.parents.isEmpty() || !xmlParserData.parents.peek().contains(elemQName)) { return; } xmlParserData.currentNode = (BMap) xmlParserData.nodesStack.pop(); xmlParserData.siblings = xmlParserData.parents.pop(); - if (xmlParserData.siblings.containsKey(elemQName)) { + if (xmlParserData.siblings.contains(elemQName)) { + // TODO: This is duplicated in several places. Remove the duplication. xmlParserData.fieldHierarchy.pop(); xmlParserData.restTypes.pop(); } - xmlParserData.restFieldsPoints.remove(elemQName); + removeQNameFromRestFieldsPoints(elemQName, xmlParserData); xmlParserData.siblings.put(elemQName, true); } + private void removeQNameFromRestFieldsPoints(QualifiedName elemQName, XmlParserData xmlParserData) { + Stack restFieldsPoints = xmlParserData.restFieldsPoints; + if (restFieldsPoints.contains(elemQName)) { + restFieldsPoints.remove(elemQName); + } else { + restFieldsPoints.stream().filter( + qName -> qName.getLocalPart().equals(elemQName.getLocalPart()) + && qName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)).findFirst().ifPresent( + restFieldsPoints::remove); + } + } + @SuppressWarnings("unchecked") private void readTextRest(XMLStreamReader xmlStreamReader, BString currentFieldName, @@ -862,7 +863,7 @@ private void convertTextRestAndUpdateCurrentNodeForRestType(BMap siblings) { + private QualifiedName getLastElementInSiblings(Map siblings) { Object[] arr = siblings.keySet().toArray(); QualifiedName lastElement = null; if (arr.length != 0) { @@ -887,18 +888,33 @@ private Map getAllFieldsInRecordType(RecordType recordType } } - Map fields = new HashMap<>(); + Map fieldMap = new HashMap<>(); + Map> fieldNames = new HashMap<>(); Map recordFields = recordType.getFields(); for (String key : recordFields.keySet()) { QualifiedName modifiedQName = - modifiedNames.getOrDefault(key, new QualifiedName(QualifiedName.NS_ANNOT_NOT_DEFINED, key, "")); - if (fields.containsKey(modifiedQName)) { - throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, modifiedQName.getLocalPart()); - } else if (!xmlParserData.attributeHierarchy.peek().containsKey(modifiedQName)) { - fields.put(modifiedQName, recordFields.get(key)); + modifiedNames.getOrDefault(key, new QualifiedName(Constants.NS_ANNOT_NOT_DEFINED, key, "")); + String localName = modifiedQName.getLocalPart(); + if (fieldMap.containsKey(modifiedQName)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } else if (fieldNames.containsKey(localName)) { + if (modifiedQName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } + List qNames = fieldNames.get(localName); + qNames.forEach(qName -> { + if (qName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { + throw DiagnosticLog.error(DiagnosticErrorCode.DUPLICATE_FIELD, localName); + } + }); + fieldMap.put(modifiedQName, recordFields.get(key)); + fieldNames.get(localName).add(modifiedQName); + } else if (!xmlParserData.attributeHierarchy.peek().contains(modifiedQName)) { + fieldMap.put(modifiedQName, recordFields.get(key)); + fieldNames.put(localName, new ArrayList<>(List.of(modifiedQName))); } } - return fields; + return fieldMap; } @SuppressWarnings("unchecked") @@ -933,7 +949,7 @@ private void handleAttributes(XMLStreamReader xmlStreamReader, XmlParserData xml QName attributeQName = xmlStreamReader.getAttributeName(i); QualifiedName attQName = new QualifiedName(attributeQName.getNamespaceURI(), xmlParserData.attributePrefix + attributeQName.getLocalPart(), attributeQName.getPrefix()); - Field field = xmlParserData.attributeHierarchy.peek().remove(attQName); + Field field = xmlParserData.attributeHierarchy.peek().removeElement(attQName); if (field == null) { Optional f = getFieldFromFieldHierarchy(attQName, xmlParserData); if (f.isEmpty()) { @@ -1017,12 +1033,12 @@ private Optional handleRecordRestType(XmlParserData xmlParserData, XMLSt } private void updateStacksWhenRecordAsRestType(QualifiedName elementQName, XmlParserData xmlParserData) { - if (!xmlParserData.siblings.containsKey(elementQName)) { + if (!xmlParserData.siblings.contains(elementQName)) { xmlParserData.siblings.put(elementQName, false); } xmlParserData.restFieldsPoints.push(Constants.EXIT_REST_POINT); xmlParserData.parents.push(xmlParserData.siblings); - xmlParserData.siblings = new LinkedHashMap<>(); + xmlParserData.siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); } private QualifiedName getElementName(XMLStreamReader xmlStreamReader) { @@ -1047,16 +1063,16 @@ static class TextValue { */ public static class XmlParserData { private final Stack nodesStack = new Stack<>(); - private final Stack> fieldHierarchy = new Stack<>(); - private final Stack> attributeHierarchy = new Stack<>(); + private final Stack> fieldHierarchy = 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<>(); private RecordType rootRecord; private Field currentField; private QualifiedName rootElement; - private final Stack> parents = new Stack<>(); - private LinkedHashMap siblings = new LinkedHashMap<>(); + private final Stack> parents = new Stack<>(); + private QualifiedNameMap siblings = new QualifiedNameMap<>(new LinkedHashMap<>()); private BMap currentNode; private String attributePrefix; private String textFieldName; 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 82d137c..8d4cdf7 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 @@ -113,9 +113,8 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { String textFieldName = analyzerData.textFieldName; if (currentField == null) { QualifiedName contentQName = new QualifiedName("", textFieldName, ""); - Map currentFieldMap = analyzerData.fieldHierarchy.peek(); - if (currentFieldMap.containsKey(contentQName)) { - currentField = currentFieldMap.remove(contentQName); + if (analyzerData.fieldHierarchy.peek().contains(contentQName)) { + currentField = analyzerData.fieldHierarchy.peek().removeElement(contentQName); } else if (analyzerData.restTypes.peek() != null) { currentField = TypeCreator.createField(analyzerData.restTypes.peek(), analyzerData.textFieldName, SymbolFlags.REQUIRED); @@ -161,29 +160,22 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { @SuppressWarnings("unchecked") private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { - QualifiedName elementName = DataUtils.getElementName(xmlItem.getQName()); - Map fieldMap = analyzerData.fieldHierarchy.peek(); - String localPartName = null; - for (QualifiedName key : fieldMap.keySet()) { - if (key.getLocalPart().equals(elementName.getLocalPart())) { - localPartName = key.getLocalPart(); - break; - } - } - - Field currentField = fieldMap.get(elementName); + QualifiedName elementQName = DataUtils.getElementName(xmlItem.getQName()); + QualifiedNameMap fieldsMap = analyzerData.fieldHierarchy.peek(); + Field currentField = fieldsMap.get(elementQName); analyzerData.currentField = currentField; if (currentField == null) { Type restType = analyzerData.restTypes.peek(); + String elementName = elementQName.getLocalPart(); if (restType != null) { - if (localPartName != null) { - throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName.getLocalPart(), + if (fieldsMap.contains(elementName)) { + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName, analyzerData.rootRecord); } convertWithRestType(xmlItem, restType, analyzerData); } else if (!analyzerData.allowDataProjection) { - throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName.getLocalPart(), + throw DiagnosticLog.error(DiagnosticErrorCode.UNDEFINED_FIELD, elementName, analyzerData.rootRecord); } return; @@ -264,8 +256,8 @@ private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { } else { analyzerData.restTypes.push(fieldType); } - analyzerData.fieldHierarchy.push(new HashMap<>()); - analyzerData.attributeHierarchy.push(new HashMap<>()); + analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); + analyzerData.attributeHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); } private BMap updateNextRecord(BXmlItem xmlItem, RecordType recordType, String fieldName, @@ -382,7 +374,7 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat currentNode = nextValue; handleAttributesRest(xmlItem, nextValue, restType); - analyzerData.fieldHierarchy.push(new HashMap<>()); + analyzerData.fieldHierarchy.push(new QualifiedNameMap<>(new HashMap<>())); if (restType.getTag() == TypeTags.ARRAY_TAG) { Type memberType = ((ArrayType) restType).getElementType(); analyzerData.restTypes.push(memberType); @@ -551,7 +543,7 @@ private void handleAttributes(BXmlItem xmlItem, BMap currentNod BString key = entry.getKey(); QualifiedName attribute = getAttributePreservingNamespace(nsPrefixMap, key.getValue(), analyzerData.attributePrefix); - Field field = analyzerData.attributeHierarchy.peek().remove(attribute); + Field field = analyzerData.attributeHierarchy.peek().removeElement(attribute); if (field == null) { if (innerElements.contains(attribute.getLocalPart())) { // Element and Attribute have same name. Priority given to element. From 7cafb0a28612c36cb17492c5c72a4f58c642a883 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Apr 2024 09:35:22 +0530 Subject: [PATCH 3/8] Add negative test --- ballerina/tests/fromXml_test.bal | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index ea09143..52ca71f 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -3144,3 +3144,47 @@ function testUnsupportedTypeNegative() { |}|error err3 = parseAsType(xmlVal2); test:assertEquals((err3).message(), "unsupported input type"); } + +@Namespace { + uri: "http://example.com/book" +} +type OpenBook record { + @Namespace { + uri: "http://example.com" + } + int id; + @Namespace { + uri: "http://example.com/book" + } + string title; + @Namespace { + uri: "http://example.com/book" + } + string author; +}; + +@test:Config +function testInvalidNamespaceInOpenRecordForParseString() { + string xmldata = string ` + + 601970 + string + string + `; + OpenBook|Error err = parseString(xmldata); + test:assertTrue(err is error); + test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook'"); +} + +@test:Config +function testInvalidNamespaceInOpenRecordForParseAsType() { + xml xmldata = xml ` + + 601970 + string + string + `; + OpenBook|Error err = parseAsType(xmldata); + test:assertTrue(err is error); + test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook'"); +} From 6ac4bb199aff59fc575478e2f5e4ba3570bbb48e Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Fri, 26 Apr 2024 09:46:29 +0530 Subject: [PATCH 4/8] refactor method name --- .../io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java | 2 +- .../java/io/ballerina/lib/data/xmldata/xml/XmlParser.java | 4 ++-- .../java/io/ballerina/lib/data/xmldata/xml/XmlTraversal.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java index ed0665f..4fbc856 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java @@ -21,7 +21,7 @@ public Map getMembers() { return members; } - public V removeElement(QualifiedName qName) { + public V remove(QualifiedName qName) { V field = members.remove(qName); if (field == null) { Map> fields = stringToQNameMap; 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 d2a6530..4c16820 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 @@ -264,7 +264,7 @@ private void readText(XMLStreamReader xmlStreamReader, } return; } else { - currentField = xmlParserData.fieldHierarchy.peek().removeElement(contentQName); + currentField = xmlParserData.fieldHierarchy.peek().remove(contentQName); } } @@ -949,7 +949,7 @@ private void handleAttributes(XMLStreamReader xmlStreamReader, XmlParserData xml QName attributeQName = xmlStreamReader.getAttributeName(i); QualifiedName attQName = new QualifiedName(attributeQName.getNamespaceURI(), xmlParserData.attributePrefix + attributeQName.getLocalPart(), attributeQName.getPrefix()); - Field field = xmlParserData.attributeHierarchy.peek().removeElement(attQName); + Field field = xmlParserData.attributeHierarchy.peek().remove(attQName); if (field == null) { Optional f = getFieldFromFieldHierarchy(attQName, xmlParserData); if (f.isEmpty()) { 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 8d4cdf7..db0680a 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 @@ -114,7 +114,7 @@ private void convertText(String text, XmlAnalyzerData analyzerData) { if (currentField == null) { QualifiedName contentQName = new QualifiedName("", textFieldName, ""); if (analyzerData.fieldHierarchy.peek().contains(contentQName)) { - currentField = analyzerData.fieldHierarchy.peek().removeElement(contentQName); + currentField = analyzerData.fieldHierarchy.peek().remove(contentQName); } else if (analyzerData.restTypes.peek() != null) { currentField = TypeCreator.createField(analyzerData.restTypes.peek(), analyzerData.textFieldName, SymbolFlags.REQUIRED); @@ -543,7 +543,7 @@ private void handleAttributes(BXmlItem xmlItem, BMap currentNod BString key = entry.getKey(); QualifiedName attribute = getAttributePreservingNamespace(nsPrefixMap, key.getValue(), analyzerData.attributePrefix); - Field field = analyzerData.attributeHierarchy.peek().removeElement(attribute); + Field field = analyzerData.attributeHierarchy.peek().remove(attribute); if (field == null) { if (innerElements.contains(attribute.getLocalPart())) { // Element and Attribute have same name. Priority given to element. From f23ac444e9abff006d3019851693913a20350e47 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 7 May 2024 15:32:22 +0530 Subject: [PATCH 5/8] Fix mistake in updating rootRecord --- .../lib/data/xmldata/xml/XmlTraversal.java | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) 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 db0680a..0e7d500 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 @@ -191,12 +191,8 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { BString bCurrentFieldName = StringUtils.fromString(fieldName); switch (currentFieldType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> { - currentNode = updateNextRecord(xmlItem, (RecordType) currentFieldType, fieldName, - currentFieldType, mapValue, analyzerData); - traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); - DataUtils.validateRequiredFields((BMap) currentNode, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); - currentNode = analyzerData.nodesStack.pop(); + handleRecordType(xmlItem, currentFieldType, fieldName, (RecordType) currentFieldType, mapValue, + analyzerData); return; } case TypeTags.ARRAY_TAG -> { @@ -207,12 +203,8 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { Type elementType = TypeUtils.getReferredType(((ArrayType) currentFieldType).getElementType()); int elementTypeTag = elementType.getTag(); if (elementTypeTag == TypeTags.RECORD_TYPE_TAG) { - currentNode = updateNextRecord(xmlItem, (RecordType) elementType, fieldName, - currentFieldType, mapValue, analyzerData); - traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); - DataUtils.validateRequiredFields((BMap) currentNode, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); - currentNode = analyzerData.nodesStack.pop(); + handleRecordType(xmlItem, currentFieldType, fieldName, (RecordType) elementType, mapValue, + analyzerData); return; } else if (elementTypeTag == TypeTags.MAP_TAG) { updateNextMap(elementType, analyzerData); @@ -250,6 +242,19 @@ private void convertElement(BXmlItem xmlItem, XmlAnalyzerData analyzerData) { traverseXml(xmlItem.getChildrenSeq(), currentFieldType, analyzerData); } + private void handleRecordType(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); + analyzerData.rootRecord = prevRecord; + currentNode = analyzerData.nodesStack.pop(); + } + private void updateNextMap(Type fieldType, XmlAnalyzerData analyzerData) { if (fieldType.getTag() == TypeTags.MAP_TAG) { analyzerData.restTypes.push(((MapType) fieldType).getConstrainedType()); @@ -308,12 +313,7 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat switch (restType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> { - currentNode = updateNextRecord(xmlItem, (RecordType) restType, elemName, restType, mapValue, - analyzerData); - traverseXml(xmlItem.getChildrenSeq(), restType, analyzerData); - DataUtils.validateRequiredFields((BMap) currentNode, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); - currentNode = analyzerData.nodesStack.pop(); + handleRecordType(xmlItem, restType, elemName, (RecordType) restType, mapValue, analyzerData); return; } case TypeTags.ARRAY_TAG -> { @@ -324,12 +324,8 @@ private void convertWithRestType(BXmlItem xmlItem, Type restType, XmlAnalyzerDat } Type elementType = ((ArrayType) restType).getElementType(); if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { - currentNode = updateNextRecord(xmlItem, (RecordType) elementType, elemName, - restType, mapValue, analyzerData); - traverseXml(xmlItem.getChildrenSeq(), elementType, analyzerData); - DataUtils.validateRequiredFields((BMap) currentNode, analyzerData); - DataUtils.removeExpectedTypeStacks(analyzerData); - currentNode = analyzerData.nodesStack.pop(); + handleRecordType(xmlItem, restType, elemName, (RecordType) elementType, mapValue, + analyzerData); return; } } From 326535a04ef14987a7d01c3b9984cbda49082646 Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Tue, 7 May 2024 15:34:06 +0530 Subject: [PATCH 6/8] Add test with open record with nested record --- ballerina/tests/fromXml_test.bal | 66 ++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/ballerina/tests/fromXml_test.bal b/ballerina/tests/fromXml_test.bal index 52ca71f..1163c0e 100644 --- a/ballerina/tests/fromXml_test.bal +++ b/ballerina/tests/fromXml_test.bal @@ -3148,7 +3148,7 @@ function testUnsupportedTypeNegative() { @Namespace { uri: "http://example.com/book" } -type OpenBook record { +type OpenBook1 record { @Namespace { uri: "http://example.com" } @@ -3164,27 +3164,79 @@ type OpenBook record { }; @test:Config -function testInvalidNamespaceInOpenRecordForParseString() { +function testInvalidNamespaceInOpenRecordForParseString1() { string xmldata = string ` 601970 string string `; - OpenBook|Error err = parseString(xmldata); + OpenBook1|Error err = parseString(xmldata); test:assertTrue(err is error); - test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook'"); + test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook1'"); } @test:Config -function testInvalidNamespaceInOpenRecordForParseAsType() { +function testInvalidNamespaceInOpenRecordForParseAsType1() { xml xmldata = xml ` 601970 string string `; - OpenBook|Error err = parseAsType(xmldata); + OpenBook1|Error err = parseAsType(xmldata); test:assertTrue(err is error); - test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook'"); + test:assertEquals((err).message(), "undefined field 'id' in record 'data.xmldata:OpenBook1'"); +} + +@Namespace { + uri: "http://example.com/book" +} +type OpenBook2 record { + AuthorOpen author; + @Namespace { + uri: "http://example.com/book" + } + string title; +}; + +type AuthorOpen record { + @Namespace { + uri: "http://example.com" + } + string name; + @Namespace { + uri: "http://example.com/book" + } + int age; +}; + +@test:Config +function testInvalidNamespaceInOpenRecordForParseString2() { + string xmldata = string ` + + + R.C Martin + 60 + + Clean Code + `; + OpenBook2|Error err = parseString(xmldata); + test:assertTrue(err is error); + test:assertEquals((err).message(), "undefined field 'name' in record 'data.xmldata:AuthorOpen'"); +} + +@test:Config +function testInvalidNamespaceInOpenRecordForParseAsType2() { + xml xmldata = xml ` + + + R.C Martin + 60 + + Clean Code + `; + OpenBook2|Error err = parseAsType(xmldata); + test:assertTrue(err is error); + test:assertEquals((err).message(), "undefined field 'name' in record 'data.xmldata:AuthorOpen'"); } From 98bf3a49a6af547327f3d881c8d34a11960e6bfc Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 27 May 2024 14:00:45 +0530 Subject: [PATCH 7/8] Correct remove method of QualifiedNameMap --- .../ballerina/lib/data/xmldata/xml/QualifiedNameMap.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java index 4fbc856..2a69d27 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/xml/QualifiedNameMap.java @@ -29,12 +29,19 @@ public V remove(QualifiedName qName) { if (!fields.containsKey(localName)) { return null; } + + List qNames = fields.get(localName); for (QualifiedName qualifiedName : fields.get(localName)) { if (qualifiedName.getNamespaceURI().equals(Constants.NS_ANNOT_NOT_DEFINED)) { field = this.members.remove(qualifiedName); + qNames.remove(qualifiedName); break; } } + + if (qNames.isEmpty()) { + fields.remove(localName); + } } return field; } From b9ba5e34c5239a5865da577bcd5a42e83b286bfe Mon Sep 17 00:00:00 2001 From: prakanth <50439067+prakanth97@users.noreply.github.com> Date: Mon, 27 May 2024 14:05:42 +0530 Subject: [PATCH 8/8] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index e3597f6..1577886 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "data.xmldata" -version = "0.1.0" +version = "0.1.1" authors = ["Ballerina"] keywords = ["xml"] repository = "https://github.com/ballerina-platform/module-ballerina-data-xmldata" @@ -12,5 +12,5 @@ export = ["data.xmldata"] [[platform.java17.dependency]] groupId = "io.ballerina.lib" artifactId = "data-native" -version = "0.1.0" -path = "../native/build/libs/data.xmldata-native-0.1.0.jar" +version = "0.1.1" +path = "../native/build/libs/data.xmldata-native-0.1.1-SNAPSHOT.jar" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 7eef6a8..98592f5 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.0.jar" +path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-0.1.1-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 544d7c8..57bd1b1 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.9.0" [[package]] org = "ballerina" name = "data.xmldata" -version = "0.1.0" +version = "0.1.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"},