Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove some limitation from the output of toRecord() #418

Merged
merged 22 commits into from
Apr 18, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
632aa49
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 23, 2022
4f4bf86
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 23, 2022
503f24d
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 23, 2022
7e6a730
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 23, 2022
bc10245
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 24, 2022
fc7dbd4
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 24, 2022
e8128cd
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 24, 2022
594dfe2
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 25, 2022
390bff6
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 27, 2022
166f057
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 27, 2022
ebc382b
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 27, 2022
70b4391
Merge https://github.com/ballerina-platform/module-ballerina-xmldata …
kalaiyarasiganeshalingam Mar 28, 2022
c0cdd19
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 30, 2022
0d1b7d9
Update output of toRecord() impl
kalaiyarasiganeshalingam Mar 30, 2022
af822c0
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 30, 2022
b272805
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 30, 2022
669a811
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 30, 2022
a838971
[Automated] Update the native jar versions
kalaiyarasiganeshalingam Mar 30, 2022
124f122
Remove arrayEntryTag and attributePrefix initialization
kalaiyarasiganeshalingam Mar 30, 2022
cb281eb
Remove double quotes
kalaiyarasiganeshalingam Mar 31, 2022
f60ad19
Remove new JSON option initialization
kalaiyarasiganeshalingam Apr 12, 2022
1f2c6c3
Update the change log file
kalaiyarasiganeshalingam Apr 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ballerina/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
org = "ballerina"
name = "xmldata"
version = "2.2.1"
version = "2.2.2"
authors = ["Ballerina"]
keywords = ["xml", "json"]
repository = "https://github.com/ballerina-platform/module-ballerina-xmldata"
Expand All @@ -10,4 +10,4 @@ license = ["Apache-2.0"]
distribution = "2201.0.1"

