diff --git a/distribution/zip/jballerina-tools/build.gradle b/distribution/zip/jballerina-tools/build.gradle index 92062e14da8f..112c8be99dba 100644 --- a/distribution/zip/jballerina-tools/build.gradle +++ b/distribution/zip/jballerina-tools/build.gradle @@ -136,6 +136,7 @@ dependencies { langserverLib project(':ls-extensions:partial-parser') langserverLib project(':ls-extensions:trigger-service') langserverLib project(':ls-extensions:bal-shell-service') + langserverLib project(':xml-to-record-converter') debugAdapterLib project(path: ':debug-adapter:debug-adapter-core', configuration: 'libs') debugAdapterLib project(':debug-adapter:debug-adapter-cli') diff --git a/misc/identifier-util/src/main/java/module-info.java b/misc/identifier-util/src/main/java/module-info.java index b1f82f4c35bd..bedc68c8ee99 100644 --- a/misc/identifier-util/src/main/java/module-info.java +++ b/misc/identifier-util/src/main/java/module-info.java @@ -4,5 +4,5 @@ exports io.ballerina.identifier to io.ballerina.lang, io.ballerina.runtime, io.ballerina.shell, io.ballerina.testerina.runtime, io.ballerina.lang.runtime, io.ballerina.lang.error, - ballerina.debug.adapter.core, io.ballerina.jsonmapper, io.ballerina.cli; + ballerina.debug.adapter.core, io.ballerina.jsonmapper, io.ballerina.cli, io.ballerina.xmltorecordconverter; } diff --git a/misc/xml-to-record-converter/build.gradle b/misc/xml-to-record-converter/build.gradle new file mode 100644 index 000000000000..057b3f053842 --- /dev/null +++ b/misc/xml-to-record-converter/build.gradle @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +apply from: "$rootDir/gradle/javaProject.gradle" +apply from: "$rootDir/gradle/ballerinaLangLibLoad.gradle" + +configurations { + compile.transitive = false + compileClasspath.extendsFrom(compileOnly) +} + +dependencies { + compileOnly project(':ballerina-parser') + compileOnly project(':ballerina-lang') + compileOnly project(':formatter:formatter-core') + compileOnly project(':language-server:language-server-commons') + + implementation project(':ballerina-tools-api') + implementation project(':identifier-util') + implementation "org.apache.commons:commons-lang3:${project.apacheCommonsLang3Version}" + implementation "org.javatuples:javatuples:${project.javaTuples}" + compileOnly (group: 'org.eclipse.lsp4j', name: 'org.eclipse.lsp4j', version: "${project.eclipseLsp4jVersion}") + + testCompile 'org.testng:testng' + testCompile project(':ballerina-lang') + testCompile project(':formatter:formatter-core') + testCompile project(':language-server:language-server-core') + testCompile "org.javatuples:javatuples:${project.javaTuples}" + testCompile (group: 'org.eclipse.lsp4j', name: 'org.eclipse.lsp4j', version: "${project.eclipseLsp4jVersion}") +} + +test { + useTestNG() { + suites 'src/test/resources/testng.xml' + } +} + +description = 'Module for converting XML to Ballerina Records Directly' + +ext.moduleName = 'xml-to-record-converter' + +compileJava { + inputs.property("moduleName", moduleName) + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/Constants.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/Constants.java new file mode 100644 index 000000000000..83b0d7906778 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/Constants.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +/** + * Represents the Service constants. + * + * @since 2201.7.2 + */ +public class Constants { + public static final String CAPABILITY_NAME = "xmlToRecord"; +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverter.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverter.java new file mode 100644 index 000000000000..c86fb9ceb81b --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverter.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ArrayDimensionNode; +import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.BasicLiteralNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.ImportDeclarationNode; +import io.ballerina.compiler.syntax.tree.LiteralValueToken; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.MinutiaeList; +import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeFactory; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NonTerminalNode; +import io.ballerina.compiler.syntax.tree.ParenthesisedTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.RecordFieldNode; +import io.ballerina.compiler.syntax.tree.RecordTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; +import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.UnionTypeDescriptorNode; +import io.ballerina.xmltorecordconverter.diagnostic.DiagnosticMessage; +import io.ballerina.xmltorecordconverter.diagnostic.DiagnosticUtils; +import org.apache.commons.lang3.StringUtils; +import org.ballerinalang.formatter.core.ForceFormattingOptions; +import org.ballerinalang.formatter.core.Formatter; +import org.ballerinalang.formatter.core.FormatterException; +import org.ballerinalang.formatter.core.FormattingOptions; +import org.ballerinalang.langserver.commons.toml.visitor.Array; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static io.ballerina.xmltorecordconverter.util.ConverterUtils.escapeIdentifier; +import static io.ballerina.xmltorecordconverter.util.ConverterUtils.extractUnionTypeDescNode; +import static io.ballerina.xmltorecordconverter.util.ConverterUtils.getPrimitiveTypeName; +import static io.ballerina.xmltorecordconverter.util.ConverterUtils.sortTypeDescriptorNodes; + +/** + * APIs for conversion from XML to Ballerina records. + * + * @since 2201.7.2 + */ +public class XMLToRecordConverter { + + private XMLToRecordConverter() {} + + public static XMLToRecordResponse convert(String xmlValue, boolean preserveNamespaces, boolean isRecordTypeDesc, + boolean isClosed, boolean forceFormatRecordFields) { + Map recordToTypeDescNodes = new LinkedHashMap<>(); + List diagnosticMessages = new ArrayList<>(); + XMLToRecordResponse response = new XMLToRecordResponse(); + + try { + // Convert the XML string to an InputStream + ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlValue.getBytes()); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(inputStream); + + Element rootElement = doc.getDocumentElement(); + generateRecords(rootElement, isClosed, recordToTypeDescNodes, diagnosticMessages); + } catch (ParserConfigurationException parserConfigurationException) { + + } catch (SAXException saxException) { + + } catch (IOException | IllegalArgumentException e) { + + } + + io.ballerina.compiler.syntax.tree.NodeList imports = AbstractNodeFactory.createEmptyNodeList(); + List typeDefNodes = recordToTypeDescNodes.entrySet().stream() + .map(entry -> { + String recordName = entry.getKey(); + String recordTypeName = escapeIdentifier(StringUtils.capitalize(recordName)); + MetadataNode metadata = recordName.equals(recordTypeName) ? null : getXMLNameNode(recordName); + Token typeKeyWord = AbstractNodeFactory.createToken(SyntaxKind.TYPE_KEYWORD); + IdentifierToken typeName = AbstractNodeFactory.createIdentifierToken(recordTypeName); + Token semicolon = AbstractNodeFactory.createToken(SyntaxKind.SEMICOLON_TOKEN); + return NodeFactory.createTypeDefinitionNode(metadata, null, typeKeyWord, typeName, entry.getValue(), + semicolon); + }).collect(Collectors.toList()); + + NodeList moduleMembers = + AbstractNodeFactory.createNodeList(new ArrayList<>(typeDefNodes));; +// if (isRecordTypeDesc) { +// Optional lastTypeDefNode = convertToInlineRecord(typeDefNodes, diagnosticMessages); +// moduleMembers = lastTypeDefNode +// .>map(AbstractNodeFactory::createNodeList) +// .orElseGet(AbstractNodeFactory::createEmptyNodeList); +// } else { +// moduleMembers = AbstractNodeFactory.createNodeList(new ArrayList<>(typeDefNodes)); +// } + + Token eofToken = AbstractNodeFactory.createIdentifierToken(""); + ModulePartNode modulePartNode = NodeFactory.createModulePartNode(imports, moduleMembers, eofToken); + try { + ForceFormattingOptions forceFormattingOptions = ForceFormattingOptions.builder() + .setForceFormatRecordFields(forceFormatRecordFields).build(); + FormattingOptions formattingOptions = FormattingOptions.builder() + .setForceFormattingOptions(forceFormattingOptions).build(); + response.setCodeBlock(Formatter.format(modulePartNode.syntaxTree(), formattingOptions).toSourceCode()); + } catch (FormatterException e) { +// DiagnosticMessage message = DiagnosticMessage.jsonToRecordConverter102(null); +// diagnosticMessages.add(message); + } +// if (!updatedFieldNames.isEmpty()) { +// updatedFieldNames.forEach((oldFieldName, updateFieldName) -> { +// DiagnosticMessage message = +// DiagnosticMessage.jsonToRecordConverter106(new String[]{oldFieldName, updateFieldName}); +// diagnosticMessages.add(message); +// }); +// } + return DiagnosticUtils.getDiagnosticResponse(diagnosticMessages, response); + } + + private static void generateRecords(Element xmlElement, boolean isClosed, + Map recordToTypeDescNodes, + List diagnosticMessages) { + Token recordKeyWord = AbstractNodeFactory.createToken(SyntaxKind.RECORD_KEYWORD); + Token bodyStartDelimiter = AbstractNodeFactory.createToken(isClosed ? SyntaxKind.OPEN_BRACE_PIPE_TOKEN : + SyntaxKind.OPEN_BRACE_TOKEN); + + List recordFields = new ArrayList<>(); + + String xmlNodeName = xmlElement.getNodeName(); + if (recordToTypeDescNodes.containsKey(xmlNodeName)) { + // Process differently + } else { + org.w3c.dom.NodeList xmlNodeList = xmlElement.getChildNodes(); + 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) { + Element xmlElementNode = (Element) xmlNode; + boolean isLeafXMLElementNode = isLeafXMLElementNode(xmlElementNode); + if (!isLeafXMLElementNode) { + generateRecords(xmlElementNode, isClosed, recordToTypeDescNodes, diagnosticMessages); + } + RecordFieldNode recordField = getRecordField(xmlElementNode, null, null, false); + if (recordFields.stream().anyMatch(recField -> ((RecordFieldNode) recField).fieldName().text() + .equals(recordField.fieldName().text()))) { + int indexOfRecordFieldNode = IntStream.range(0, recordFields.size()) + .filter(j -> ((RecordFieldNode) recordFields.get(j)).fieldName().text() + .equals(recordField.fieldName().text()) && (isLeafXMLElementNode ^ ((RecordFieldNode) recordFields.get(j)).typeName().kind() + .equals(SyntaxKind.IDENTIFIER_TOKEN))) + .findFirst().orElse(-1); + if (indexOfRecordFieldNode == -1) { + recordFields.add(recordField); + } else { + RecordFieldNode existingRecordField = + (RecordFieldNode) recordFields.remove(indexOfRecordFieldNode); + RecordFieldNode updatedRecordField = mergeRecordFields(existingRecordField, recordField); + recordFields.add(indexOfRecordFieldNode, updatedRecordField); + } + + } else { + recordFields.add(recordField); + } + } + } + org.w3c.dom.NamedNodeMap xmlAttributesMap = xmlElement.getAttributes(); + for (int i = 0; i < xmlAttributesMap.getLength(); i++) { + org.w3c.dom.Node xmlNode = xmlAttributesMap.item(i); + if (xmlNode.getNodeType() == org.w3c.dom.Node.ATTRIBUTE_NODE) { + RecordFieldNode recordField = getRecordField(xmlNode); + recordFields.add(recordField); + } + } + } + NodeList fieldNodes = AbstractNodeFactory.createNodeList(recordFields); + Token bodyEndDelimiter = AbstractNodeFactory.createToken(isClosed ? SyntaxKind.CLOSE_BRACE_PIPE_TOKEN : + SyntaxKind.CLOSE_BRACE_TOKEN); + RecordTypeDescriptorNode recordTypeDescriptorNode = + NodeFactory.createRecordTypeDescriptorNode(recordKeyWord, bodyStartDelimiter, + fieldNodes, null, bodyEndDelimiter); + + if (!recordToTypeDescNodes.containsKey(xmlNodeName)) { + recordToTypeDescNodes.put(xmlNodeName, recordTypeDescriptorNode); + } + } + + private static RecordFieldNode getRecordField(Element xmlElementNode, List existingFieldNames, + Map updatedFieldNames, boolean isOptionalField) { + Token typeName; + Token questionMarkToken = AbstractNodeFactory.createToken(SyntaxKind.QUESTION_MARK_TOKEN); + TypeDescriptorNode fieldTypeName; + IdentifierToken fieldName = + AbstractNodeFactory.createIdentifierToken(escapeIdentifier(xmlElementNode.getNodeName().trim())); + Token optionalFieldToken = isOptionalField ? questionMarkToken : null; + Token semicolonToken = AbstractNodeFactory.createToken(SyntaxKind.SEMICOLON_TOKEN); + + RecordFieldNode recordFieldNode; + + if (isLeafXMLElementNode(xmlElementNode)) { + typeName = getPrimitiveTypeName(xmlElementNode.getFirstChild().getNodeValue()); + } else { + // At the moment all are considered as Objects here + String elementKey = xmlElementNode.getNodeName().trim(); + String type = escapeIdentifier(StringUtils.capitalize(elementKey)); +// String updatedType = getAndUpdateFieldNames(type, false, existingFieldNames, updatedFieldNames); + typeName = AbstractNodeFactory.createIdentifierToken(type); + } + fieldTypeName = NodeFactory.createBuiltinSimpleNameReferenceNode(typeName.kind(), typeName); + recordFieldNode = NodeFactory.createRecordFieldNode(null, null, + fieldTypeName, fieldName, + optionalFieldToken, semicolonToken); +// else if (entry.getValue().isJsonArray()) { +// Map.Entry jsonArrayEntry = +// new AbstractMap.SimpleEntry<>(entry.getKey(), entry.getValue().getAsJsonArray()); +// ArrayTypeDescriptorNode arrayTypeName = +// getArrayTypeDescriptorNode(jsonArrayEntry, existingFieldNames, updatedFieldNames); +// recordFieldNode = NodeFactory.createRecordFieldNode(null, null, +// arrayTypeName, fieldName, +// optionalFieldToken, semicolonToken); +// } + return recordFieldNode; + } + + private static RecordFieldNode getRecordField(org.w3c.dom.Node xmlAttributeNode) { + Token typeName = AbstractNodeFactory.createToken(SyntaxKind.STRING_KEYWORD);; + TypeDescriptorNode fieldTypeName = NodeFactory.createBuiltinSimpleNameReferenceNode(typeName.kind(), typeName); + IdentifierToken fieldName = + AbstractNodeFactory.createIdentifierToken(escapeIdentifier(xmlAttributeNode.getNodeName())); + Token semicolonToken = AbstractNodeFactory.createToken(SyntaxKind.SEMICOLON_TOKEN); + + return NodeFactory.createRecordFieldNode(getXMLAttributeNode(), null, + fieldTypeName, fieldName, + null, semicolonToken); + } + + private static RecordFieldNode mergeRecordFields(RecordFieldNode existingRecordFieldNode, + RecordFieldNode newRecordFieldNode) { + TypeDescriptorNode existingTypeName = (TypeDescriptorNode) existingRecordFieldNode.typeName(); + TypeDescriptorNode newTypeName = (TypeDescriptorNode) newRecordFieldNode.typeName(); + + if (existingTypeName.kind().equals(SyntaxKind.ARRAY_TYPE_DESC)) { + TypeDescriptorNode memberTypeDescNode = ((ArrayTypeDescriptorNode) existingTypeName).memberTypeDesc(); + if (memberTypeDescNode.toSourceCode().strip().equals(newTypeName.toSourceCode().strip())) { + return existingRecordFieldNode; + } else { + Token openParenToken = NodeFactory.createToken(SyntaxKind.OPEN_PAREN_TOKEN); + Token closeParenToken = NodeFactory.createToken(SyntaxKind.CLOSE_PAREN_TOKEN); + + List extractedTypeDescNodes = extractUnionTypeDescNode(memberTypeDescNode); + extractedTypeDescNodes.add(newTypeName); + List sortedTypeDescNodes = sortTypeDescriptorNodes(extractedTypeDescNodes); + TypeDescriptorNode unionTypeDescNode = joinToUnionTypeDescriptorNode(sortedTypeDescNodes); + ParenthesisedTypeDescriptorNode parenTypeDescNode = + NodeFactory.createParenthesisedTypeDescriptorNode(openParenToken, unionTypeDescNode, closeParenToken); + ArrayTypeDescriptorNode updatedTypeName = getArrayTypeDesc(parenTypeDescNode); + return existingRecordFieldNode.modify().withTypeName(updatedTypeName).apply(); + } + } else { + if (existingTypeName.toSourceCode().strip().equals(newTypeName.toSourceCode().strip())) { + ArrayTypeDescriptorNode updatedTypeName = getArrayTypeDesc(existingTypeName); + return existingRecordFieldNode.modify().withTypeName(updatedTypeName).apply(); + } else { + Token openParenToken = NodeFactory.createToken(SyntaxKind.OPEN_PAREN_TOKEN); + Token closeParenToken = NodeFactory.createToken(SyntaxKind.CLOSE_PAREN_TOKEN); + + List sortedTypeDescNodes = + sortTypeDescriptorNodes(List.of(existingTypeName, newTypeName)); + TypeDescriptorNode unionTypeDescNode = joinToUnionTypeDescriptorNode(sortedTypeDescNodes); + ParenthesisedTypeDescriptorNode parenTypeDescNode = + NodeFactory.createParenthesisedTypeDescriptorNode(openParenToken, unionTypeDescNode, closeParenToken); + ArrayTypeDescriptorNode updatedTypeName = getArrayTypeDesc(parenTypeDescNode); + return existingRecordFieldNode.modify().withTypeName(updatedTypeName).apply(); + } + } + } + + private static ArrayTypeDescriptorNode getArrayTypeDesc(TypeDescriptorNode typeDescNode) { + Token openSBracketToken = AbstractNodeFactory.createToken(SyntaxKind.OPEN_BRACKET_TOKEN); + Token closeSBracketToken = AbstractNodeFactory.createToken(SyntaxKind.CLOSE_BRACKET_TOKEN); + ArrayDimensionNode arrayDimension = NodeFactory.createArrayDimensionNode(openSBracketToken, null, + closeSBracketToken); + NodeList arrayDimensions = NodeFactory.createNodeList(arrayDimension); + + return NodeFactory.createArrayTypeDescriptorNode(typeDescNode, arrayDimensions); + +// Token pipeToken = NodeFactory.createToken(SyntaxKind.PIPE_TOKEN); +// Token openParenToken = NodeFactory.createToken(SyntaxKind.OPEN_PAREN_TOKEN); +// Token closeParenToken = NodeFactory.createToken(SyntaxKind.CLOSE_PAREN_TOKEN); +// +// List extractedTypeDescNodes = extractUnionTypeDescNode(typeDescNode1); +// +// if (typeDescNode1.kind().equals(SyntaxKind.PARENTHESISED_TYPE_DESC)) { +// TypeDescriptorNode innerTypeDescNode = ((ParenthesisedTypeDescriptorNode) typeDescNode1).typedesc(); +// if (innerTypeDescNode.kind().equals(SyntaxKind.UNION_TYPE_DESC)) { +// List extractedTypeDescNodes = +// extractUnionTypeDescNode((UnionTypeDescriptorNode) innerTypeDescNode); +// extractedTypeDescNodes.add(typeDescNode2); +// List sortedTypeDescNodes = sortTypeDescriptorNodes(extractedTypeDescNodes); +// UnionTypeDescriptorNode unionTypeDescNode = +// (UnionTypeDescriptorNode) joinToUnionTypeDescriptorNode(sortedTypeDescNodes); +// ParenthesisedTypeDescriptorNode parenTypeDescNode = +// NodeFactory.createParenthesisedTypeDescriptorNode(openParenToken, unionTypeDescNode, +// closeParenToken); +// return NodeFactory.createArrayTypeDescriptorNode(parenTypeDescNode, arrayDimensions); +// } else { +// UnionTypeDescriptorNode unionTypeDescNode = +// NodeFactory.createUnionTypeDescriptorNode(innerTypeDescNode, pipeToken, typeDescNode2); +// ParenthesisedTypeDescriptorNode parenTypeDescNode = +// NodeFactory.createParenthesisedTypeDescriptorNode(openParenToken, unionTypeDescNode, closeParenToken); +// return NodeFactory.createArrayTypeDescriptorNode(parenTypeDescNode, arrayDimensions); +// } +// } else { +// List sortedTypeDescNodes = sortTypeDescriptorNodes(List.of(typeDescNode1, typeDescNode2)); +// TypeDescriptorNode unionTypeDescNode = joinToUnionTypeDescriptorNode(sortedTypeDescNodes); +// ParenthesisedTypeDescriptorNode parenTypeDescNode = +// NodeFactory.createParenthesisedTypeDescriptorNode(openParenToken, unionTypeDescNode, closeParenToken); +// return NodeFactory.createArrayTypeDescriptorNode(parenTypeDescNode, arrayDimensions); +// } + } + + private static TypeDescriptorNode joinToUnionTypeDescriptorNode(List typeNames) { + Token pipeToken = NodeFactory.createToken(SyntaxKind.PIPE_TOKEN); + + TypeDescriptorNode unionTypeDescNode = typeNames.get(0); + for (int i = 1; i < typeNames.size(); i++) { + unionTypeDescNode = + NodeFactory.createUnionTypeDescriptorNode(unionTypeDescNode, pipeToken, typeNames.get(i)); + } + return unionTypeDescNode; + } + + private static boolean isLeafXMLElementNode(Element xmlElementNode) { + return xmlElementNode.getChildNodes().getLength() == 1 && + xmlElementNode.getChildNodes().item(0).getNodeType() == org.w3c.dom.Node.TEXT_NODE; + } + + private static MetadataNode getXMLNameNode(String value) { + Token atToken = AbstractNodeFactory.createToken(SyntaxKind.AT_TOKEN); + + IdentifierToken modulePrefix = AbstractNodeFactory.createIdentifierToken("xmldata"); + Token colon = AbstractNodeFactory.createToken(SyntaxKind.COLON_TOKEN); + IdentifierToken identifier = AbstractNodeFactory.createIdentifierToken("Name"); + Node annotReference = NodeFactory.createQualifiedNameReferenceNode(modulePrefix, colon, identifier); + + Token openBrace = AbstractNodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN); + Token closeBrace = AbstractNodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN); + IdentifierToken fieldName = AbstractNodeFactory.createIdentifierToken("value"); + MinutiaeList emptyMinutiaeList = AbstractNodeFactory.createEmptyMinutiaeList(); + LiteralValueToken literalToken = + NodeFactory.createLiteralValueToken(SyntaxKind.STRING_LITERAL_TOKEN, String.format("\"%s\"", value), + emptyMinutiaeList, emptyMinutiaeList); + BasicLiteralNode valueExpr = NodeFactory.createBasicLiteralNode(SyntaxKind.STRING_LITERAL, literalToken); + MappingFieldNode mappingField = + NodeFactory.createSpecificFieldNode(null, fieldName, colon, valueExpr); + SeparatedNodeList mappingFields = AbstractNodeFactory.createSeparatedNodeList(mappingField); + MappingConstructorExpressionNode annotValue = + NodeFactory.createMappingConstructorExpressionNode(openBrace, mappingFields, closeBrace); + + AnnotationNode annotation = NodeFactory.createAnnotationNode(atToken, annotReference, annotValue); + NodeList annotations = AbstractNodeFactory.createSeparatedNodeList(annotation); + + return NodeFactory.createMetadataNode(null, annotations); + } + + private static MetadataNode getXMLAttributeNode() { + Token atToken = AbstractNodeFactory.createToken(SyntaxKind.AT_TOKEN); + + IdentifierToken name = AbstractNodeFactory.createIdentifierToken("Attribute"); + Node annotReference = NodeFactory.createSimpleNameReferenceNode(name); + + AnnotationNode annotation = NodeFactory.createAnnotationNode(atToken, annotReference, null); + NodeList annotations = AbstractNodeFactory.createSeparatedNodeList(annotation); + + return NodeFactory.createMetadataNode(null, annotations); + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilities.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilities.java new file mode 100644 index 000000000000..c02ccc9cf560 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilities.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import org.ballerinalang.langserver.commons.registration.BallerinaClientCapability; + +/** + * Client capabilities for the XMLToRecord service. + * + * @since 2201.7.2 + */ +public class XMLToRecordConverterClientCapabilities extends BallerinaClientCapability { + + private boolean convert; + + public boolean isConvert() { + return convert; + } + + public void setConvert(boolean convert) { + this.convert = convert; + } + + public XMLToRecordConverterClientCapabilities() { + super(Constants.CAPABILITY_NAME); + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilitySetter.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilitySetter.java new file mode 100644 index 000000000000..a1f26257e5ab --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterClientCapabilitySetter.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + + +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.commons.registration.BallerinaClientCapabilitySetter; + +/** + * Client Capability setter for the {@link XMLToRecordConverterService}. + * + * @since 2201.7.2 + */ +@JavaSPIService("org.ballerinalang.langserver.commons.registration.BallerinaClientCapabilitySetter") +public class XMLToRecordConverterClientCapabilitySetter extends + BallerinaClientCapabilitySetter { + + @Override + public String getCapabilityName() { + return Constants.CAPABILITY_NAME; + } + + @Override + public Class getCapability() { + return XMLToRecordConverterClientCapabilities.class; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilities.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilities.java new file mode 100644 index 000000000000..7b4594efbfb1 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilities.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import org.ballerinalang.langserver.commons.registration.BallerinaServerCapability; + +/** + * Server capabilities for the XMLToRecord service. + * + * @since 2201.7.2 + */ +public class XMLToRecordConverterServerCapabilities extends BallerinaServerCapability { + + private boolean convert; + + public boolean isConvert() { + return convert; + } + + public void setConvert(boolean convert) { + this.convert = convert; + } + + public XMLToRecordConverterServerCapabilities() { + super(Constants.CAPABILITY_NAME); + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilitySetter.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilitySetter.java new file mode 100644 index 000000000000..99e53493a165 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterServerCapabilitySetter.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.commons.registration.BallerinaServerCapabilitySetter; + +import java.util.Optional; + +/** + * Capability setter for the {@link XMLToRecordConverterService}. + * + * @since 2201.7.2 + */ +@JavaSPIService("org.ballerinalang.langserver.commons.registration.BallerinaServerCapabilitySetter") +public class XMLToRecordConverterServerCapabilitySetter extends + BallerinaServerCapabilitySetter { + + @Override + public Optional build() { + XMLToRecordConverterServerCapabilities capabilities = new XMLToRecordConverterServerCapabilities(); + capabilities.setConvert(true); + return Optional.of(capabilities); + } + + @Override + public String getCapabilityName() { + return Constants.CAPABILITY_NAME; + } + + @Override + public Class getCapability() { + return XMLToRecordConverterServerCapabilities.class; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterService.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterService.java new file mode 100644 index 000000000000..c054f61bd78f --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterService.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import org.ballerinalang.annotation.JavaSPIService; +import org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService; +import org.ballerinalang.langserver.commons.workspace.WorkspaceManager; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; +import org.eclipse.lsp4j.services.LanguageServer; + +import java.util.concurrent.CompletableFuture; + +/** + * The extended service for the XMLToRecord endpoint. + * + * @since 2.0.0 + */ +@JavaSPIService("org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService") +@JsonSegment("xmlToRecord") +public class XMLToRecordConverterService implements ExtendedLanguageServerService { + private WorkspaceManager workspaceManager; + + @Override + public void init(LanguageServer langServer, WorkspaceManager workspaceManager) { + ExtendedLanguageServerService.super.init(langServer, workspaceManager); + this.workspaceManager = workspaceManager; + } + + @Override + public Class getRemoteInterface() { + return getClass(); + } + + @JsonRequest + public CompletableFuture convert(XMLToRecordRequest request) { + return CompletableFuture.supplyAsync(() -> { + String xmlValue = request.getXmlValue(); + boolean isRecordTypeDesc = request.getIsRecordTypeDesc(); + boolean isClosed = request.getIsClosed(); + boolean forceFormatRecordFields = request.getForceFormatRecordFields(); + + return XMLToRecordConverter.convert(xmlValue, false, isRecordTypeDesc, isClosed, + forceFormatRecordFields); + }); + } + + @Override + public String getName() { + return Constants.CAPABILITY_NAME; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordRequest.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordRequest.java new file mode 100644 index 000000000000..5b532e742b0c --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordRequest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +/** + * Request format for XMLToRecord endpoint. + * + * @since 2.0.0 + */ +public class XMLToRecordRequest { + + private String xmlValue; + private boolean isRecordTypeDesc; + private boolean isClosed; + private boolean forceFormatRecordFields; + + public XMLToRecordRequest(String xmlValue, boolean isRecordTypeDesc, boolean isClosed, + boolean forceFormatRecordFields) { + this.xmlValue = xmlValue; + this.isRecordTypeDesc = isRecordTypeDesc; + this.isClosed = isClosed; + this.forceFormatRecordFields = forceFormatRecordFields; + } + + public String getXmlValue() { + return xmlValue; + } + + public void setXmlValue(String xmlValue) { + this.xmlValue = xmlValue; + } + + public boolean getIsRecordTypeDesc() { + return isRecordTypeDesc; + } + + public void setIsRecordTypeDesc(boolean isRecordTypeDesc) { + this.isRecordTypeDesc = isRecordTypeDesc; + } + + public boolean getIsClosed() { + return isClosed; + } + + public void setIsClosed(boolean isClosed) { + this.isClosed = isClosed; + } + + public boolean getForceFormatRecordFields() { + return forceFormatRecordFields; + } + + public void setForceFormatRecordFields(boolean forceFormatRecordFields) { + this.forceFormatRecordFields = forceFormatRecordFields; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordResponse.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordResponse.java new file mode 100644 index 000000000000..b5e66fc8884c --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/XMLToRecordResponse.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import io.ballerina.xmltorecordconverter.diagnostic.XMLToRecordConverterDiagnostic; + +import java.util.ArrayList; +import java.util.List; + +/** + * Response format for XMLToRecordConverter endpoint. + * + * @since 2201.7.2 + */ +public class XMLToRecordResponse { + private String codeBlock = ""; + private List diagnostics = new ArrayList<>(); + + public String getCodeBlock() { + return codeBlock; + } + + public void setCodeBlock(String codeBlock) { + this.codeBlock = codeBlock; + } + + public List getDiagnostics() { + return diagnostics; + } + + public void setDiagnostics(List diagnostic) { + this.diagnostics = diagnostic; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticMessage.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticMessage.java new file mode 100644 index 000000000000..7c196c1ea1eb --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticMessage.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter.diagnostic; + +import io.ballerina.tools.diagnostics.DiagnosticSeverity; + +import java.util.Objects; + +public class DiagnosticMessage { + + private final String code; + private final String description; + private final DiagnosticSeverity severity; + private final Object[] args; + + private DiagnosticMessage(String code, String description, DiagnosticSeverity severity, Object[] args) { + this.code = code; + this.description = description; + this.severity = severity; + this.args = args; + } + + public String getCode() { + return this.code; + } + + public String getDescription() { + return this.description; + } + + public DiagnosticSeverity getSeverity() { + return this.severity; + } + + public Object[] getArgs() { + return Objects.requireNonNullElse(this.args, new Object[0]).clone(); + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticUtils.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticUtils.java new file mode 100644 index 000000000000..f6f559c28c74 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/DiagnosticUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter.diagnostic; + +import io.ballerina.xmltorecordconverter.XMLToRecordResponse; + +import java.util.List; + +/** + * Util methods for XML to record converter diagnostics. + * + * @since 2201.7.2 + */ +public class DiagnosticUtils { + + private DiagnosticUtils() {} + + public static XMLToRecordResponse getDiagnosticResponse(List diagnosticMessages, + XMLToRecordResponse response) { + List diagnostics = response.getDiagnostics(); + for (DiagnosticMessage message : diagnosticMessages) { + XMLToRecordConverterDiagnostic diagnostic = new XMLToRecordConverterDiagnostic( + message.getCode(), message.getDescription(), message.getSeverity(), null, message.getArgs()); + diagnostics.add(diagnostic); + } + return response; + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/XMLToRecordConverterDiagnostic.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/XMLToRecordConverterDiagnostic.java new file mode 100644 index 000000000000..280b516ef9c1 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/diagnostic/XMLToRecordConverterDiagnostic.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter.diagnostic; + +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticInfo; +import io.ballerina.tools.diagnostics.DiagnosticProperty; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.ballerina.tools.diagnostics.Location; + +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; + +/** + * Represents a {@code Diagnostic} related to XML to Record converter. + * + * @since 2201.7.2 + */ +public class XMLToRecordConverterDiagnostic extends Diagnostic { + + private final DiagnosticInfo diagnosticInfo; + private final Location location; + private final List> properties; + private final String message; + private final String severity; + + public XMLToRecordConverterDiagnostic(String code, String message, DiagnosticSeverity severity, + Location location, Object[] args) { + this.diagnosticInfo = new DiagnosticInfo(code, message, severity); + this.location = location; + this.properties = Collections.emptyList(); + this.message = MessageFormat.format(message, args); + this.severity = severity.name(); + } + + @Override + public Location location() { + return this.location; + } + + @Override + public DiagnosticInfo diagnosticInfo() { + return this.diagnosticInfo; + } + + @Override + public String message() { + return this.message; + } + + @Override + public List> properties() { + return this.properties; + } + + public String getSeverity() { + return this.severity; + } + + @Override + public String toString() { + String severity = this.diagnosticInfo().severity().toString(); + return "[" + severity + "] " + this.message(); + } +} diff --git a/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/util/ConverterUtils.java b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/util/ConverterUtils.java new file mode 100644 index 000000000000..a1b088a966b2 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/io/ballerina/xmltorecordconverter/util/ConverterUtils.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter.util; + +import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.ArrayTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.ParenthesisedTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SyntaxInfo; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.UnionTypeDescriptorNode; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.ballerina.identifier.Utils.escapeSpecialCharacters; +import static io.ballerina.identifier.Utils.unescapeUnicodeCodepoints; + +/** + * Util methods for XML to record converter. + * + * @since 2201.2.0 + */ +public class ConverterUtils { + + private ConverterUtils() {} + + private static final String QUOTED_IDENTIFIER_PREFIX = "'"; + private static final String ESCAPE_NUMERIC_PATTERN = "\\b\\d.*"; + private static final List KEYWORDS = SyntaxInfo.keywords(); + + /** + * This method returns the identifiers with special characters. + * + * @param identifier Identifier name. + * @return {@link String} Special characters escaped identifier. + */ + public static String escapeIdentifier(String identifier) { + if (KEYWORDS.stream().anyMatch(identifier::equals)) { + return "'" + identifier; + } else { + if (identifier.startsWith(QUOTED_IDENTIFIER_PREFIX)) { + identifier = identifier.substring(1); + } + identifier = unescapeUnicodeCodepoints(identifier); + identifier = escapeSpecialCharacters(identifier); + if (identifier.matches(ESCAPE_NUMERIC_PATTERN)) { + identifier = "\\" + identifier; + } + return identifier; + } + } + + /** + * This method returns the sorted TypeDescriptorNode list. + * + * @param typeDescriptorNodes List of TypeDescriptorNodes has to be sorted. + * @return {@link List} The sorted TypeDescriptorNode list. + */ + public static List sortTypeDescriptorNodes(List typeDescriptorNodes) { + List nonArrayNodes = typeDescriptorNodes.stream() + .filter(node -> !(node instanceof ArrayTypeDescriptorNode)).collect(Collectors.toList()); + List arrayNodes = typeDescriptorNodes.stream() + .filter(node -> (node instanceof ArrayTypeDescriptorNode)).collect(Collectors.toList()); + nonArrayNodes.sort(Comparator.comparing(TypeDescriptorNode::toSourceCode)); + arrayNodes.sort((node1, node2) -> { + ArrayTypeDescriptorNode arrayNode1 = (ArrayTypeDescriptorNode) node1; + ArrayTypeDescriptorNode arrayNode2 = (ArrayTypeDescriptorNode) node2; + return getNumberOfDimensions(arrayNode1).equals(getNumberOfDimensions(arrayNode2)) ? + (arrayNode1).memberTypeDesc().toSourceCode() + .compareTo((arrayNode2).memberTypeDesc().toSourceCode()) : + getNumberOfDimensions(arrayNode1) - getNumberOfDimensions(arrayNode2); + }); + return Stream.concat(nonArrayNodes.stream(), arrayNodes.stream()).collect(Collectors.toList()); + } + + /** + * This method returns the number of dimensions of an ArrayTypeDescriptorNode. + * + * @param arrayNode ArrayTypeDescriptorNode for which the no. of dimensions has to be calculated. + * @return {@link Integer} The total no. of dimensions of the ArrayTypeDescriptorNode. + */ + public static Integer getNumberOfDimensions(ArrayTypeDescriptorNode arrayNode) { + int totalDimensions = arrayNode.dimensions().size(); + if (arrayNode.memberTypeDesc() instanceof ArrayTypeDescriptorNode) { + totalDimensions += getNumberOfDimensions((ArrayTypeDescriptorNode) arrayNode.memberTypeDesc()); + } + return totalDimensions; + } + + /** + * This method returns a list of TypeDescriptorNodes extracted from a UnionTypeDescriptorNode. + * + * @param typeDescNode UnionTypeDescriptorNode for which that has to be extracted. + * @return {@link List} The list of extracted TypeDescriptorNodes. + */ + public static List extractUnionTypeDescNode(TypeDescriptorNode typeDescNode) { + List extractedTypeDescNodes = new ArrayList<>(); + TypeDescriptorNode extractedTypeDescNode = typeDescNode; + if (typeDescNode.kind().equals(SyntaxKind.PARENTHESISED_TYPE_DESC)) { + extractedTypeDescNode = extractParenthesisedTypeDescNode(typeDescNode); + } + if (extractedTypeDescNode.kind().equals(SyntaxKind.UNION_TYPE_DESC)) { + UnionTypeDescriptorNode unionTypeDescNode = (UnionTypeDescriptorNode) extractedTypeDescNode; + TypeDescriptorNode leftTypeDescNode = unionTypeDescNode.leftTypeDesc(); + TypeDescriptorNode rightTypeDescNode = unionTypeDescNode.rightTypeDesc(); + extractedTypeDescNodes.addAll(extractUnionTypeDescNode(leftTypeDescNode)); + extractedTypeDescNodes.addAll(extractUnionTypeDescNode(rightTypeDescNode)); + } else { + extractedTypeDescNodes.add(extractedTypeDescNode); + } + return extractedTypeDescNodes; + } + + private static TypeDescriptorNode extractParenthesisedTypeDescNode(TypeDescriptorNode typeDescNode) { + if (typeDescNode instanceof ParenthesisedTypeDescriptorNode) { + return extractParenthesisedTypeDescNode(((ParenthesisedTypeDescriptorNode) typeDescNode).typedesc()); + } else { + return typeDescNode; + } + } + + /** + * This method returns the SyntaxToken corresponding to the JsonPrimitive. + * + * @param value JsonPrimitive that has to be classified. + * @return {@link Token} Classified Syntax Token. + */ + public static Token getPrimitiveTypeName(String value) { + if (isBoolean(value)) { + return AbstractNodeFactory.createToken(SyntaxKind.BOOLEAN_KEYWORD); + } else if (isInteger(value)) { + return AbstractNodeFactory.createToken(SyntaxKind.INT_KEYWORD); + } else if (isDouble(value)) { + return AbstractNodeFactory.createToken(SyntaxKind.DECIMAL_KEYWORD); + } + return AbstractNodeFactory.createToken(SyntaxKind.STRING_KEYWORD); + } + + private static boolean isBoolean(String value) { + return value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false"); + } + + private static boolean isInteger(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + private static boolean isDouble(String value) { + try { + Double.parseDouble(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/misc/xml-to-record-converter/src/main/java/module-info.java b/misc/xml-to-record-converter/src/main/java/module-info.java new file mode 100644 index 000000000000..5d73798c781f --- /dev/null +++ b/misc/xml-to-record-converter/src/main/java/module-info.java @@ -0,0 +1,15 @@ +module io.ballerina.xmltorecordconverter { + requires io.ballerina.formatter.core; + requires io.ballerina.identifier; + requires io.ballerina.lang; + requires io.ballerina.language.server.commons; + requires io.ballerina.parser; + requires io.ballerina.tools.api; + requires java.xml; + requires javatuples; + requires org.apache.commons.lang3; + requires org.eclipse.lsp4j.jsonrpc; + + exports io.ballerina.xmltorecordconverter; +// exports io.ballerina.jsonmapper.diagnostic; +} diff --git a/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaClientCapabilitySetter b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaClientCapabilitySetter new file mode 100644 index 000000000000..741b2f672d60 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaClientCapabilitySetter @@ -0,0 +1 @@ +io.ballerina.xmltorecordconverter.XMLToRecordConverterClientCapabilitySetter \ No newline at end of file diff --git a/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaServerCapabilitySetter b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaServerCapabilitySetter new file mode 100644 index 000000000000..03a06a568d50 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.registration.BallerinaServerCapabilitySetter @@ -0,0 +1 @@ +io.ballerina.xmltorecordconverter.XMLToRecordConverterServerCapabilitySetter \ No newline at end of file diff --git a/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService new file mode 100644 index 000000000000..2cf3ace93a59 --- /dev/null +++ b/misc/xml-to-record-converter/src/main/resources/META-INF/services/org.ballerinalang.langserver.commons.service.spi.ExtendedLanguageServerService @@ -0,0 +1 @@ +io.ballerina.xmltorecordconverter.XMLToRecordConverterService diff --git a/misc/xml-to-record-converter/src/test/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterTests.java b/misc/xml-to-record-converter/src/test/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterTests.java new file mode 100644 index 000000000000..7aec4e97e295 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/java/io/ballerina/xmltorecordconverter/XMLToRecordConverterTests.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.xmltorecordconverter; + +import org.ballerinalang.langserver.util.TestUtil; +import org.eclipse.lsp4j.jsonrpc.Endpoint; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + * Tests for XMLToRecordConverter. + */ +public class XMLToRecordConverterTests { + + private static final Path RES_DIR = Paths.get("src/test/resources/").toAbsolutePath(); + private static final String XML_DIR = "xml"; + private static final String BAL_DIR = "ballerina"; + + private final Path sample0XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_0.xml"); + private final Path sample0Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_0.bal"); + + private final Path sample1XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_1.xml"); + private final Path sample1Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_1.bal"); + + private final Path sample2XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_2.xml"); + private final Path sample2Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_2.bal"); + + private final Path sample3XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_3.xml"); + private final Path sample3Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_3.bal"); + + private final Path sample4XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_4.xml"); + private final Path sample4Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_4.bal"); + + private final Path sample5XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_5.xml"); + private final Path sample5Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_5.bal"); + + private final Path sample6XML = RES_DIR.resolve(XML_DIR) + .resolve("sample_6.xml"); + private final Path sample6Bal = RES_DIR.resolve(BAL_DIR) + .resolve("sample_6.bal"); + + private static final String XMLToRecordServiceEP = "xmlToRecord/convert"; + + + @Test(description = "testBasicXML") + public void testBasicXML() throws IOException { + String xmlFileContent = Files.readString(sample0XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample0Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLWithTwoLevels") + public void testBasicXMLWithTwoLevels() throws IOException { + String xmlFileContent = Files.readString(sample1XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample1Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLWithTwoLevelsWithDifferentElements") + public void testBasicXMLWithTwoLevelsWithDifferentElements() throws IOException { + String xmlFileContent = Files.readString(sample2XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample2Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLWithNodesWithSameName") + public void testBasicXMLWithNodesWithSameName() throws IOException { + String xmlFileContent = Files.readString(sample3XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample3Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLForObjectArray") + public void testBasicXMLForObjectArray() throws IOException { + String xmlFileContent = Files.readString(sample4XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample4Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLWithNodesWithSameNameAndDifferentDataTypes") + public void testBasicXMLWithNodesWithSameNameAndDifferentDataTypes() throws IOException { + String xmlFileContent = Files.readString(sample5XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample5Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testBasicXMLWithNodesWithPrimitiveAndOtherDataTypes") + public void testBasicXMLWithNodesWithPrimitiveAndOtherDataTypes() throws IOException { + String xmlFileContent = Files.readString(sample6XML); + String generatedCodeBlock = XMLToRecordConverter.convert(xmlFileContent, false, false, false, false) + .getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample6Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } + + @Test(description = "testXMLToRecordService") + public void testXMLToRecordService() throws IOException, ExecutionException, InterruptedException { + Endpoint serviceEndpoint = TestUtil.initializeLanguageSever(); + String xmlValue = Files.readString(sample0XML); + + XMLToRecordRequest request = new XMLToRecordRequest(xmlValue, false, false, false); + CompletableFuture result = serviceEndpoint.request(XMLToRecordServiceEP, request); + XMLToRecordResponse response = (XMLToRecordResponse) result.get(); + String generatedCodeBlock = response.getCodeBlock().replaceAll("\\s+", ""); + String expectedCodeBlock = Files.readString(sample0Bal).replaceAll("\\s+", ""); + Assert.assertEquals(generatedCodeBlock, expectedCodeBlock); + } +} diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_0.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_0.bal new file mode 100644 index 000000000000..455a1e91a277 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_0.bal @@ -0,0 +1,11 @@ +@xmldata:Name { + value: "book" +} +type Book record { + string author; + string title; + string genre; + decimal price; + string publish_date; + string description; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_1.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_1.bal new file mode 100644 index 000000000000..53cd981d9594 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_1.bal @@ -0,0 +1,20 @@ +@xmldata:Name { + value: "book" +} +type Book record { + string author; + string title; + string genre; + decimal price; + string publish_date; + string description; + @Attribute + string id; +}; + +@xmldata:Name { + value: "catalog" +} +type Catalog record { + Book book; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_2.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_2.bal new file mode 100644 index 000000000000..96a1a8367175 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_2.bal @@ -0,0 +1,38 @@ +@xmldata:Name { + value: "guest" +} +type Guest record { + string firstName; + string lastName; + string email; + string phone; +}; + +@xmldata:Name { + value: "reservationDetails" +} +type ReservationDetails record { + string location; + string hotelName; + string arrivalDate; + string departureDate; + int roomCount; + string roomType; + string specialRequest; +}; + +@xmldata:Name { + value: "reservation" +} +type Reservation record { + Guest guest; + ReservationDetails reservationDetails; +}; + + +@xmldata:Name { + value: "reservations" +} +type Reservations record { + Reservation reservation; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_3.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_3.bal new file mode 100644 index 000000000000..41ff944ca729 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_3.bal @@ -0,0 +1,30 @@ +@xmldata:Name { + value: "address" +} +type Address record { + string street; + string city; + string country; +}; + +@xmldata:Name { + value: "codes" +} +type Codes record { + int[] item; +}; + +@xmldata:Name { + value: "bookstore" +} +type Bookstore record { + string storeName; + int postalCode; + boolean isOpen; + Address address; + Codes codes; + @Attribute + string status; + @Attribute + string xmlns\:ns0; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_4.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_4.bal new file mode 100644 index 000000000000..9b2e372ea949 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_4.bal @@ -0,0 +1,17 @@ +@xmldata:Name { + value: "book" +} +type Book record { + string author; + string title; + decimal price; + @Attribute + string id; +}; + +@xmldata:Name { + value: "catalog" +} +type Catalog record { + Book[] book; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_5.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_5.bal new file mode 100644 index 000000000000..6fa006325999 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_5.bal @@ -0,0 +1,20 @@ +@xmldata:Name { + value: "codes" +} +type Codes record { + (decimal|int|string)[] item; +}; + +@xmldata:Name { + value: "bookstore" +} +type Bookstore record { + string storeName; + int postalCode; + boolean isOpen; + Codes codes; + @Attribute + string status; + @Attribute + string xmlns\:ns0; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/ballerina/sample_6.bal b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_6.bal new file mode 100644 index 000000000000..f5035bac366d --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/ballerina/sample_6.bal @@ -0,0 +1,27 @@ +@xmldata:Name {value: "item"} +type Item record { + int ItemCode; +}; + +type OtherItem record { + string ItemCode; +}; + +@xmldata:Name {value: "codes"} +type Codes record { + (decimal|int|string)[] item; + Item[] item; + OtherItem OtherItem; +}; + +@xmldata:Name {value: "bookstore"} +type Bookstore record { + string storeName; + int postalCode; + boolean isOpen; + Codes codes; + @Attribute + string status; + @Attribute + string xmlns\:ns0; +}; diff --git a/misc/xml-to-record-converter/src/test/resources/testng.xml b/misc/xml-to-record-converter/src/test/resources/testng.xml new file mode 100644 index 000000000000..817607fa101a --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/testng.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_0.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_0.xml new file mode 100644 index 000000000000..91bd8a120e49 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_0.xml @@ -0,0 +1,10 @@ + + + Gambardella, Matthew + XML Developer's Guide + Computer + 44.95 + 2000-10-01 + An in-depth look at creating applications + with XML. + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_1.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_1.xml new file mode 100644 index 000000000000..a09d00d3cadb --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_1.xml @@ -0,0 +1,12 @@ + + + + Gambardella, Matthew + XML Developer's Guide + Computer + 44.95 + 2000-10-01 + An in-depth look at creating applications + with XML. + + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_2.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_2.xml new file mode 100644 index 000000000000..0dd5079d4f1f --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_2.xml @@ -0,0 +1,19 @@ + + + + Jimmy + Anderson + jimmy@neptide.com + +1 (858) 495-3964 + + + CHIHR + Grand Hyatt + 2023-09-05 + 2023-09-16 + 2 + Deluxe Room + Champagne in room + + + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_3.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_3.xml new file mode 100644 index 000000000000..2fb63f8e876d --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_3.xml @@ -0,0 +1,17 @@ + + foo + 94 + true +
+ Galle Road + Colombo + Sri Lanka +
+ + 4 + 8 + 9 + +
+ + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_4.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_4.xml new file mode 100644 index 000000000000..f7f2159cbd58 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_4.xml @@ -0,0 +1,13 @@ + + + + Gambardella, Matthew + XML Developer's Guide + 44.95 + + + Ralls, Kim + Midnight Rain + 5.95 + + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_5.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_5.xml new file mode 100644 index 000000000000..69bd0bf296fc --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_5.xml @@ -0,0 +1,10 @@ + + foo + 94 + true + + 4 + 8.15 + String Item + + diff --git a/misc/xml-to-record-converter/src/test/resources/xml/sample_6.xml b/misc/xml-to-record-converter/src/test/resources/xml/sample_6.xml new file mode 100644 index 000000000000..909f799ac2a8 --- /dev/null +++ b/misc/xml-to-record-converter/src/test/resources/xml/sample_6.xml @@ -0,0 +1,19 @@ + + foo + 94 + true + + 4 + 8.15 + String Item + + 23 + + + 23Str + + + 23Str + + + diff --git a/settings.gradle b/settings.gradle index e93fe3f475c9..a8ee0dc1e0de 100644 --- a/settings.gradle +++ b/settings.gradle @@ -32,6 +32,7 @@ include(':ballerina-lang-test') include(':ballerina-langlib:test') include(':ballerina-io-internal') include(':json-mapper') +include(':xml-to-record-converter') include(':lib-creator') include(':ballerina-runtime') include(':ballerina-rt') @@ -184,6 +185,7 @@ project(':ballerina-treegen').projectDir = file('compiler/ballerina-treegen') project(':ballerina-test-utils').projectDir = file('tests/ballerina-test-utils') project(':ballerina-io-internal').projectDir = file('misc/io-internal') project(':json-mapper').projectDir = file('misc/json-to-record-converter') +project(':xml-to-record-converter').projectDir = file('misc/xml-to-record-converter') project(':lib-creator').projectDir = file('misc/lib-creator') project(':ballerina-bindgen').projectDir = file('misc/ballerina-bindgen') project(':maven-resolver').projectDir = file('misc/maven-resolver')