Skip to content

Commit

Permalink
Use localpart to validate fieldnames
Browse files Browse the repository at this point in the history
  • Loading branch information
prakanth97 committed Nov 1, 2023
1 parent 79ab095 commit 5977491
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 26 deletions.
124 changes: 119 additions & 5 deletions ballerina/tests/fromXml_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ function testXmlStringToRecord31() returns error? {
}

type Rec record {|
string example\:element1;
string element1;
string element2;
|};

Expand All @@ -381,10 +381,85 @@ function testXmlStringToRecord32() returns error? {
</root>`;

Rec rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.example\:element1, "Value 1");
test:assertEquals(rec1.element1, "Value 1");
test:assertEquals(rec1.element2, "Value 2");
}

@Namespace {
prefix: "x",
uri: "example.com"
}
type NSRec1 record {|
string \#content;
|};

@test:Config{}
function testXmlStringToRecord33() returns error? {
string xmlStr1 = string `<x:foo xmlns:x="example.com">1</x:foo>`;
NSRec1 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.get("#content"), "1");
}

@Namespace {
prefix: "x",
uri: "example.com"
}
type NSRec2 record {|
@Namespace {
prefix: "x",
uri: "example.com"
}
string bar;
|};

@test:Config{}
function testXmlStringToRecord34() returns error? {
string xmlStr1 = string `<x:foo xmlns:x="example.com"><x:bar>1</x:bar></x:foo>`;
NSRec2 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.bar, "1");
}

@Namespace {
prefix: "x",
uri: "example.com"
}
type NSRec3 record {|
@Namespace {
prefix: "x",
uri: "example.com"
}
record {|
@Namespace {
uri: "example2.com"
}
string baz;
|} bar;
|};

@test:Config{}
function testXmlStringToRecord35() returns error? {
string xmlStr1 = string `<x:foo xmlns:x="example.com" xmlns="example2.com"><x:bar><baz>2</baz></x:bar></x:foo>`;
NSRec3 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.bar.baz, "2");
}

@Namespace {
uri: "example.com"
}
type NSRec4 record {|
@Namespace {
uri: "example.com"
}
string bar;
|};

@test:Config{}
function testXmlStringToRecord36() returns error? {
string xmlStr1 = string `<foo xmlns="example.com"><bar>1</bar></foo>`;
NSRec4 rec1 = check fromXmlStringWithType(xmlStr1);
test:assertEquals(rec1.bar, "1");
}

// Test projection with fixed array size.

type DataProj record {|
Expand Down Expand Up @@ -542,9 +617,6 @@ function testXmlStringToRecordNegative8() {
}

type DataN7 record {|
@Name {
value: "ns1:A"
}
@Namespace {
uri: "www.example.com"
}
Expand All @@ -557,3 +629,45 @@ function testXmlStringToRecordNegative9() {
DataN7|error rec1 = fromXmlStringWithType(xmlStr1);
test:assertEquals((<error>rec1).message(), "namespace mismatched for the field: A");
}

@Namespace {
prefix: "x",
uri: "example.com"
}
type DataN8 record {|
@Namespace {
uri: "example.com"
}
string bar;
|};

@test:Config{}
function testXmlStringToRecordNegative10() {
string xmlStr1 = string `<x:foo xmlns:x="example.com"><x:bar>1</x:bar></x:foo>`;
DataN8|error rec1 = fromXmlStringWithType(xmlStr1);
test:assertEquals((<error>rec1).message(), "namespace mismatched for the field: bar");
}

@Namespace {
prefix: "x",
uri: "example.com"
}
type DataN9 record {|
@Namespace {
prefix: "x",
uri: "example.com"
}
record {|
@Namespace {
uri: "example.com"
}
string baz;
|} bar;
|};

@test:Config{}
function testXmlStringToRecordNegative11() {
string xmlStr1 = string `<x:foo xmlns:x="example.com" xmlns="example2.com"><x:bar><baz>2</baz></x:bar></x:foo>`;
DataN9|error rec1 = fromXmlStringWithType(xmlStr1);
test:assertEquals((<error>rec1).message(), "namespace mismatched for the field: baz");
}
69 changes: 48 additions & 21 deletions native/src/main/java/io/ballerina/stdlib/data/xml/XmlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ private void parseRootElement(XMLStreamReader xmlStreamReader, XmlParserData xml
xmlParserData.rootElement =
validateAndGetXmlNameFromRecordAnnotation(rootRecord, rootRecord.getName(), elementName);

validateNamespace(xmlStreamReader, rootRecord, false, xmlParserData);
validateTypeNamespace(xmlStreamReader, rootRecord);

// Keep track of fields and attributes
xmlParserData.fieldHierarchy.push(new HashMap<>(getAllFieldsInRecordType(rootRecord, xmlParserData)));
Expand Down Expand Up @@ -391,10 +391,9 @@ private void readElement(XMLStreamReader xmlStreamReader, XmlParserData xmlParse
return;
}

validateNamespace(xmlStreamReader, xmlParserData.rootRecord, true, xmlParserData);

Field currentField = xmlParserData.currentField;
String fieldName = currentField.getFieldName();
validateFieldNamespace(xmlStreamReader, xmlParserData.rootRecord, fieldName, xmlParserData);
Object temp = currentNode.get(StringUtils.fromString(fieldName));
if (!xmlParserData.siblings.containsKey(elemName)) {
xmlParserData.siblings.put(elemName, false);
Expand Down Expand Up @@ -428,7 +427,7 @@ private void updateNextRecord(XMLStreamReader xmlStreamReader, XmlParserData xml
xmlParserData.rootRecord = recordType;
updateNextValue(recordType, fieldName, fieldType, xmlParserData);
xmlParserData.attributeHierarchy.push(new HashMap<>(getAllAttributesInRecordType(recordType)));
validateNamespace(xmlStreamReader, recordType, false, xmlParserData);
validateTypeNamespace(xmlStreamReader, recordType);
handleAttributes(xmlStreamReader, xmlParserData);
}

Expand Down Expand Up @@ -646,7 +645,14 @@ private Map<String, Field> getAllFieldsInRecordType(RecordType recordType, XmlPa
Map<String, Field> fields = new HashMap<>();
Map<String, Field> recordFields = recordType.getFields();
for (String key : recordFields.keySet()) {
fields.put(modifiedNames.getOrDefault(key, key), recordFields.get(key));
String modifiedName = modifiedNames.getOrDefault(key, key);
if (fields.containsKey(modifiedName)) {
// TODO: Handle the cases with different namespace by representing field with QName.
// eg:- <x:foo xmlns:x="example.com" xmlns:y="example2.com" ><x:bar></x:bar><y:bar></y:bar></x:foo>
throw DataUtils.getXmlError("Duplicate field '" + modifiedName + "'");
}

fields.put(modifiedName, recordFields.get(key));
}
xmlParserData.modifiedNamesHierarchy.add(modifiedNames);
return fields;
Expand All @@ -661,9 +667,8 @@ private String getModifiedName(Map<BString, Object> fieldAnnotation, String attr
return attributeName;
}

private void validateNamespace(XMLStreamReader xmlStreamReader, RecordType recordType, boolean isField,
XmlParserData xmlParserData) {
ArrayList<String> namespace = getNamespace(recordType, isField);
private void validateTypeNamespace(XMLStreamReader xmlStreamReader, RecordType recordType) {
ArrayList<String> namespace = getNamespace(recordType);

if (namespace.isEmpty()) {
return;
Expand All @@ -673,29 +678,54 @@ private void validateNamespace(XMLStreamReader xmlStreamReader, RecordType recor
&& xmlStreamReader.getName().getNamespaceURI().equals(namespace.get(1))) {
return;
}
throw DataUtils.getXmlError("namespace mismatched for the type: "
+ recordType.getName());
}

if (isField) {
throw DataUtils.getXmlError("namespace mismatched for the field: "
+ xmlParserData.currentField.getFieldName());
} else {
throw DataUtils.getXmlError("namespace mismatched for the type: "
+ recordType.getName());
private void validateFieldNamespace(XMLStreamReader xmlStreamReader, RecordType recordType, String fieldName,
XmlParserData xmlParserData) {
ArrayList<String> namespace = getFieldNamespace(recordType, fieldName);

if (namespace.isEmpty()) {
return;
}

if (xmlStreamReader.getName().getPrefix().equals(namespace.get(0))
&& xmlStreamReader.getName().getNamespaceURI().equals(namespace.get(1))) {
return;
}
throw DataUtils.getXmlError("namespace mismatched for the field: " + xmlParserData.currentField.getFieldName());
}

private ArrayList<String> getNamespace(RecordType recordType, boolean isField) {
private ArrayList<String> getNamespace(RecordType recordType) {
BMap<BString, Object> annotations = recordType.getAnnotations();
String namespacePrefix = null;
String namespaceUri = null;
for (BString annotationsKey : annotations.getKeys()) {
String key = annotationsKey.getValue();
if (!isField && !key.contains(Constants.FIELD) && key.endsWith(Constants.NAME_SPACE)) {
if (!key.contains(Constants.FIELD) && key.endsWith(Constants.NAME_SPACE)) {
BMap<BString, Object> namespaceAnnotation = (BMap<BString, Object>) annotations.get(annotationsKey);
namespacePrefix = namespaceAnnotation.containsKey(Constants.PREFIX) ?
((BString) namespaceAnnotation.get(Constants.PREFIX)).getValue() : "";
namespaceUri = ((BString) namespaceAnnotation.get(Constants.URI)).getValue();
break;
} else if (isField && key.contains(Constants.FIELD)) {
}
}
ArrayList<String> namespace = new ArrayList<>();
if (namespacePrefix != null && namespaceUri != null) {
namespace.add(namespacePrefix);
namespace.add(namespaceUri);
}
return namespace;
}

private ArrayList<String> getFieldNamespace(RecordType recordType, String fieldName) {
BMap<BString, Object> annotations = recordType.getAnnotations();
String namespacePrefix = null;
String namespaceUri = null;
for (BString annotationsKey : annotations.getKeys()) {
String key = annotationsKey.getValue();
if (key.contains(Constants.FIELD) && key.split("\\$field\\$\\.")[1].equals(fieldName)) {
BMap<BString, Object> namespaceAnnotation = (BMap<BString, Object>) annotations.get(annotationsKey);
for (BString keyStr : namespaceAnnotation.getKeys()) {
if (keyStr.getValue().endsWith(Constants.NAME_SPACE)) {
Expand Down Expand Up @@ -770,10 +800,7 @@ private Optional<Object> handleRecordRestType(XmlParserData xmlParserData, XMLSt
}

private String getElementName(XMLStreamReader xmlStreamReader) {
QName qName = xmlStreamReader.getName();
String prefix = qName.getPrefix();
String attributeName = qName.getLocalPart();
return prefix.equals("") ? attributeName : prefix + ":" + attributeName;
return xmlStreamReader.getName().getLocalPart();
}

static class XmlParserData {
Expand Down

0 comments on commit 5977491

Please sign in to comment.