diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml
index afd9674..f948987 100644
--- a/ballerina/Ballerina.toml
+++ b/ballerina/Ballerina.toml
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "data.xmldata"
-version = "0.1.1"
+version = "0.1.2"
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.1"
-path = "../native/build/libs/data.xmldata-native-0.1.1.jar"
+version = "0.1.2"
+path = "../native/build/libs/data.xmldata-native-0.1.2-SNAPSHOT.jar"
diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml
index 2a0250b..0ee6f03 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.1.jar"
+path = "../compiler-plugin/build/libs/data.xmldata-compiler-plugin-0.1.2-SNAPSHOT.jar"
diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml
index 57bd1b1..bcfbdef 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.1"
+version = "0.1.2"
dependencies = [
{org = "ballerina", name = "io"},
{org = "ballerina", name = "jballerina.java"},
diff --git a/ballerina/tests/toXml_test.bal b/ballerina/tests/toXml_test.bal
index 0950703..e48ee94 100644
--- a/ballerina/tests/toXml_test.bal
+++ b/ballerina/tests/toXml_test.bal
@@ -1165,6 +1165,171 @@ isolated function testRecordWithNamespaceAnnotationToXml1() returns error? {
test:assertEquals(result.toString(), expected, msg = "testComplexRecordToXml result incorrect");
}
+type AddressR1 record {|
+ string city;
+ int code;
+|};
+
+type Wsa_ReplyTo1 record {
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ AddressR1 Address;
+};
+
+type Htng_ReplyTo1 record {
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ AddressR1 Address;
+};
+
+@Name {value: "soap"}
+type Soap1 record {
+ @Name {value: "ReplyTo"}
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ Wsa_ReplyTo1 wsaReplyTo;
+ @Name {value: "ReplyTo"}
+ @Namespace {prefix: "htng", uri: "example2.com"}
+ Htng_ReplyTo1 htngReplyTo;
+};
+
+@test:Config {
+ groups: ["toXml", "testFail"]
+}
+isolated function testXmlToRecordWithNamespaceAttachedToFields() returns error? {
+ Soap1 val = {
+ htngReplyTo: {
+ Address: {
+ code: 40000,
+ city: "Colombo"
+ }
+ },
+ wsaReplyTo: {
+ Address: {
+ code: 10000,
+ city: "Kandy"
+ }
+ }
+ };
+
+ xml xmlVal = check toXml(val);
+ string expected = "" +
+ "" +
+ "Kandy10000
" +
+ "" +
+ "Colombo40000
" +
+ "";
+ test:assertEquals(xmlVal.toString(), expected);
+}
+
+@Namespace {prefix: "wsa", uri: "example1.com"}
+type AddressR2 record {|
+ string city;
+ int code;
+|};
+
+@Namespace {prefix: "wsa", uri: "example1.com"}
+type Wsa_ReplyTo2 record {
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ AddressR2 Address;
+};
+
+@Namespace {prefix: "htng", uri: "example2.com"}
+type Htng_ReplyTo2 record {
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ AddressR2 Address;
+};
+
+@Name {value: "soap"}
+type Soap2 record {
+ @Name {value: "ReplyTo"}
+ @Namespace {prefix: "wsa", uri: "example1.com"}
+ Wsa_ReplyTo2 wsaReplyTo;
+ @Name {value: "ReplyTo"}
+ @Namespace {prefix: "htng", uri: "example2.com"}
+ Htng_ReplyTo2 htngReplyTo;
+};
+
+@test:Config {
+ groups: ["toXml"]
+}
+isolated function testXmlToRecordWithNamespaceAttachedToFieldsAndTypes() returns error? {
+ Soap2 val = {
+ htngReplyTo: {
+ Address: {
+ code: 40000,
+ city: "Colombo"
+ }
+ },
+ wsaReplyTo: {
+ Address: {
+ code: 10000,
+ city: "Kandy"
+ }
+ }
+ };
+
+ xml xmlVal = check toXml(val);
+ string expected = "" +
+ "" +
+ "Kandy10000
" +
+ "" +
+ "Colombo40000
" +
+ "";
+ test:assertEquals(xmlVal.toString(), expected);
+}
+
+type RequestorID record {
+ @Attribute
+ string ID;
+ @Attribute
+ string ID_Context;
+ @Attribute
+ string Type;
+};
+
+type Source record {
+ RequestorID RequestorID;
+};
+
+@test:Config {
+ groups: ["toXml"]
+}
+isolated function testUnderscoreInTheFieldName() returns error? {
+ Source s = {
+ RequestorID: {
+ ID: "1",
+ ID_Context: "2",
+ Type: "3"}};
+ xml xmlVal = check toXml(s);
+ test:assertEquals(xmlVal.toString(), "");
+}
+
+@Namespace {
+ uri: "example.com"
+}
+type File record {|
+ @Namespace {
+ uri: "example.com"
+ }
+ string fileName;
+ @Namespace {
+ uri: "example.com"
+ }
+ string fileNamespace;
+|};
+
+@test:Config {
+ groups: ["toXml"]
+}
+isolated function testToRecordFieldNameEndsWithNameOrNamespace() returns error? {
+ File file = {
+ fileName: "test.bal",
+ fileNamespace: "wso2.com"
+ };
+
+ xml result = check toXml(file);
+ string expected = "test.balwso2.com";
+ test:assertEquals(result.toString(), expected);
+}
+
@test:Config {
groups: ["toXml"]
}
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 2e3e477..04830ac 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
@@ -42,6 +42,7 @@ private Constants() {}
public static final ArrayType JSON_ARRAY_TYPE = TypeCreator.createArrayType(PredefinedTypes.TYPE_JSON);
public static final String FIELD = "$field$.";
public static final String NAMESPACE = "Namespace";
+ public static final String MODULE_NAME = "ballerina/data.xmldata";
public static final BString URI = StringUtils.fromString("uri");
public static final BString PREFIX = StringUtils.fromString("prefix");
public static final String ATTRIBUTE = "Attribute";
@@ -58,6 +59,6 @@ private Constants() {}
public static final BString ATTRIBUTE_PREFIX = StringUtils.fromString("attributePrefix");
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 RECORD_FIELD_NAME_ESCAPE_CHAR_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 dfe0d70..f9c3b95 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
@@ -66,8 +66,7 @@ public static QualifiedName validateAndGetXmlNameFromRecordAnnotation(RecordType
BMap annotations = recordType.getAnnotations();
String localName = recordName;
for (BString annotationsKey : annotations.getKeys()) {
- String key = annotationsKey.getValue();
- if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAME)) {
+ if (isNameAnnotationKey(annotationsKey.getValue())) {
String name = ((BMap) annotations.get(annotationsKey)).get(Constants.VALUE).toString();
String localPart = elementName.getLocalPart();
if (!name.equals(localPart)) {
@@ -81,7 +80,7 @@ public static QualifiedName validateAndGetXmlNameFromRecordAnnotation(RecordType
// Handle the namespace annotation.
for (BString annotationsKey : annotations.getKeys()) {
String key = annotationsKey.getValue();
- if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(key)) {
Map namespaceAnnotation =
((Map) annotations.get(StringUtils.fromString(key)));
BString uri = (BString) namespaceAnnotation.get(Constants.URI);
@@ -106,8 +105,7 @@ private static ArrayList getNamespace(RecordType recordType) {
BMap annotations = recordType.getAnnotations();
ArrayList namespace = new ArrayList<>();
for (BString annotationsKey : annotations.getKeys()) {
- String key = annotationsKey.getValue();
- if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(annotationsKey.getValue())) {
BMap namespaceAnnotation = (BMap) annotations.get(annotationsKey);
namespace.add(namespaceAnnotation.containsKey(Constants.PREFIX) ?
((BString) namespaceAnnotation.get(Constants.PREFIX)).getValue() : "");
@@ -184,7 +182,7 @@ public static Map getAllAttributesInRecordType(RecordType
@SuppressWarnings("unchecked")
public static QualifiedName getFieldNameFromRecord(Map fieldAnnotation, String fieldName) {
for (BString key : fieldAnnotation.keySet()) {
- if (key.getValue().endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(key.getValue())) {
Map namespaceAnnotation = ((Map) fieldAnnotation.get(key));
BString uri = (BString) namespaceAnnotation.get(Constants.URI);
BString prefix = (BString) namespaceAnnotation.get(Constants.PREFIX);
@@ -198,7 +196,7 @@ public static QualifiedName getFieldNameFromRecord(Map fieldAnn
@SuppressWarnings("unchecked")
private static String getModifiedName(Map fieldAnnotation, String attributeName) {
for (BString key : fieldAnnotation.keySet()) {
- if (key.getValue().endsWith(Constants.NAME)) {
+ if (isNameAnnotationKey(key.getValue())) {
return ((Map) fieldAnnotation.get(key)).get(Constants.VALUE).toString();
}
}
@@ -459,14 +457,28 @@ private static BMap addFields(BMap input, Type
processRecord(key, annotations, recordValue, value, fieldType);
} else if (fieldType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) {
Type referredType = TypeUtils.getReferredType(fieldType);
+ BMap namespaceAnnotRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE);
+ boolean doesNamespaceDefinedInField = false;
if (annotations.size() > 0) {
+ String fieldName = key;
key = getKeyNameFromAnnotation(annotations, key);
+ QName qName = addFieldNamespaceAnnotation(fieldName, key, annotations, namespaceAnnotRecord);
+ if (!qName.getNamespaceURI().equals("")) {
+ doesNamespaceDefinedInField = true;
+ }
+ String localPart = qName.getLocalPart();
+ key = qName.getPrefix().isBlank() ? localPart : qName.getPrefix() + ":" + localPart;
}
- BMap subRecordAnnotations = ((RecordType) referredType).getAnnotations();
- key = getElementName(subRecordAnnotations, key);
+
BMap annotationRecord = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE);
- processSubRecordAnnotation(subRecordAnnotations, annotationRecord);
+ if (!doesNamespaceDefinedInField) {
+ BMap subRecordAnnotations = ((RecordType) referredType).getAnnotations();
+ key = getElementName(subRecordAnnotations, key);
+ processSubRecordAnnotation(subRecordAnnotations, annotationRecord);
+ }
+
BMap subRecordValue = addFields(((BMap) value), referredType);
+ addNamespaceToSubRecord(key, namespaceAnnotRecord, subRecordValue);
if (annotationRecord.size() > 0) {
subRecordValue.put(annotationRecord.getKeys()[0],
annotationRecord.get(annotationRecord.getKeys()[0]));
@@ -475,7 +487,7 @@ private static BMap addFields(BMap input, Type
} else if (fieldType.getTag() == TypeTags.ARRAY_TAG) {
processArray(fieldType, annotations, recordValue, entry);
} else {
- addPrimitiveValue(addFieldNamespaceAnnotation(key, annotations, recordValue),
+ addPrimitiveValue(addFieldNamespaceAnnotation(key, key, annotations, recordValue),
annotations, recordValue, value);
}
} else {
@@ -486,15 +498,32 @@ private static BMap addFields(BMap input, Type
}
@SuppressWarnings("unchecked")
- private static QName addFieldNamespaceAnnotation(String key, BMap annotations,
+ private static void addNamespaceToSubRecord(String key, BMap namespaceAnnotRecord,
+ BMap subRecord) {
+ if (namespaceAnnotRecord.isEmpty()) {
+ return;
+ }
+
+ Object value = namespaceAnnotRecord.get(StringUtils.fromString(key));
+ if (value == null) {
+ return;
+ }
+
+ for (Map.Entry nsAnnotEntry: ((BMap) value).entrySet()) {
+ subRecord.put(nsAnnotEntry.getKey(), nsAnnotEntry.getValue());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static QName addFieldNamespaceAnnotation(String fieldName, String key, BMap annotations,
BMap recordValue) {
BString annotationKey = StringUtils.fromString(Constants.FIELD
- + (key.replaceAll(Constants.NON_NUMERIC_STRING_REGEX, "\\\\$0")));
+ + (fieldName.replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0")));
boolean isAttributeField = isAttributeField(annotationKey, annotations);
if (annotations.containsKey(annotationKey)) {
BMap annotationValue = (BMap) annotations.get(annotationKey);
for (BString fieldKey : annotationValue.getKeys()) {
- if (fieldKey.toString().endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(fieldKey.getValue())) {
return processFieldNamespaceAnnotation(annotationValue, key, fieldKey, recordValue,
isAttributeField);
}
@@ -511,7 +540,7 @@ public static boolean isAttributeField(BString annotationKey, BMap annotationValue = (BMap) annotations.get(annotationKey);
for (BString fieldKey : annotationValue.getKeys()) {
- if (fieldKey.toString().endsWith(Constants.ATTRIBUTE)) {
+ if (isAttributeAnnotationKey(fieldKey.getValue())) {
return true;
}
}
@@ -524,7 +553,7 @@ private static BMap getFieldNamespaceAndNameAnnotations(String
BMap nsFieldAnnotation = ValueCreator.createMapValue(Constants.JSON_MAP_TYPE);
BString annotationKey =
StringUtils.fromString((Constants.FIELD
- + (key.replaceAll(Constants.NON_NUMERIC_STRING_REGEX, "\\\\$0"))));
+ + (key.replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0"))));
if (!parentAnnotations.containsKey(annotationKey)) {
return nsFieldAnnotation;
}
@@ -532,7 +561,7 @@ private static BMap getFieldNamespaceAndNameAnnotations(String
BMap annotationValue = (BMap) parentAnnotations.get(annotationKey);
for (BString fieldKey : annotationValue.getKeys()) {
String keyName = fieldKey.getValue();
- if (keyName.endsWith(Constants.NAMESPACE) || keyName.endsWith(Constants.NAME)) {
+ if (isNamespaceAnnotationKey(keyName) || isNameAnnotationKey(keyName)) {
nsFieldAnnotation.put(fieldKey, annotationValue.get(fieldKey));
break;
}
@@ -568,7 +597,7 @@ private static void addPrimitiveValue(QName qName, BMap annotat
BString key = qName.getPrefix().isBlank() ?
localPart : StringUtils.fromString(qName.getPrefix() + ":" + localPart);
BString annotationKey = StringUtils.fromString(Constants.FIELD
- + (localPart.getValue().replaceAll(Constants.NON_NUMERIC_STRING_REGEX, "\\\\$0")));
+ + (localPart.getValue().replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0")));
BMap currentValue;
if (record.containsKey(key)) {
currentValue = (BMap) record.get(key);
@@ -625,7 +654,7 @@ private static void processArray(Type childType, BMap annotatio
@SuppressWarnings("unchecked")
private static String getKeyNameFromAnnotation(BMap annotations, String keyName) {
BString annotationKey = StringUtils.fromString(Constants.FIELD
- + (keyName.replaceAll(Constants.NON_NUMERIC_STRING_REGEX, "\\\\$0")));
+ + (keyName.replaceAll(Constants.RECORD_FIELD_NAME_ESCAPE_CHAR_REGEX, "\\\\$0")));
if (annotations.containsKey(annotationKey)) {
BMap annotationValue = (BMap) annotations.get(annotationKey);
return processFieldAnnotation(annotationValue, keyName);
@@ -663,7 +692,7 @@ private static BMap processParentAnnotation(Type type, BMap annotation, String key) {
for (BString value : annotation.getKeys()) {
String stringValue = value.getValue();
- if (stringValue.endsWith(Constants.NAME)) {
+ if (isNameAnnotationKey(stringValue)) {
BMap names = (BMap) annotation.get(value);
String name = names.get(StringUtils.fromString(VALUE)).toString();
if (key.contains(Constants.COLON)) {
@@ -674,7 +703,7 @@ private static String processFieldAnnotation(BMap annotation, S
key = name;
}
}
- if (stringValue.endsWith(Constants.ATTRIBUTE)) {
+ if (isAttributeAnnotationKey(stringValue)) {
key = ATTRIBUTE_PREFIX.concat(key);
}
}
@@ -687,10 +716,10 @@ private static BString processAnnotation(BMap annotation, Strin
for (BString value : annotation.getKeys()) {
if (!value.getValue().contains(Constants.FIELD)) {
String stringValue = value.getValue();
- if (stringValue.endsWith(Constants.NAME)) {
+ if (isNameAnnotationKey(stringValue)) {
key = processNameAnnotation(annotation, key, value, hasNamespaceAnnotation);
}
- if (stringValue.endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(stringValue)) {
hasNamespaceAnnotation = true;
key = processNamespaceAnnotation(annotation, key, value, namespaces);
}
@@ -702,7 +731,7 @@ private static BString processAnnotation(BMap annotation, Strin
private static void processSubRecordAnnotation(BMap annotation, BMap subRecord) {
BString[] keys = annotation.getKeys();
for (BString value : keys) {
- if (value.getValue().endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(value.getValue())) {
processNamespaceAnnotation(annotation, "", value, subRecord);
}
}
@@ -713,7 +742,7 @@ private static String getElementName(BMap annotation, String ke
BString[] keys = annotation.getKeys();
boolean hasNamespaceAnnotation = false;
for (BString value : keys) {
- if (value.getValue().endsWith(Constants.NAMESPACE)) {
+ if (isNamespaceAnnotationKey(value.getValue())) {
hasNamespaceAnnotation = true;
BMap namespaceAnnotation = (BMap) annotation.get(value);
BString prefix = (BString) namespaceAnnotation.get(Constants.PREFIX);
@@ -721,7 +750,10 @@ private static String getElementName(BMap annotation, String ke
key = prefix.getValue().concat(Constants.COLON).concat(key);
}
}
- if (value.getValue().endsWith(Constants.NAME)) {
+ }
+
+ for (BString value : keys) {
+ if (isNameAnnotationKey(value.getValue())) {
key = processNameAnnotation(annotation, key, value, hasNamespaceAnnotation);
}
}
@@ -782,6 +814,18 @@ private static String addAttributeToRecord(BString prefix, BString uri, String k
return prefix.getValue().concat(Constants.COLON).concat(key);
}
+ private static boolean isNamespaceAnnotationKey(String key) {
+ return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.NAMESPACE);
+ }
+
+ private static boolean isNameAnnotationKey(String key) {
+ return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.NAME);
+ }
+
+ private static boolean isAttributeAnnotationKey(String key) {
+ return key.startsWith(Constants.MODULE_NAME) && key.endsWith(Constants.ATTRIBUTE);
+ }
+
/**
* Holds data required for the traversing.
*