Skip to content

Commit bd9ed7a

Browse files
authored
Merge pull request #15 from Nuvindu/xsd-fix
Fix handling root elements with base64 type
2 parents 5cc9223 + 7358cfa commit bd9ed7a

File tree

6 files changed

+89
-36
lines changed

6 files changed

+89
-36
lines changed

xsd-core/src/main/java/io/ballerina/xsd/core/XSDToRecord.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import static io.ballerina.xsd.core.visitor.VisitorUtils.COMMA;
4343
import static io.ballerina.xsd.core.visitor.VisitorUtils.OPEN_BRACES;
4444
import static io.ballerina.xsd.core.visitor.VisitorUtils.QUOTATION_MARK;
45+
import static io.ballerina.xsd.core.visitor.VisitorUtils.STRING;
4546
import static io.ballerina.xsd.core.visitor.VisitorUtils.WHITESPACE;
4647
import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.ENUM;
4748
import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.NAME;
@@ -156,11 +157,17 @@ public static void processRootElements(Map<String, ModuleMemberDeclarationNode>
156157
for (Map.Entry<String, String> entry : rootElements.entrySet()) {
157158
String element = entry.getKey();
158159
String type = entry.getValue();
159-
String[] tokens = nodes.get(type).toString().split(WHITESPACE);
160-
if (!nodes.get(type).toString().contains(RECORD_WITH_OPEN_BRACE)) {
161-
Utils.processSingleTypeElements(nodes, element, type, tokens, CONTENT_FIELD);
160+
if (nodes.containsKey(type)) {
161+
String[] tokens = nodes.get(type).toString().split(WHITESPACE);
162+
if (!nodes.get(type).toString().contains(RECORD_WITH_OPEN_BRACE)) {
163+
Utils.processSingleTypeElements(nodes, element, type, tokens, CONTENT_FIELD);
164+
} else {
165+
Utils.processRecordTypeElements(nodes, element, type, CONTENT_FIELD);
166+
}
162167
} else {
163-
Utils.processRecordTypeElements(nodes, element, type, CONTENT_FIELD);
168+
String rootElement = nodes.get(element).toString().replace(type, STRING);
169+
ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(rootElement);
170+
nodes.put(element, moduleNode);
164171
}
165172
}
166173
}

xsd-core/src/main/java/io/ballerina/xsd/core/visitor/VisitorUtils.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public final class VisitorUtils {
9494
private static final String WHITESPACE_PATTERN = "\\s";
9595
private static final String SPECIAL_CHARS_PATTERN = "[!@$%^&*()_\\-|]";
9696
public static final String NMTOKEN = "NMTOKEN";
97+
public static final String IDREF = "IDREF";
9798

9899
public static String addNamespace(XSDVisitorImpl xsdVisitor, String namespace) {
99100
if (Objects.equals(namespace, EMPTY_STRING)) {
@@ -171,7 +172,7 @@ public static String deriveType(Node node) {
171172
public static String typeGenerator(String typeName) {
172173
switch (typeName) {
173174
case TIME, DATE_TIME, DATE, G_YEAR_MONTH, G_YEAR, STRING, LANGUAGE,
174-
DURATION, ANY_URI, G_MONTH_DAY, NMTOKEN -> {
175+
DURATION, ANY_URI, G_MONTH_DAY, NMTOKEN, IDREF -> {
175176
return STRING;
176177
}
177178
case INTEGER, LONG, NEGATIVE_INTEGER, NON_POSITIVE_INTEGER, POSITIVE_INTEGER, SHORT,
@@ -238,4 +239,18 @@ public static String sanitizeString(String input) {
238239
}
239240
return keyPart + " = \"" + input + "\"";
240241
}
242+
243+
public static String resolveNames(String input) {
244+
if (!input.matches(INVALID_CHARS_PATTERN)
245+
|| (input.matches(DIGIT_PATTERN) && !input.matches(STARTS_WITH_DIGIT_PATTERN))) {
246+
return input;
247+
}
248+
if (input.matches(STARTS_WITH_DIGIT_PATTERN)) {
249+
input = UNDERSCORE + input;
250+
}
251+
for (String placeholder : Arrays.asList(SLASH_PATTERN, WHITESPACE_PATTERN, SPECIAL_CHARS_PATTERN)) {
252+
input = input.replaceAll(placeholder, UNDERSCORE);
253+
}
254+
return input;
255+
}
241256
}

xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import static io.ballerina.xsd.core.visitor.VisitorUtils.handleMinOccurrences;
5151
import static io.ballerina.xsd.core.visitor.VisitorUtils.isSimpleType;
5252
import static io.ballerina.xsd.core.visitor.VisitorUtils.convertToCamelCase;
53+
import static io.ballerina.xsd.core.visitor.VisitorUtils.resolveNames;
5354
import static io.ballerina.xsd.core.visitor.VisitorUtils.sanitizeString;
5455
import static io.ballerina.xsd.core.visitor.VisitorUtils.typeGenerator;
5556

@@ -99,6 +100,8 @@ public class XSDVisitorImpl implements XSDVisitor {
99100
public static final String EMPTY_STRING = "";
100101
public static final String RECORD_WITH_OPEN_BRACE = "record {|";
101102
public static final String REQUIRED_FIELD_NOT_FOUND_ERROR = "Required field is not found in <complexType>: '%s'";
103+
public static final String ELEMENT_NAME_NOT_FOUND_ERROR = "Missing name attribute for the root element of '%s'";
104+
public static final String ATTRIBUTE_NOT_FOUND_ERROR = "Required attribute is not found: '%s'";
102105
public static final String ONE = "1";
103106
public static final String XMLDATA_CHOICE = "@xmldata:Choice";
104107
public static final String CHOICE_NAME = "ChoiceOption";
@@ -115,12 +118,12 @@ public class XSDVisitorImpl implements XSDVisitor {
115118
public static final String REF = "ref";
116119

117120
private final ArrayList<String> imports = new ArrayList<>();
118-
private Map<String, String> extensions = new LinkedHashMap<>();
119-
private Map<String, String> rootElements = new LinkedHashMap<>();
121+
private final Map<String, String> extensions = new LinkedHashMap<>();
122+
private final Map<String, String> rootElements = new LinkedHashMap<>();
120123
private final Map<String, String> nameResolvers = new LinkedHashMap<>();
121-
private ArrayList<String> simpleTypeNames = new ArrayList<>();
122-
private Map<String, String> nestedElements = new LinkedHashMap<>();
123-
private Map<String, ArrayList<String>> enumerationElements = new LinkedHashMap<>();
124+
private final ArrayList<String> simpleTypeNames = new ArrayList<>();
125+
private final Map<String, String> nestedElements = new LinkedHashMap<>();
126+
private final Map<String, ArrayList<String>> enumerationElements = new LinkedHashMap<>();
124127
private final List<XsdDiagnostic> diagnostics = new ArrayList<>();
125128
private String targetNamespace;
126129

@@ -151,7 +154,11 @@ public String visit(Element element, boolean subElement) {
151154
if (component.isEmpty()) {
152155
continue;
153156
}
154-
if (component.get() instanceof SimpleType) {
157+
if (component.get() instanceof SimpleType simpleType) {
158+
if (nameNode == null) {
159+
throw new Exception(String.format(ELEMENT_NAME_NOT_FOUND_ERROR,
160+
simpleType.getNode().getNodeName()));
161+
}
155162
return handleNestedSimpleTypes(builder, nameNode, component.get());
156163
}
157164
}
@@ -170,7 +177,7 @@ public String visit(Element element, boolean subElement) {
170177
} else if (typeNode != null && node.hasAttributes()) {
171178
handleFixedValues(node, builder, typeNode);
172179
handleMaxOccurrences(node, builder);
173-
builder.append(nameNode.getNodeValue());
180+
builder.append(resolveNames(nameNode.getNodeValue()));
174181
handleMinOccurrences(element, builder);
175182
handleDefaultValues(node, builder, typeNode);
176183
}
@@ -333,21 +340,22 @@ private Node visitNestedElements(Node node, Node nameNode, Node typeNode) throws
333340
typeNode = nameNode;
334341
for (int i = 0; i < node.getAttributes().getLength(); i++) {
335342
Node attribute = node.getAttributes().item(i);
336-
if (attribute.getNodeName().equals(TYPE)) {
343+
if (attribute.getNodeName().equals(TYPE) || attribute.getNodeName().equals(REF)) {
337344
typeNode = attribute;
338-
} else if (attribute.getNodeName().equals(REF)) {
339-
typeNode = attribute;
340345
}
341346
}
342347
}
343348
return typeNode;
344349
}
345350

346-
public String visitAttribute(Node attribute) {
351+
public String visitAttribute(Node attribute) throws Exception {
347352
StringBuilder builder = new StringBuilder();
348353
this.addImports(BALLERINA_XML_DATA_MODULE);
349354
Node nameNode = attribute.getAttributes().getNamedItem(NAME);
350355
Node typeNode = attribute.getAttributes().getNamedItem(TYPE);
356+
if (nameNode == null) {
357+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, NAME));
358+
}
351359
builder.append(ATTRIBUTE_ANNOTATION).append(WHITESPACE);
352360
Node fixedNode = attribute.getAttributes().getNamedItem(FIXED);
353361
Node defaultNode = attribute.getAttributes().getNamedItem(DEFAULT);
@@ -372,11 +380,11 @@ public String visitAttribute(Node attribute) {
372380
}
373381

374382
public String visitAttributeChildNodes(NodeList childNodes) {
375-
for (int i = 0; i < childNodes.getLength(); i++) {
376-
if (childNodes.item(i).getNodeType() != Node.ELEMENT_NODE) {
383+
384+
for (Node childNode: asIterable(childNodes)) {
385+
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
377386
continue;
378387
}
379-
Node childNode = childNodes.item(i);
380388
if (childNode.getLocalName().equals(SIMPLE_TYPE)) {
381389
for (Node simpleTypeNode : asIterable(childNode.getChildNodes())) {
382390
if (simpleTypeNode.getNodeType() != Node.ELEMENT_NODE) {
@@ -477,6 +485,9 @@ public String visitAllContent(Node node, boolean isOptional) throws Exception {
477485
private String handleElementsWithChildNodes(Node node, StringBuilder builder) throws Exception {
478486
Node nameNode = node.getAttributes().getNamedItem(NAME);
479487
Node typeNode = node.getAttributes().getNamedItem(TYPE);
488+
if (nameNode == null) {
489+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, NAME));
490+
}
480491
if (typeNode != null && typeNode.getNodeValue().equals(nameNode.getNodeValue())) {
481492
String resolvedName = resolveTypeNameConflicts(nameNode.getNodeValue(), typeNode.getNodeValue());
482493
nameResolvers.put(resolvedName, nameNode.getNodeValue());
@@ -558,25 +569,23 @@ private void processChildNodeByType(Node childNode, StringBuilder builder) throw
558569

559570
private void processChildChoiceNodes(NodeList childNodes, StringBuilder stringBuilder) throws Exception {
560571
for (Node childNode : asIterable(childNodes)) {
561-
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
562-
continue;
563-
}
564-
if (childNode.getLocalName().equals(ANNOTATION)) {
572+
if (childNode.getNodeType() != Node.ELEMENT_NODE || childNode.getLocalName().equals(ANNOTATION)) {
565573
continue;
566574
}
567575
if (childNode.getLocalName().equals(SEQUENCE)) {
568576
stringBuilder.append(visitSequence(childNode, true));
569577
} else {
570578
stringBuilder.append(addNamespace(this, getTargetNamespace()));
571-
Node nameNode = childNode.getAttributes().getNamedItem(NAME);
572-
Node typeNode = childNode.getAttributes().getNamedItem(TYPE);
573579
if (childNode.hasChildNodes()) {
574580
StringBuilder childNodeBuilder = new StringBuilder();
575-
processChildNode(false, childNode, childNodeBuilder);
581+
processChildNode(childNode, childNodeBuilder);
576582
}
577-
if (typeNode == null) {
578-
typeNode = nameNode;
583+
Node nameNode = childNode.getAttributes().getNamedItem(NAME);
584+
if (nameNode == null) {
585+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, NAME));
579586
}
587+
Node typeNode = childNode.getAttributes().getNamedItem(TYPE);
588+
typeNode = typeNode == null ? nameNode : typeNode;
580589
if (childNode.hasAttributes() && childNode.getAttributes().getNamedItem(REF) != null) {
581590
Node refNode = childNode.getAttributes().getNamedItem(REF);
582591
String derivedType = refNode.getNodeValue().contains(COLON)
@@ -586,7 +595,7 @@ private void processChildChoiceNodes(NodeList childNodes, StringBuilder stringBu
586595
stringBuilder.append(derivedType);
587596
} else {
588597
stringBuilder.append(deriveType(typeNode)).append(WHITESPACE);
589-
stringBuilder.append(nameNode == null ? deriveType(typeNode) : nameNode.getNodeValue());
598+
stringBuilder.append(nameNode.getNodeValue());
590599
}
591600
stringBuilder.append(QUESTION_MARK).append(SEMICOLON);
592601
}
@@ -657,12 +666,12 @@ private void processAllChildNodes(boolean isOptional, NodeList childNodes,
657666
}
658667
}
659668

660-
private void processChildNode(boolean isOptional, Node childNode,
669+
private void processChildNode(Node childNode,
661670
StringBuilder stringBuilder) throws Exception {
662671
Optional<XSDComponent> component = XSDFactory.generateComponents(childNode);
663672
if (component.isPresent()) {
664673
component.get().setSubType(true);
665-
component.get().setOptional(isOptional);
674+
component.get().setOptional(false);
666675
stringBuilder.append(addNamespace(this, getTargetNamespace()));
667676
stringBuilder.append(component.get().accept(this));
668677
}
@@ -698,9 +707,17 @@ private void processUnionOfSimpleTypes(String nameValue, StringBuilder builder,
698707
if (simpleTypeNode.hasAttributes() && simpleTypeNode.getAttributes().getNamedItem(MEMBER_TYPES) != null) {
699708
builder.append(addNamespace(xsdVisitor, targetNamespace));
700709
builder.append(PUBLIC).append(WHITESPACE).append(TYPE).append(WHITESPACE);
701-
builder.append(simpleTypeNode.getParentNode().getAttributes().getNamedItem(NAME).getNodeValue());
710+
Node nameNode = simpleTypeNode.getParentNode().getAttributes().getNamedItem(NAME);
711+
if (nameNode == null) {
712+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, NAME));
713+
}
714+
builder.append(nameNode.getNodeValue());
702715
builder.append(WHITESPACE);
703-
String unionTypes = simpleTypeNode.getAttributes().getNamedItem(MEMBER_TYPES).getNodeValue();
716+
Node memberTypesNode = simpleTypeNode.getAttributes().getNamedItem(MEMBER_TYPES);
717+
if (memberTypesNode == null) {
718+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, MEMBER_TYPES));
719+
}
720+
String unionTypes = memberTypesNode.getNodeValue();
704721
String[] typesArray = unionTypes.split(WHITESPACE);
705722
ArrayList<String> existingTypes = new ArrayList<>();
706723
for (String type: typesArray) {
@@ -744,14 +761,18 @@ private void processUnionOfSimpleTypes(String nameValue, StringBuilder builder,
744761
}
745762
}
746763

747-
private static boolean hasEnumerations(Node simpleTypeNode, ArrayList<String> enumValues) {
764+
private static boolean hasEnumerations(Node simpleTypeNode, ArrayList<String> enumValues) throws Exception {
748765
boolean enumeration = false;
749766
if (simpleTypeNode.hasChildNodes()) {
750767
NodeList nodes = simpleTypeNode.getChildNodes();
751768
for (Node node : asIterable(nodes)) {
752769
if (node.getNodeType() == Node.ELEMENT_NODE && ENUMERATION.equals(node.getLocalName())) {
753770
enumeration = true;
754-
String enumValue = sanitizeString(node.getAttributes().getNamedItem(VALUE).getNodeValue());
771+
Node valueNode = node.getAttributes().getNamedItem(VALUE);
772+
if (valueNode == null) {
773+
throw new Exception(String.format(ATTRIBUTE_NOT_FOUND_ERROR, VALUE));
774+
}
775+
String enumValue = sanitizeString(valueNode.getNodeValue());
755776
if (enumValue.equals(EMPTY_STRING)) {
756777
continue;
757778
}

xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ private static Stream<Object[]> provideTestPaths() {
7474
new Object[] {"32_elements_with_imports.xsd", "32_elements_with_imports.bal"},
7575
new Object[] {"33_elements_with_simple_types.xml", "33_elements_with_simple_types.bal"},
7676
new Object[] {"34_elements_with_simple_types.xml", "34_elements_with_simple_types.bal"},
77-
new Object[] {"35_unions_of_simple_types.xsd", "35_unions_of_simple_types.bal"}
77+
new Object[] {"35_unions_of_simple_types.xsd", "35_unions_of_simple_types.bal"},
78+
new Object[] {"36_elements_with_byte_type.xsd", "36_elements_with_byte_type.bal"}
7879
);
7980
}
8081

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import ballerina/data.xmldata;
2+
3+
@xmldata:Namespace {uri: "http://www.namespaces.com"}
4+
public type xmlBytes record {|
5+
string \#content;
6+
|};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.namespaces.com" targetNamespace="http://www.namespaces.com">
2+
<xsd:element name="xmlBytes" type="xsd:base64Binary"/>
3+
</xsd:schema>

0 commit comments

Comments
 (0)