[[platform.java11.dependency]]
path = "../native/build/libs/xmldata-native-2.2.1.jar"
path = "../native/build/libs/xmldata-native-2.2.2-SNAPSHOT.jar"
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ modules = [
[[package]]
org = "ballerina"
name = "xmldata"
version = "2.2.1"
version = "2.2.2"
dependencies = [
{org = "ballerina", name = "jballerina.java"},
{org = "ballerina", name = "test"}
Expand Down
84 changes: 84 additions & 0 deletions ballerina/tests/xml_from_json_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -769,3 +769,87 @@ isolated function testWithAttribute3() {
test:assertFail("failed to convert json to xml");
}
}

type Order record {
Invoice Invoice;
};

type Invoice record {
PurchesedItems PurchesedItems;
Address1 Address;
string _xmlns?;
string _xmlns\:ns?;
string _attr?;
string _ns\:attr?;
};

type PurchesedItems record {
Purchase[] PLine;
};

type Purchase record {
string|ItemCode ItemCode;
int Count;
};

type ItemCode record {
string _discount;
string \#content;
};

type Address1 record {
string StreetAddress;
string City;
int Zip;
string Country;
string _xmlns?;
};

@test:Config {
groups: ["fromJson"]
}
isolated function testfromjsonwithRecord() {
Order data = {
Invoice: {
PurchesedItems: {
PLine: [
{ItemCode: "223345", Count: 10},
{ItemCode: "223300", Count: 7},
{
ItemCode: {_discount: "22%", \#content: "200777"},
Count: 7
}
]
},
Address: {
StreetAddress: "20, Palm grove, Colombo 3",
City: "Colombo",
Zip: 300,
Country: "LK"
},
_attr: "attr-val",
_xmlns: "example.com",
_xmlns\:ns: "ns.com"
}
};
string expected = "<Invoice xmlns=\"example.com\" xmlns:ns=\"ns.com\" attr=\"attr-val\">" +
"<PurchesedItems>" +
"<PLine><ItemCode>223345</ItemCode><Count>10</Count></PLine>" +
"<PLine><ItemCode>223300</ItemCode><Count>7</Count></PLine>" +
"<PLine><ItemCode discount=\"22%\">200777</ItemCode><Count>7</Count></PLine>" +
"</PurchesedItems>" +
"<Address>" +
"<StreetAddress>20, Palm grove, Colombo 3</StreetAddress>" +
"<City>Colombo</City>" +
"<Zip>300</Zip>" +
"<Country>LK</Country>" +
"</Address>" +
"</Invoice>";
json jsonData = data.toJson();
xml?|error result = fromJson(jsonData, {attributePrefix: "_"});
if result is xml {
test:assertEquals(result.toString(), expected.toString());
} else {
test:assertFail("failed to convert json to xml");
}
}
54 changes: 10 additions & 44 deletions ballerina/tests/xml_to_record_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -52,40 +52,6 @@ isolated function testToRecordWithEscapedString() returns error? {
test:assertEquals(actual, expected, msg = "testToRecordWithEscapedString result incorrect");
}

type Order record {
Invoice Invoice;
};

type Invoice record {
PurchesedItems PurchesedItems;
Address1 Address;
string _xmlns?;
string _xmlns_ns?;
string _attr?;
string _ns_attr?;
};

type PurchesedItems record {
Purchase[] PLine;
};

type Purchase record {
string|ItemCode ItemCode;
int Count;
};

type ItemCode record {
string _discount;
};

type Address1 record {
string StreetAddress;
string City;
int Zip;
string Country;
string _xmlns?;
};

xml e2 = xml `<Invoice xmlns="example.com" attr="attr-val" xmlns:ns="ns.com" ns:attr="ns-attr-val">
<PurchesedItems>
<PLine><ItemCode>223345</ItemCode><Count>10</Count></PLine>
Expand All @@ -111,7 +77,7 @@ function testToRecordComplexXmlElement() returns error? {
{ItemCode: "223345", Count: 10},
{ItemCode: "223300", Count: 7},
{
ItemCode: {"_discount": "22%", "#content": "200777"},
ItemCode: {_discount: "22%", \#content: "200777"},
Count: 7
}
]
Expand All @@ -124,9 +90,9 @@ function testToRecordComplexXmlElement() returns error? {
_xmlns: ""
},
_xmlns: "example.com",
_xmlns_ns: "ns.com",
_xmlns\:ns: "ns.com",
_attr: "attr-val",
_ns_attr: "ns-attr-val"
_ns\:attr: "ns-attr-val"
}
};
Order actual = check toRecord(e2);
Expand All @@ -144,7 +110,7 @@ function testToRecordComplexXmlElementWithoutPreserveNamespaces() returns error?
{ItemCode: "223345", Count: 10},
{ItemCode: "223300", Count: 7},
{
ItemCode: {"_discount": "22%", "#content": "200777"},
ItemCode: {_discount: "22%", \#content: "200777"},
Count: 7
}
]
Expand Down Expand Up @@ -264,8 +230,8 @@ type r2 record {
};

type Root2 record {
string _xmlns_ns;
string _ns_x;
string _xmlns\:ns;
string _ns\:x;
string _x;
};

Expand All @@ -277,8 +243,8 @@ isolated function testToRecordWithMultipleAttributesAndNamespaces() returns Erro

r2 expected = {
Root: {
_xmlns_ns: "ns.com",
_ns_x: "y",
_xmlns\:ns: "ns.com",
_ns\:x: "y",
_x: "z"
}
};
Expand Down Expand Up @@ -476,7 +442,7 @@ type BookStore2 record {
Address3 address;
Codes codes;
string _status;
string _xmlns_ns0;
string _xmlns\:ns0;
};

@test:Config {
Expand Down Expand Up @@ -515,7 +481,7 @@ isolated function testToRecordWithNamespaces() returns error? {
codes: {
item: [4, 8, 9]
},
_xmlns_ns0: "http://sample.com/test",
_xmlns\:ns0: "http://sample.com/test",
_status: "online"
}
};
Expand Down
64 changes: 36 additions & 28 deletions ballerina/xmldata.bal
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public type JsonOptions record {
# successfully converted or else an `xmldata:Error`
public isolated function fromJson(json jsonValue, JsonOptions options = {}) returns xml?|Error {
if !isSingleNode(jsonValue) {
return getElement("root", check traverseNode(jsonValue, {}, options),
check getAttributesMap(jsonValue, options = options));
return getElement("root", check traverseNode(jsonValue, {}, options), options,
check getAttributesMap(jsonValue, options));
} else {
map<json>|error jMap = jsonValue.ensureType();
if jMap is map<json> {
Expand All @@ -54,15 +54,15 @@ public isolated function fromJson(json jsonValue, JsonOptions options = {}) retu
}
json value = jMap.toArray()[0];
if value is json[] {
return getElement("root", check traverseNode(value, {}, options, jMap.keys()[0]),
check getAttributesMap(value, options = options));
return getElement("root", check traverseNode(value, {}, options, jMap.keys()[0]), options,
check getAttributesMap(value, options));
} else {
string key = jMap.keys()[0];
if key == CONTENT {
return xml:createText(value.toString());
}
return getElement(jMap.keys()[0], check traverseNode(value, {}, options),
check getAttributesMap(value, options = options));
return getElement(jMap.keys()[0], check traverseNode(value, {}, options), options,
check getAttributesMap(value, options));
}
}
if jsonValue !is null {
Expand All @@ -73,23 +73,22 @@ public isolated function fromJson(json jsonValue, JsonOptions options = {}) retu
}
}

isolated function traverseNode(json jNode, map<string> parentNamespaces, JsonOptions options = {},
isolated function traverseNode(json jNode, map<string> parentNamespaces, JsonOptions options,
string? key = ()) returns xml|Error {
string arrayEntryTag = options.arrayEntryTag == "" ? "item" : options.arrayEntryTag;
string attributePrefix = options.attributePrefix == "" ? "@" : options.attributePrefix;
xml xNode = xml ``;
if jNode is map<json> {
foreach [string, json] [k, v] in jNode.entries() {
if !k.startsWith(attributePrefix) {
if !k.startsWith(options.attributePrefix) {
if k == CONTENT {
xNode += xml:createText(v.toString());
} else if v is json[] {
xml node = check traverseNode(v, check getNamespacesMap(v, parentNamespaces, options), options, k);
xml node = check traverseNode(v, check getNamespacesMap(v, options, parentNamespaces), options, k);
xNode += node;
} else {
xml node = check getElement(k, check traverseNode(v,
check getNamespacesMap(v, parentNamespaces, options), options),
check getAttributesMap(v, parentNamespaces, options = options));
check getNamespacesMap(v, options, parentNamespaces), options),
options,
check getAttributesMap(v, options, parentNamespaces));
xNode += node;
}
}
Expand All @@ -100,11 +99,12 @@ isolated function traverseNode(json jNode, map<string> parentNamespaces, JsonOpt
if (key is string) {
arrayEntryTagKey = key;
} else {
arrayEntryTagKey = arrayEntryTag;
arrayEntryTagKey = options.arrayEntryTag;
}
xml item = check getElement(arrayEntryTagKey, check traverseNode(i,
check getNamespacesMap(i, parentNamespaces, options)),
check getAttributesMap(i, parentNamespaces, options = options));
check getNamespacesMap(i, options, parentNamespaces), options),
options,
check getAttributesMap(i, options, parentNamespaces));
xNode += item;
}
} else {
Expand All @@ -124,9 +124,9 @@ isolated function isSingleNode(json node) returns boolean {
return true;
}

isolated function getElement(string name, xml children, map<string> attributes = {}, JsonOptions options = {})
isolated function getElement(string name, xml children, JsonOptions options, map<string> attributes = {})
returns xml|Error {
string attributePrefix = options.attributePrefix == "" ? "@" : options.attributePrefix;
string attributePrefix = options.attributePrefix;
xml:Element element;
int? index = name.indexOf(":");
if index is int {
Expand All @@ -149,9 +149,9 @@ isolated function getElement(string name, xml children, map<string> attributes =
return element;
}

isolated function getAttributesMap(json jTree, map<string> parentNamespaces = {}, JsonOptions options = {})
isolated function getAttributesMap(json jTree, JsonOptions options, map<string> parentNamespaces = {})
returns map<string>|Error {
string attributePrefix = options.attributePrefix == "" ? "@" : options.attributePrefix;
string attributePrefix = options.attributePrefix;
map<string> attributes = parentNamespaces.clone();
map<json>|error attr = jTree.ensureType();
if attr is map<json> {
Expand All @@ -160,9 +160,12 @@ isolated function getAttributesMap(json jTree, map<string> parentNamespaces = {}
if v is map<json> || v is json[] {
return error Error("attribute cannot be an object or array");
}
if k.startsWith(attributePrefix + "xmlns") {
string prefix = k.substring(<int>k.indexOf(":") + 1);
attributes[string `{${XMLNS_NAMESPACE_URI}}${prefix}`] = v.toString();
int? index = k.indexOf(":");
if index is int {
if k.startsWith(attributePrefix + "xmlns") {
string prefix = k.substring(index + 1);
attributes[string `{${XMLNS_NAMESPACE_URI}}${prefix}`] = v.toString();
}
} else {
attributes[k.substring(1)] = v.toString();
}
Expand All @@ -172,20 +175,25 @@ isolated function getAttributesMap(json jTree, map<string> parentNamespaces = {}
return attributes;
}

isolated function getNamespacesMap(json jTree, map<string> parentNamespaces = {}, JsonOptions options = {})
isolated function getNamespacesMap(json jTree, JsonOptions options, map<string> parentNamespaces = {})
returns map<string>|Error {
string attributePrefix = options.attributePrefix == "" ? "@" : options.attributePrefix;
string attributePrefix = options.attributePrefix;
map<string> namespaces = parentNamespaces.clone();
map<json>|error attr = jTree.ensureType();
if attr is map<json> {
foreach [string, json] [k, v] in attr.entries() {
if k.startsWith(attributePrefix) {
if v is map<json> || v is json[] {
return error Error("attribute cannot be an object or array");
return error Error("attribute cannot be an object or array.");
}
if k.startsWith(attributePrefix + "xmlns") {
string prefix = k.substring(<int>k.indexOf(":") + 1);
namespaces[string `{${XMLNS_NAMESPACE_URI}}${prefix}`] = v.toString();
int? index = k.indexOf(":");
if index is int {
string prefix = k.substring(index + 1);
namespaces[string `{${XMLNS_NAMESPACE_URI}}${prefix}`] = v.toString();
} else {
namespaces[string `{${XMLNS_NAMESPACE_URI}}`] = v.toString();
}
}
}
}
Expand Down
15 changes: 1 addition & 14 deletions docs/spec/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The conforming implementation of the specification is released and included in t
* 2.1 [Record](#23-record)
3. [Rules](#3-rules)
* 3.1 [Rules for XML to JSON Conversion](#31-rules-for-xml-to-json-conversion)
* 3.2 [Rules for XML to Record Conversion](#32-rules-for-xml-to-json-conversion)
* 3.2 [Rules for XML to Record Conversion](#32-rules-for-xml-to-record-conversion)
* 3.1 [Rules for JSON to XML Conversion](#33-rules-for-json-to-xml-conversion)
4. [Operations](#4-operations)
* 4.1 [XML to JSON Conversion](#41-xml-to-json-conversion)
Expand Down Expand Up @@ -235,19 +235,6 @@ The following API returns the record to the given XML structure by configuring t
public isolated function toRecord(xml xmlValue, boolean preserveNamespaces = true, typedesc<record {}> returnType = <>) returns returnType|Error
```

The XML value may not have a key to convert JSON data. Hence, Ballerina uses a default key as a `#content` to handle this case.

Let's consider this,
```ballerina
xml x3 = xml `<ns0:bookStore status="online" xmlns:ns0="http://sample.com/test">Book</ns0:bookStore>`;
record{} result = check xmldata:toRecord(x3);
```
Output of this is,
```ballerina
{"ns0:bookStore":{"_xmlns_ns0":"http://sample.com/test","_status":"online","#content":"Book"}}
```
Here, `Book` does not have a key. So, JSON data introduces a key as `#content`. but it is not mapped to any field in the Ballerina record. If the user needs to convert these XML values to a Ballerina record, it should be an open record with other fields.

#### 4.2.1 Sample

```ballerina
Expand Down
Loading