Skip to content

Commit

Permalink
Merge pull request #42589 from mindula/fix-42556
Browse files Browse the repository at this point in the history
[Master] Fix record generation for xml with multiple namespaces
  • Loading branch information
mindula authored Jun 4, 2024
2 parents 6f4b966 + 8bbaeaa commit 14f09a3
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
Expand Down Expand Up @@ -250,9 +252,34 @@ private static List<Node> getRecordFieldsForXMLElement(Element xmlElement, boole
generateRecords(xmlElementNode, isClosed, recordToTypeDescNodes, recordToAnnotationNodes,
recordToElementNodes, diagnosticMessages, textFieldName, withNameSpace);
}
RecordFieldNode recordField = getRecordField(xmlElementNode, false, withNameSpace);
if (recordFields.stream().anyMatch(recField -> ((RecordFieldNode) recField).fieldName().text()
.equals(recordField.fieldName().text()))) {
Map<String, Boolean> prefixMap = hasMultipleFieldsWithSameName(xmlNodeList,
xmlElementNode.getLocalName());
RecordFieldNode recordField = getRecordField(xmlElementNode, false, withNameSpace,
prefixMap.size() > 1);

if (withNameSpace && xmlElementNode.getPrefix() != null) {
int indexOfRecordFieldNode = IntStream.range(0, recordFields.size())
.filter(j -> ((RecordFieldNode) recordFields.get(j)).fieldName().text()
.equals(recordField.fieldName().text())
&& prefixMap.get(xmlElementNode.getPrefix())).findFirst().orElse(-1);
if (indexOfRecordFieldNode == -1) {
if (prefixMap.size() > 1) {
generateRecordFieldForSameLocalNameElements(recordFields, xmlElementNode, recordField);
} else {
recordFields.add(recordField);
}
} else {
RecordFieldNode existingRecordField =
(RecordFieldNode) recordFields.remove(indexOfRecordFieldNode);
RecordFieldNode updatedRecordField = mergeRecordFields(existingRecordField, recordField);
if (prefixMap.size() > 1) {
generateRecordFieldForSameLocalNameElements(recordFields, xmlElementNode,
updatedRecordField);
} else {
recordFields.add(indexOfRecordFieldNode, updatedRecordField);
}
}
} else {
int indexOfRecordFieldNode = IntStream.range(0, recordFields.size())
.filter(j -> ((RecordFieldNode) recordFields.get(j)).fieldName().text()
.equals(recordField.fieldName().text())).findFirst().orElse(-1);
Expand All @@ -264,8 +291,6 @@ private static List<Node> getRecordFieldsForXMLElement(Element xmlElement, boole
RecordFieldNode updatedRecordField = mergeRecordFields(existingRecordField, recordField);
recordFields.add(indexOfRecordFieldNode, updatedRecordField);
}
} else {
recordFields.add(recordField);
}
}
}
Expand Down Expand Up @@ -315,6 +340,41 @@ private static List<Node> getRecordFieldsForXMLElement(Element xmlElement, boole
return recordFields;
}

private static void generateRecordFieldForSameLocalNameElements(List<Node> recordFields, Element xmlElementNode,
RecordFieldNode recordField) {
recordFields.add(recordField.modify().withFieldName(
AbstractNodeFactory.createIdentifierToken(
xmlElementNode.getPrefix() +
xmlElementNode.getLocalName().substring(0, 1).toUpperCase(Locale.ENGLISH) +
xmlElementNode.getLocalName().substring(1))).apply());
}

/**
* This method checks whether there are multiple fields with the same name in the provided NodeList.
*
* @param xmlNodeList NodeList of XML nodes
* @param fieldName Field name to be checked
* @return {@link Map<String, Boolean>} Map of prefixes and whether there are multiple fields with the same name
*/
private static Map<String, Boolean> hasMultipleFieldsWithSameName(org.w3c.dom.NodeList xmlNodeList,
String fieldName) {
String defaultNamespace = "";
Map<String, Boolean> prefixMap = new HashMap<>();
for (int i = 0; i < xmlNodeList.getLength(); i++) {
org.w3c.dom.Node xmlNode = xmlNodeList.item(i);
if (xmlNode.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE) {
continue;
}
Element xmlElementNode = (Element) xmlNode;
if (!xmlNode.getLocalName().equals(fieldName)) {
continue;
}
String prefix = xmlElementNode.getPrefix() == null ? defaultNamespace : xmlElementNode.getPrefix();
prefixMap.put(prefix, prefixMap.containsKey(prefix));
}
return prefixMap;
}

