From 053ce37b72279b4c8da73afae18b259864f1c5f2 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Fri, 22 Nov 2024 17:25:49 +0530 Subject: [PATCH 1/6] Add type reference annotation to subtype --- .../lib/data/xmldata/utils/DataUtils.java | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) 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 e3cf856..782baac 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 @@ -31,6 +31,7 @@ import io.ballerina.runtime.api.creators.TypeCreator; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.flags.SymbolFlags; +import io.ballerina.runtime.api.types.AnnotatableType; import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.Field; import io.ballerina.runtime.api.types.MapType; @@ -432,7 +433,9 @@ public static boolean isSameAttributeFlag(QualifiedName.AttributeState flag1, Qu public static Object getModifiedRecord(BMap input, BString textFieldName, BTypedesc type) { Type describingType = type.getDescribingType(); + Type referredType = TypeUtils.getReferredType(describingType); contentFieldName = textFieldName.getValue(); + if (describingType.getTag() == TypeTags.MAP_TAG) { Type constraintType = TypeUtils.getReferredType(((MapType) describingType).getConstrainedType()); switch (constraintType.getTag()) { @@ -452,11 +455,11 @@ public static Object getModifiedRecord(BMap input, BString text } } } - if (describingType.getTag() == TypeTags.RECORD_TYPE_TAG && + if (referredType instanceof RecordType && describingType.getFlags() != Constants.DEFAULT_TYPE_FLAG) { BArray jsonArray = ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); - BMap recordField = addFields(input, type.getDescribingType()); - BMap processedRecord = processParentAnnotation(type.getDescribingType(), recordField); + BMap recordField = addFields(input, describingType); + BMap processedRecord = processParentAnnotation(describingType, recordField); BString rootTagName = processedRecord.getKeys()[0]; jsonArray.append(processedRecord.get(rootTagName)); jsonArray.append(rootTagName); @@ -465,10 +468,23 @@ public static Object getModifiedRecord(BMap input, BString text return input; } + private static BMap mergeOriginalAndCurrentAnnotations( + BMap originalTypeAnnotations, BMap typeAnnotations) { + BMap annotations = originalTypeAnnotations; + for (Map.Entry entry: typeAnnotations.entrySet()) { + if (annotations.containsKey(entry.getKey())) { + continue; + } + annotations.put(entry.getKey(), entry.getValue()); + } + return annotations; + } + @SuppressWarnings("unchecked") private static BMap processArrayValue(BMap input, ArrayType arrayType) { - Type elementType = TypeUtils.getReferredType(arrayType.getElementType()); - switch (elementType.getTag()) { + Type elementType = arrayType.getElementType(); + Type referedType = TypeUtils.getReferredType(elementType); + switch (referedType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> { BMap jsonMap = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); for (Map.Entry entry : input.entrySet()) { @@ -477,12 +493,12 @@ private static BMap processArrayValue(BMap inp for (int i = 0; i < arrayValue.getLength(); i++) { BMap record = addFields(((BMap) arrayValue.get(i)), elementType); - BMap parentRecord = processParentAnnotation(elementType, record); + BMap parentRecord = processParentAnnotation(referedType, record); // Remove parent element records.add((BMap) parentRecord.get(parentRecord.getKeys()[0])); } jsonMap.put(entry.getKey(), ValueCreator.createArrayValue(records.toArray(), - TypeCreator.createArrayType(elementType))); + TypeCreator.createArrayType(referedType))); } return jsonMap; } @@ -508,9 +524,18 @@ private static BMap processArrayValue(BMap inp private static BMap addFields(BMap input, Type type) { BMap recordValue = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - Map fields = ((RecordType) type).getFields(); - BMap annotations = ((RecordType) type).getAnnotations(); - for (Map.Entry entry : input.entrySet()) { + BMap annotations = ValueCreator.createMapValue(); + if (type instanceof AnnotatableType annotatableType) { + annotations = annotatableType.getAnnotations(); + } + + RecordType recordType = (RecordType) TypeUtils.getReferredType(type); + if (type instanceof ReferenceType) { + annotations = mergeOriginalAndCurrentAnnotations(annotations, recordType.getAnnotations()); + } + + Map fields = recordType.getFields(); + for (Map.Entry entry: input.entrySet()) { String key = entry.getKey().getValue(); Object value = entry.getValue(); if (fields.containsKey(key)) { @@ -579,6 +604,9 @@ private static void processRecordField(Type fieldType, BMap ann private static void processTypeReferenceType(Type fieldType, BMap annotations, BMap recordValue, String key, Object value) { BMap namespaceAnnotRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + Type referredType = TypeUtils.getReferredType(fieldType); + RecordType recType = (RecordType) referredType; + annotations = mergeOriginalAndCurrentAnnotations(annotations, (recType).getAnnotations()); boolean doesNamespaceDefinedInField = false; if (!annotations.isEmpty()) { String fieldName = key; @@ -592,9 +620,9 @@ private static void processTypeReferenceType(Type fieldType, BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - Type referredType = TypeUtils.getReferredType(fieldType); + if (!doesNamespaceDefinedInField) { - BMap subRecordAnnotations = ((RecordType) referredType).getAnnotations(); + BMap subRecordAnnotations = (recType).getAnnotations(); key = getElementName(subRecordAnnotations, key); processSubRecordAnnotation(subRecordAnnotations, annotationRecord); } @@ -729,22 +757,25 @@ private static void addPrimitiveValue(QName qName, BMap annotat @SuppressWarnings("unchecked") private static void processArray(Type elementType, BMap annotations, BMap record, Map.Entry entry) { - BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); + Type elementType = ((ArrayType) childType).getElementType(); + Type referedType = TypeUtils.getReferredType(elementType); + BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); String keyName = entry.getKey().getValue(); if (!annotations.isEmpty()) { keyName = getKeyNameFromAnnotation(annotations, keyName); processSubRecordAnnotation(annotations, annotationRecord); } BArray arrayValue = (BArray) entry.getValue(); - if (elementType.getTag() == TypeTags.RECORD_TYPE_TAG) { + if (referedType.getTag() == TypeTags.RECORD_TYPE_TAG) { List> records = new ArrayList<>(); for (int i = 0; i < arrayValue.getLength(); i++) { - BMap subRecord = addFields(((BMap) arrayValue.get(i)), elementType); - subRecord = processParentAnnotation(elementType, subRecord); + BMap subRecord = addFields(((BMap) arrayValue.get(i)), + elementType); + subRecord = processParentAnnotation(referedType, subRecord); records.add((BMap) subRecord.get(subRecord.getKeys()[0])); } record.put( - StringUtils.fromString(getElementName(((RecordType) elementType).getAnnotations(), keyName)), + StringUtils.fromString(getElementName(((RecordType) referedType).getAnnotations(), keyName)), ValueCreator.createArrayValue(records.toArray(), TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); } else { @@ -785,9 +816,17 @@ public static Type getTypeFromUnionType(Type childType, Object value) { } private static BMap processParentAnnotation(Type type, BMap record) { + BMap annotations = ValueCreator.createMapValue(); + if (type instanceof AnnotatableType annotatableType) { + annotations = annotatableType.getAnnotations(); + } + Type referedType = TypeUtils.getReferredType(type); + if (type instanceof ReferenceType referenceType) { + annotations = mergeOriginalAndCurrentAnnotations(annotations, ((RecordType) referedType).getAnnotations()); + } + BMap parentRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); BMap namespaces = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); - BMap annotations = ((RecordType) type).getAnnotations(); BString rootName = processAnnotation(annotations, type.getName(), namespaces); if (!namespaces.isEmpty()) { for (Map.Entry namespace : namespaces.entrySet()) { From 67bdc230530a8d30f40a5cd153c960138d285478 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Mon, 25 Nov 2024 13:43:35 +0530 Subject: [PATCH 2/6] Update processRecordField to gather namespace informations --- .../io/ballerina/lib/data/xmldata/utils/DataUtils.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 782baac..f261364 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 @@ -585,8 +585,7 @@ private static void processRecordField(Type fieldType, BMap ann switch (fieldType.getTag()) { case TypeTags.RECORD_TYPE_TAG -> processRecord(key, annotations, recordValue, value, (RecordType) fieldType); - case TypeTags.ARRAY_TAG -> processArray(TypeUtils.getReferredType(((ArrayType) fieldType) - .getElementType()), annotations, recordValue, entry); + case TypeTags.ARRAY_TAG -> processArray(fieldType, annotations, recordValue, entry); case TypeTags.TYPE_REFERENCED_TYPE_TAG -> { Type referredType = TypeUtils.getReferredType(fieldType); if (referredType.getTag() != TypeTags.RECORD_TYPE_TAG) { @@ -622,7 +621,7 @@ private static void processTypeReferenceType(Type fieldType, BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE); if (!doesNamespaceDefinedInField) { - BMap subRecordAnnotations = (recType).getAnnotations(); + BMap subRecordAnnotations = recType.getAnnotations(); key = getElementName(subRecordAnnotations, key); processSubRecordAnnotation(subRecordAnnotations, annotationRecord); } @@ -755,7 +754,7 @@ private static void addPrimitiveValue(QName qName, BMap annotat } @SuppressWarnings("unchecked") - private static void processArray(Type elementType, BMap annotations, + private static void processArray(Type childType, BMap annotations, BMap record, Map.Entry entry) { Type elementType = ((ArrayType) childType).getElementType(); Type referedType = TypeUtils.getReferredType(elementType); From a9f687cb5791fe2d5b4f04aff4e9a53ec5151dd0 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Tue, 26 Nov 2024 11:37:18 +0530 Subject: [PATCH 3/6] Add partial implementation for primitive value arrays with namespaces --- .../lib/data/xmldata/utils/DataUtils.java | 12 ++- .../lib/data/xmldata/utils/ToXmlUtils.java | 97 +++++++++++-------- 2 files changed, 64 insertions(+), 45 deletions(-) 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 f261364..1129504 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 @@ -782,8 +782,16 @@ private static void processArray(Type childType, BMap annotatio for (int i = 0; i < arrayValue.getLength(); i++) { records.add(arrayValue.get(i)); } - record.put(StringUtils.fromString(keyName), ValueCreator.createArrayValue(records.toArray(), - TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); + + QName qName = addFieldNamespaceAnnotation(keyName, keyName, annotations, record); + BString localPart = StringUtils.fromString(qName.getLocalPart()); + BString key = qName.getPrefix().isBlank() ? + localPart : StringUtils.fromString(qName.getPrefix() + ":" + localPart); + record.put(key, ValueCreator.createArrayValue( + records.toArray(), TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); + +// record.put(StringUtils.fromString(keyName), ValueCreator.createArrayValue(records.toArray(), +// TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); } if (!annotationRecord.isEmpty()) { record.put(annotationRecord.getKeys()[0], diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java index 541a23d..95a7550 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java @@ -76,7 +76,7 @@ public static Object fromRecordToXml(Object jsonValue, BMap opt getEmptyStringMap(), options, null, referredType, false, false, null, null), allNamespaces, options, - getAttributesMap(jsonValue, options, allNamespaces, getEmptyStringMap())); + getAttributesMap(jsonValue, options, allNamespaces, getEmptyStringMap(), null)); } BMap jMap = null; @@ -115,9 +115,10 @@ public static Object fromRecordToXml(Object jsonValue, BMap opt return getElementFromRecordMember(rootTag == null ? StringUtils.fromString(Constants.ROOT) : rootTagBstring, traverseRecordAndGenerateXml( value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( - referredType, keyStr), isSequenceField, isSequenceField, + referredType, recordKey), isSequenceField, isSequenceField, parentModelGroupInfo, elementInfo), - allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap(), + key)); } if (key.equals(options.get(Constants.TEXT_FIELD_NAME))) { @@ -128,7 +129,7 @@ value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( traverseRecordAndGenerateXml(value, allNamespaces, getEmptyStringMap(), options, null, getChildElementType(referredType, recordKey), isSequenceField, isSequenceField, parentModelGroupInfo, elementInfo), - allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap(), key)); if (isContainsModelGroup) { output = output.children(); } @@ -200,7 +201,8 @@ public static BXml traverseRecordAndGenerateXml(Object jNode, BMap getAttributesMap(Object jsonTree, BMap options, BMap namespaces, - BMap parentNamespaces) { + BMap parentNamespaces, Object keyObj) { BMap attributes = (BMap) parentNamespaces.copy(new HashMap<>()); + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); try { BMap attr = (BMap) ValueUtils.convert( jsonTree, TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); - - String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); for (Map.Entry entry : attr.entrySet()) { String key = entry.getKey().toString(); Object value = entry.getValue(); - if (!key.startsWith(attributePrefix)) { - continue; - } - - if (value instanceof BMap || value instanceof BArray) { - DiagnosticLog.createXmlError("attribute cannot be an object or array."); - } - - int index = key.indexOf(Constants.COLON); - if (index != -1) { - String suffix = key.substring(index + 1); - if (key.startsWith(attributePrefix + XMLNS)) { - attributes.put(StringUtils.fromString(getXmlnsNameUrI() + suffix), - StringUtils.fromString(StringUtils.getStringValue(value))); - } else { - Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), StringUtils.fromString( - options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), StringUtils.fromString(key)); - String prefix = key.substring(startIndex.intValue(), index); - BString namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)); - attributes.put(StringUtils.fromString("{" + namespaceUrl + "}" + suffix), - StringUtils.fromString(StringUtils.getStringValue(value))); - } - } else { - if (key.equals(attributePrefix + XMLNS)) { - attributes.put(XMLNS, StringUtils.fromString(StringUtils.getStringValue(value))); - } else { - Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), - StringUtils.fromString(options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), - StringUtils.fromString(key)); - attributes.put(StringUtils.fromString(key.substring(startIndex.intValue())), - StringUtils.fromString(StringUtils.getStringValue(value))); - } - } + getAttributesMapFromKey(key, value, attributePrefix, attributes, options, namespaces); } return attributes; } catch (BError e) { + if (keyObj != null && keyObj instanceof BString key) { + getAttributesMapFromKey(key.getValue(), jsonTree, + options.get(Constants.ATTRIBUTE_PREFIX).toString(), attributes, options, namespaces); + } return attributes; } } + private static void getAttributesMapFromKey(String key, Object value, String attributePrefix, + BMap attributes, BMap options, + BMap namespaces) { + if (!key.startsWith(attributePrefix)) { + return; + } + + if (value instanceof BMap || value instanceof BArray) { + DiagnosticLog.createXmlError("attribute cannot be an object or array."); + } + + int index = key.indexOf(Constants.COLON); + if (index != -1) { + String suffix = key.substring(index + 1); + if (key.startsWith(attributePrefix + XMLNS)) { + attributes.put(StringUtils.fromString(getXmlnsNameUrI() + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), StringUtils.fromString( + options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), StringUtils.fromString(key)); + String prefix = key.substring(startIndex.intValue(), index); + BString namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)); + attributes.put(StringUtils.fromString("{" + namespaceUrl + "}" + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } else { + if (key.equals(attributePrefix + XMLNS)) { + attributes.put(XMLNS, StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), + StringUtils.fromString(options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), + StringUtils.fromString(key)); + attributes.put(StringUtils.fromString(key.substring(startIndex.intValue())), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } + } + public static Long getStartIndex(BString attributePrefix, BString userAttributePrefix, BString key) { String attributePrefixStr = attributePrefix.toString(); String userAttributePrefixStr = userAttributePrefix.toString(); From e6bcd7962c9e1b04dd8305938f1e940517886b5c Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Fri, 29 Nov 2024 17:04:16 +0530 Subject: [PATCH 4/6] Revert namespace changes for primitives --- .../lib/data/xmldata/utils/DataUtils.java | 13 +-- .../lib/data/xmldata/utils/ToXmlUtils.java | 95 ++++++++----------- 2 files changed, 45 insertions(+), 63 deletions(-) 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 1129504..b0a0c28 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 @@ -458,7 +458,7 @@ public static Object getModifiedRecord(BMap input, BString text if (referredType instanceof RecordType && describingType.getFlags() != Constants.DEFAULT_TYPE_FLAG) { BArray jsonArray = ValueCreator.createArrayValue(PredefinedTypes.TYPE_JSON_ARRAY); - BMap recordField = addFields(input, describingType); + BMap recordField = addFields(input, describingType); BMap processedRecord = processParentAnnotation(describingType, recordField); BString rootTagName = processedRecord.getKeys()[0]; jsonArray.append(processedRecord.get(rootTagName)); @@ -783,15 +783,8 @@ private static void processArray(Type childType, BMap annotatio records.add(arrayValue.get(i)); } - QName qName = addFieldNamespaceAnnotation(keyName, keyName, annotations, record); - BString localPart = StringUtils.fromString(qName.getLocalPart()); - BString key = qName.getPrefix().isBlank() ? - localPart : StringUtils.fromString(qName.getPrefix() + ":" + localPart); - record.put(key, ValueCreator.createArrayValue( - records.toArray(), TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); - -// record.put(StringUtils.fromString(keyName), ValueCreator.createArrayValue(records.toArray(), -// TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); + record.put(StringUtils.fromString(keyName), ValueCreator.createArrayValue(records.toArray(), + TypeCreator.createArrayType(Constants.JSON_ARRAY_TYPE))); } if (!annotationRecord.isEmpty()) { record.put(annotationRecord.getKeys()[0], diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java index 95a7550..1833a04 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java @@ -76,7 +76,7 @@ public static Object fromRecordToXml(Object jsonValue, BMap opt getEmptyStringMap(), options, null, referredType, false, false, null, null), allNamespaces, options, - getAttributesMap(jsonValue, options, allNamespaces, getEmptyStringMap(), null)); + getAttributesMap(jsonValue, options, allNamespaces, getEmptyStringMap())); } BMap jMap = null; @@ -117,8 +117,7 @@ public static Object fromRecordToXml(Object jsonValue, BMap opt value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( referredType, recordKey), isSequenceField, isSequenceField, parentModelGroupInfo, elementInfo), - allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap(), - key)); + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); } if (key.equals(options.get(Constants.TEXT_FIELD_NAME))) { @@ -129,7 +128,7 @@ allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmpty traverseRecordAndGenerateXml(value, allNamespaces, getEmptyStringMap(), options, null, getChildElementType(referredType, recordKey), isSequenceField, isSequenceField, parentModelGroupInfo, elementInfo), - allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap(), key)); + allNamespaces, options, getAttributesMap(value, options, allNamespaces, getEmptyStringMap())); if (isContainsModelGroup) { output = output.children(); } @@ -201,8 +200,7 @@ public static BXml traverseRecordAndGenerateXml(Object jNode, BMap getAttributesMap(Object jsonTree, BMap options, BMap namespaces, - BMap parentNamespaces, Object keyObj) { + BMap parentNamespaces) { BMap attributes = (BMap) parentNamespaces.copy(new HashMap<>()); - String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); try { BMap attr = (BMap) ValueUtils.convert( jsonTree, TypeCreator.createMapType(PredefinedTypes.TYPE_JSON)); + + String attributePrefix = options.get(Constants.ATTRIBUTE_PREFIX).toString(); for (Map.Entry entry : attr.entrySet()) { String key = entry.getKey().toString(); Object value = entry.getValue(); - getAttributesMapFromKey(key, value, attributePrefix, attributes, options, namespaces); + if (!key.startsWith(attributePrefix)) { + continue; + } + + if (value instanceof BMap || value instanceof BArray) { + DiagnosticLog.createXmlError("attribute cannot be an object or array."); + } + + int index = key.indexOf(Constants.COLON); + if (index != -1) { + String suffix = key.substring(index + 1); + if (key.startsWith(attributePrefix + XMLNS)) { + attributes.put(StringUtils.fromString(getXmlnsNameUrI() + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), StringUtils.fromString( + options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), StringUtils.fromString(key)); + String prefix = key.substring(startIndex.intValue(), index); + BString namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)); + attributes.put(StringUtils.fromString("{" + namespaceUrl + "}" + suffix), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } else { + if (key.equals(attributePrefix + XMLNS)) { + attributes.put(XMLNS, StringUtils.fromString(StringUtils.getStringValue(value))); + } else { + Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), + StringUtils.fromString(options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), + StringUtils.fromString(key)); + attributes.put(StringUtils.fromString(key.substring(startIndex.intValue())), + StringUtils.fromString(StringUtils.getStringValue(value))); + } + } } return attributes; } catch (BError e) { - if (keyObj != null && keyObj instanceof BString key) { - getAttributesMapFromKey(key.getValue(), jsonTree, - options.get(Constants.ATTRIBUTE_PREFIX).toString(), attributes, options, namespaces); - } return attributes; } } - private static void getAttributesMapFromKey(String key, Object value, String attributePrefix, - BMap attributes, BMap options, - BMap namespaces) { - if (!key.startsWith(attributePrefix)) { - return; - } - - if (value instanceof BMap || value instanceof BArray) { - DiagnosticLog.createXmlError("attribute cannot be an object or array."); - } - - int index = key.indexOf(Constants.COLON); - if (index != -1) { - String suffix = key.substring(index + 1); - if (key.startsWith(attributePrefix + XMLNS)) { - attributes.put(StringUtils.fromString(getXmlnsNameUrI() + suffix), - StringUtils.fromString(StringUtils.getStringValue(value))); - } else { - Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), StringUtils.fromString( - options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), StringUtils.fromString(key)); - String prefix = key.substring(startIndex.intValue(), index); - BString namespaceUrl = namespaces.get(StringUtils.fromString(getXmlnsNameUrI() + prefix)); - attributes.put(StringUtils.fromString("{" + namespaceUrl + "}" + suffix), - StringUtils.fromString(StringUtils.getStringValue(value))); - } - } else { - if (key.equals(attributePrefix + XMLNS)) { - attributes.put(XMLNS, StringUtils.fromString(StringUtils.getStringValue(value))); - } else { - Long startIndex = getStartIndex(StringUtils.fromString(attributePrefix), - StringUtils.fromString(options.get(Constants.USER_ATTRIBUTE_PREFIX).toString()), - StringUtils.fromString(key)); - attributes.put(StringUtils.fromString(key.substring(startIndex.intValue())), - StringUtils.fromString(StringUtils.getStringValue(value))); - } - } - } - public static Long getStartIndex(BString attributePrefix, BString userAttributePrefix, BString key) { String attributePrefixStr = attributePrefix.toString(); String userAttributePrefixStr = userAttributePrefix.toString(); From 8fe72031cf4b8e8e136c581fbd49217fee18acf6 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Mon, 9 Dec 2024 11:56:35 +0530 Subject: [PATCH 5/6] Add correct output for toXml with content field --- .../tests/test_ref_types_with_annotations.bal | 168 ++++++++++++++++++ ballerina/tests/toXml_test.bal | 63 +++++++ .../lib/data/xmldata/utils/ToXmlUtils.java | 6 +- 3 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 ballerina/tests/test_ref_types_with_annotations.bal diff --git a/ballerina/tests/test_ref_types_with_annotations.bal b/ballerina/tests/test_ref_types_with_annotations.bal new file mode 100644 index 0000000..ef24a3c --- /dev/null +++ b/ballerina/tests/test_ref_types_with_annotations.bal @@ -0,0 +1,168 @@ +import ballerina/test; + +@Name { + value: "root" +} +@Namespace { + uri: "http://ballerina.io", + prefix: "a1" +} +type TypeRefType1 record { + @Name { + value: "nestedA" + } + NestedTypeRefType1 a; + + @Name { + value: "nestedB" + } + NestedTypeRefType2 b; +}; + +@Namespace { + uri: "http://nested-ballerina.io", + prefix: "nestedA" +} +type NestedTypeRefType1 record { + @Name { + value: "A" + } + int a; + + @Name { + value: "B" + } + int b; +}; + +type NestedTypeRefType2 record { + @Name { + value: "A" + } + int a; + + @Name { + value: "B" + } + int b; +}; + +type TypeRefA1 TypeRefType1; + +@Namespace { + uri: "http://ballerina.io", + prefix: "A1" +} +type TypeRefB1 TypeRefA1; + +@Namespace { + uri: "http://ballerina.io", + prefix: "A1" +} +@Name { + value: "r" +} +type TypeRefC1 TypeRefA1; + +@test:Config +function testTypeRefAnnotations() returns error? { + TypeRefType1 t = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xml xmlResult = check toXml(t); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); + + TypeRefA1 ta = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xmlResult = check toXml(ta); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); + + TypeRefB1 tb = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xmlResult = check toXml(tb); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); + + TypeRefC1 tc = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xmlResult = check toXml(tc); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); +} + +@Namespace { + uri: "http://ballerina.io", + prefix: "a1" +} +@Name { + value: "root" +} +type TypeRefType2 record { + @Name { + value: "nestedA" + } + NestedTypeRefType3 a; + + @Name { + value: "nestedB" + } + NestedTypeRefType4 b; +}; + +@Namespace { + uri: "http://nested-ballerina.io", + prefix: "nestedA" +} +type NestedTypeRefType3 record { + @Name { + value: "A" + } + int a; + + @Name { + value: "B" + } + int b; +}; + +@Namespace { + uri: "http://nested-ballerina.io", + prefix: "nestedB" +} +type NestedTypeRefType4 record { + @Namespace { + uri: "http://nested-ballerina.io/a", + prefix: "a" + } + int a; + + @Name { + value: "B" + } + int b; +}; + +@test:Config +function testTypeRefAnnotations2() returns error? { + TypeRefType2 t = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xml xmlResult = check toXml(t); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); +} diff --git a/ballerina/tests/toXml_test.bal b/ballerina/tests/toXml_test.bal index 4769479..a77136d 100644 --- a/ballerina/tests/toXml_test.bal +++ b/ballerina/tests/toXml_test.bal @@ -1378,3 +1378,66 @@ function testMapXmltoXmlNegative() { xml|Error result = toXml(data); test:assertEquals((result).message(), "unsupported input type"); } + +type ContentRec record { + string \#content; +}; + +@Name { + value: "root1" +} +type ContentRec2 record { + string \#content; +}; + +type ContentRec3 record { + @Name { + value: "value" + } + string \#content; +}; + +@Namespace { + uri: "www.example.com", + prefix: "ns" +} +type ContentRec4 record { + string \#content; +}; + +@test:Config { + groups: ["toXml"] +} +function testToXmlWithContentField() returns error? { + ContentRec document = { + \#content: "this is a string" + }; + xml xmlDocument = check toXml(document); + test:assertEquals(xmlDocument.toString(), "this is a string"); + + ContentRec2 document2 = { + \#content: "this is a string" + }; + xmlDocument = check toXml(document2); + test:assertEquals(xmlDocument.toString(), "this is a string"); + + ContentRec3 document3 = { + \#content: "this is a string" + }; + xmlDocument = check toXml(document3); + test:assertEquals(xmlDocument.toString(), "this is a string"); + + ContentRec4 document4 = { + \#content: "this is a string" + }; + xmlDocument = check toXml(document4); + test:assertEquals(xmlDocument.toString(), "this is a string"); + + record { + int \#content; + } document5 = { + \#content: 2 + }; + xmlDocument = check toXml(document5); + test:assertEquals(xmlDocument.toString(), "2"); +} diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java index 1833a04..487bf28 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java @@ -121,7 +121,11 @@ value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( } if (key.equals(options.get(Constants.TEXT_FIELD_NAME))) { - return CreateText.createText(StringUtils.fromString(value.toString())); + if (rootTagBstring.equals(StringUtils.fromString(Constants.EMPTY_STRING))) { + rootTagBstring = StringUtils.fromString(Constants.ROOT); + } + return CreateElement.createElement(rootTagBstring, getEmptyStringMap(), + CreateText.createText(StringUtils.fromString(value.toString()))); } BXml output = getElementFromRecordMember(key, From 3004906d2b8650495853814d2666e82ac30e1ab8 Mon Sep 17 00:00:00 2001 From: Sasindu Alahakoon Date: Wed, 11 Dec 2024 10:15:20 +0530 Subject: [PATCH 6/6] Add conversion for nested fields with namespaces/name --- .../tests/test_ref_types_with_annotations.bal | 101 ++++++++++++++++++ .../lib/data/xmldata/utils/DataUtils.java | 12 ++- .../lib/data/xmldata/utils/ToXmlUtils.java | 2 +- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/ballerina/tests/test_ref_types_with_annotations.bal b/ballerina/tests/test_ref_types_with_annotations.bal index ef24a3c..6c99568 100644 --- a/ballerina/tests/test_ref_types_with_annotations.bal +++ b/ballerina/tests/test_ref_types_with_annotations.bal @@ -166,3 +166,104 @@ function testTypeRefAnnotations2() returns error? { test:assertTrue(xmlResult is xml:Element); test:assertEquals(xmlResult.toString(), string `1234`); } + +@Name { + value: "root" +} +type TypeRefType3 record { + @Namespace { + uri: "http://www.example.com/", + prefix: "ex" + } + @Name { + value: "A" + } + int a; +}; + +@test:Config +function testTypeRefAnnotations3() returns error? { + TypeRefType3 t = {a: 1}; + xml xmlResult = check toXml(t); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1`); +} + +@Namespace { + uri: "http://ballerina.io", + prefix: "a1" +} +@Name { + value: "root" +} +type TypeRefType4 record { + @Name { + value: "nestedA" + } + NestedTypeRefType5 a; + + @Name { + value: "nestedB" + } + NestedTypeRefType6 b; +}; + +@Namespace { + uri: "http://nested-ballerina.io", + prefix: "nestedA" +} +type NestedTypeRefType5 record { + @Name { + value: "A" + } + @Namespace { + uri: "http://nested-ballerina.io/a", + prefix: "a" + } + int a; + + @Namespace { + uri: "http://nested-ballerina.io/a", + prefix: "a" + } + @Name { + value: "B" + } + int b; +}; + +@Namespace { + uri: "http://nested-ballerina.io", + prefix: "nestedB" +} +type NestedTypeRefType6 record { + @Name { + value: "A" + } + @Namespace { + uri: "http://nested-ballerina.io/a", + prefix: "a" + } + int a; + + @Name { + value: "B" + } + @Namespace { + uri: "http://nested-ballerina.io/a", + prefix: "a" + } + int b; +}; + +@test:Config +function testTypeRefAnnotations4() returns error? { + TypeRefType4 t = { + a: {a: 1, b: 2}, + b: {a: 3, b: 4} + }; + + xml xmlResult = check toXml(t); + test:assertTrue(xmlResult is xml:Element); + test:assertEquals(xmlResult.toString(), string `1234`); +} 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 b0a0c28..e1387e6 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 @@ -738,8 +738,10 @@ private static void addPrimitiveValue(QName qName, BMap annotat BString annotationKey = StringUtils.fromString(Constants.FIELD + (localPart.getValue().replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0"))); BMap currentValue; + BString prevKey = key; if (record.containsKey(key)) { currentValue = (BMap) record.get(key); + prevKey = key; key = StringUtils.fromString(contentFieldName); } else { currentValue = record; @@ -747,7 +749,15 @@ private static void addPrimitiveValue(QName qName, BMap annotat if (annotations.containsKey(annotationKey)) { BMap annotationValue = (BMap) annotations.get(annotationKey); - currentValue.put(StringUtils.fromString(processFieldAnnotation(annotationValue, key.getValue())), value); + String keyName = processFieldAnnotation(annotationValue, prevKey.getValue()); + if (key.getValue().equals(contentFieldName)) { + currentValue.put(StringUtils.fromString(contentFieldName), value); + if (!keyName.equals(prevKey.getValue())) { + record.put(StringUtils.fromString(keyName), record.remove(prevKey)); + } + } else { + currentValue.put(StringUtils.fromString(keyName), value); + } } else { currentValue.put(key, value); } diff --git a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java index 487bf28..5db8c37 100644 --- a/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java +++ b/native/src/main/java/io/ballerina/lib/data/xmldata/utils/ToXmlUtils.java @@ -140,7 +140,7 @@ value, allNamespaces, getEmptyStringMap(), options, key, getChildElementType( return CreateElement.createElement(rootTagBstring, getEmptyStringMap(), output); } return output; - } catch (BError e) { + } catch (Exception e) { return DiagnosticLog.createXmlError(e.getMessage()); } }