/**
* This method returns a List of RecordFieldNodes that are updated if already a Record with same name exists.
*
Expand Down Expand Up @@ -393,7 +453,7 @@ private static List<Node> updateRecordFields(Map<String, RecordFieldNode> previo
}

private static RecordFieldNode getRecordField(Element xmlElementNode, boolean isOptionalField,
boolean withNameSpace) {
boolean withNameSpace, boolean sameFieldExists) {
Token typeName;
Token questionMarkToken = AbstractNodeFactory.createToken(SyntaxKind.QUESTION_MARK_TOKEN);
IdentifierToken fieldName =
Expand All @@ -409,8 +469,14 @@ private static RecordFieldNode getRecordField(Element xmlElementNode, boolean is
String type = getRecordName(elementKey);
typeName = AbstractNodeFactory.createIdentifierToken(type);
}
List<AnnotationNode> xmlNameNode = List.of(getXMLNamespaceNode(xmlElementNode.getPrefix(),
xmlElementNode.getNamespaceURI()));
List<AnnotationNode> xmlNameNode = new ArrayList<>();
if (sameFieldExists) {
xmlNameNode.add(getXMLNameNode(xmlElementNode.getLocalName()));
xmlNameNode.add(getXMLNamespaceNode(xmlElementNode.getPrefix(), xmlElementNode.getNamespaceURI()));
} else {
xmlNameNode.add(getXMLNamespaceNode(xmlElementNode.getPrefix(), xmlElementNode.getNamespaceURI()));
}

NodeList<AnnotationNode> annotationNodes = NodeFactory.createNodeList(xmlNameNode);
MetadataNode metadataNode = NodeFactory.createMetadataNode(null, annotationNodes);
TypeDescriptorNode fieldTypeName = NodeFactory.createBuiltinSimpleNameReferenceNode(typeName.kind(), typeName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,31 @@ public class XMLToRecordConverterTests {
private final Path sample27Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_27.bal");

private final Path sample28XML = RES_DIR.resolve(XML_DIR)
.resolve("sample_28.xml");
private final Path sample28Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_28.bal");

private final Path sample29XML = RES_DIR.resolve(XML_DIR)
.resolve("sample_29.xml");
private final Path sample29Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_29.bal");

private final Path sample30XML = RES_DIR.resolve(XML_DIR)
.resolve("sample_30.xml");
private final Path sample30Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_30.bal");

private final Path sample31XML = RES_DIR.resolve(XML_DIR)
.resolve("sample_31.xml");
private final Path sample31Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_31.bal");

private final Path sample32XML = RES_DIR.resolve(XML_DIR)
.resolve("sample_32.xml");
private final Path sample32Bal = RES_DIR.resolve(BAL_DIR)
.resolve("sample_32.bal");

private static final String XMLToRecordServiceEP = "xmlToRecord/convert";


Expand Down Expand Up @@ -451,4 +476,49 @@ public void testXMLWithoutNamespaceAnnotations() throws IOException {
String expectedCodeBlock = Files.readString(sample27Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}

@Test(description = "testXMLWithMultipleNamespacesAndSameElement")
public void testXMLWithMultipleNamespacesAndSameElement() throws IOException {
String xmlFileContent = Files.readString(sample28XML);
String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false,
null, true).getCodeBlock().replaceAll("\\s+", "");
String expectedCodeBlock = Files.readString(sample28Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}

@Test(description = "testXMLWithMultipleNamespaces2")
public void testXMLWithMultipleNamespaces2() throws IOException {
String xmlFileContent = Files.readString(sample29XML);
String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false,
null, true).getCodeBlock().replaceAll("\\s+", "");
String expectedCodeBlock = Files.readString(sample29Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}

@Test(description = "testXMLWithMultipleNamespaces3")
public void testXMLWithMultipleNamespaces3() throws IOException {
String xmlFileContent = Files.readString(sample30XML);
String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false,
null, true).getCodeBlock().replaceAll("\\s+", "");
String expectedCodeBlock = Files.readString(sample30Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}

@Test(description = "testXMLWithoutNamespaceAnnotation")
public void testXMLWithoutNamespaceAnnotation() throws IOException {
String xmlFileContent = Files.readString(sample31XML);
String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false,
null, false).getCodeBlock().replaceAll("\\s+", "");
String expectedCodeBlock = Files.readString(sample31Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}

@Test(description = "testXMLWithSameElementAndWithoutMultipleNamespaces")
public void testXMLWithSameElementAndWithoutMultipleNamespaces() throws IOException {
String xmlFileContent = Files.readString(sample32XML);
String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false,
null, false).getCodeBlock().replaceAll("\\s+", "");
String expectedCodeBlock = Files.readString(sample32Bal).replaceAll("\\s+", "");
Assert.assertEquals(generatedCodeBlock, expectedCodeBlock);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@xmldata:Name {value: "book"}
@xmldata:Namespace {prefix: "bk", uri: "http://example.com/book"}
type Bk_Book record {
@xmldata:Name {value: "title"}
@xmldata:Namespace {prefix: "bk", uri: "http://example.com/book"}
string bkTitle;
@xmldata:Name {value: "title"}
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
string auTitle;
@xmldata:Name {value: "title"}
@xmldata:Namespace {prefix: "da", uri: "http://example.com/date"}
string daTitle;
@xmldata:Namespace {prefix: "bk", uri: "http://example.com/book"}
int chapter;
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
string country;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type Lib_Book record {
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
int id;
@xmldata:Name {value: "title"}
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
string libTitle;
@xmldata:Name {value: "title"}
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
string auTitle;
@xmldata:Name {value: "author"}
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
string libAuthor;
@xmldata:Name {value: "author"}
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
string auAuthor;
};

@xmldata:Name {value: "library"}
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
type Lib_Library record {
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
Lib_Book book;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type Lib_Book record {
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
string[] title;
};

type Au_Author record {
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
string[] name;
};

type Pr_Price record {
@xmldata:Namespace {prefix: "pr", uri: "http://example.com/price"}
(decimal|int)[] price;
};

@xmldata:Name {value: "library"}
type Library record {
@xmldata:Namespace {prefix: "lib", uri: "http://example.com/library"}
Lib_Book book;
@xmldata:Namespace {prefix: "au", uri: "http://example.com/author"}
Au_Author author;
@xmldata:Namespace {prefix: "pr", uri: "http://example.com/price"}
Pr_Price price;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
type St_Product record {
string[] name;
};

type Mk_Brand record {
string name;
string counry;
int year;
};

type Mk_Maker record {
Mk_Brand[] brand;
};

type Pr_Cost record {
int[] amount;
};

@xmldata:Name {value: "store"}
type Store record {
St_Product product;
Mk_Maker maker;
Pr_Cost cost;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type Bk_Chapter record {
int[] number;
};

@xmldata:Name {value: "book"}
type Bk_Book record {
string[] title;
string subtitle;
string language;
Bk_Chapter[] chapter;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<bk:book xmlns:bk="http://example.com/book" xmlns:au="http://example.com/author" xmlns:da="http://example.com/date">
<bk:title>string</bk:title>
<au:title>string</au:title>
<da:title>string</da:title>
<bk:chapter>1</bk:chapter>
<au:country>string</au:country>
</bk:book>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<lib:library xmlns:lib="http://example.com/library" xmlns:au="http://example.com/author">
<lib:book>
<lib:id>601971</lib:id>
<lib:title>string</lib:title>
<au:title>Title</au:title>
<lib:author>Name</lib:author>
<au:author>Name</au:author>
</lib:book>
</lib:library>
14 changes: 14 additions & 0 deletions misc/xml-to-record-converter/src/test/resources/xml/sample_30.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<library xmlns:lib="http://example.com/library" xmlns:au="http://example.com/author" xmlns:pr="http://example.com/price">
<lib:book>
<lib:title>string</lib:title>
<lib:title>string</lib:title>
</lib:book>
<au:author>
<au:name>string</au:name>
<au:name>string</au:name>
</au:author>
<pr:price>
<pr:price>1000</pr:price>
<pr:price>1000.50</pr:price>
</pr:price>
</library>
22 changes: 22 additions & 0 deletions misc/xml-to-record-converter/src/test/resources/xml/sample_31.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<store xmlns:st="http://example.com/store" xmlns:mk="http://example.com/maker" xmlns:pr="http://example.com/price">
<st:product>
<st:name>Laptop</st:name>
<st:name>Desktop</st:name>
</st:product>
<mk:maker>
<mk:brand>
<mk:name>Dell</mk:name>
<st:counry>USA</st:counry>
<year>2024</year>
</mk:brand>
<mk:brand>
<mk:name>HP</mk:name>
<st:counry>USA</st:counry>
<year>2023</year>
</mk:brand>
</mk:maker>
<pr:cost>
<pr:amount>1000</pr:amount>
<pr:amount>800</pr:amount>
</pr:cost>
</store>
17 changes: 17 additions & 0 deletions misc/xml-to-record-converter/src/test/resources/xml/sample_32.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<bk:book xmlns:bk="http://example.com/book" xmlns:au="http://example.com/author" xmlns:da="http://example.com/date">
<bk:title>string</bk:title>
<au:title>string</au:title>
<da:title>string</da:title>
<bk:subtitle>string</bk:subtitle>
<bk:language>string</bk:language>
<bk:chapter>
<bk:number>1</bk:number>
<au:number>1</au:number>
<da:number>1</da:number>
</bk:chapter>
<bk:chapter>
<bk:number>1</bk:number>
<au:number>1</au:number>
<da:number>1</da:number>
</bk:chapter>
</bk:book>

0 comments on commit 14f09a3

Please sign in to comment.