diff --git a/README.md b/README.md index a9516c3..ad067fc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,147 @@ -# xsd-tools -This is the source code of the XSD tool which converts XSD files into Ballerina records +# Ballerina XSD Tool + +[![Build](https://github.com/ballerina-platform/xsd-tools/actions/workflows/build-timestamped-master.yml/badge.svg)](https://github.com/ballerina-platform/xsd-tools/actions/workflows/build-timestamped-master.yml) +[![codecov](https://codecov.io/gh/ballerina-platform/xsd-tools/branch/master/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/xsd-tools) +[![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/xsd-tools.svg)](https://github.com/ballerina-platform/xsd-tools/commits/master) +[![GitHub issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-standard-library/module/xsd-tools.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-library/labels/module%2Fxsd-tools) + +`XSD` (XML Schema Definition) is an approach to define the structure, elements, and constraints of XML documents. It is widely used for validating the content and structure of XML files. + +The Ballerina XSD Tool simplifies the generation of Ballerina record types from an XSD specification, improving the user experience when integrating with XML-based operations in Ballerina. + +### Installation + +Execute the command below to pull the XSD tool from Ballerina Central. + +```bash +$ bal tool pull xsd +``` + +### Usage + +The XSD tool allows you to generate Ballerina record types from an XSD specification. + +To generate Ballerina types, use the following command. It is mandatory to run the command inside a Ballerina project. + +```bash +$ bal xsd + [--module ] +``` + +#### Command options + +| Option | Description | Mandatory/Optional | +|--------|-------------|--------------------| +| `` | (Required) The path to the XSD file | Mandatory | +| `-m`, `--module` | The name of the module in which the Ballerina record types are generated | Optional | + +### Generate types for the given XSD file + +Use the following command to generate Ballerina record types for all elements defined in the specified XSD file. By default, the generated `types.bal` file will be placed in the default module of the current Ballerina project. + +```bash +$ bal xsd +``` + +For example, + +```bash +$ bal xsd sample.xsd +``` + +If successful, you will see the following output. + +```bash +The 'types.bal' file is written to the default module +``` + +#### Generate types in a specific module + +To generate the Ballerina record types in a specific module, use the `--module` option. + +```bash +$ bal xsd --module +``` + +For example, + +```bash +$ bal xsd sample.xsd --module custom +``` + +This will generate a `types.bal` file inside the `custom` submodule within the Ballerina project. + +The following output will be displayed. + +```bash +The 'types.bal' file is written to 'modules/custom' +``` + +Upon successful execution, the generated files will include, + +```bash +modules/ +└── custom/ + └── types.bal +``` + +## Building from the Source + +### Setting Up the Prerequisites + +1. OpenJDK 21 ([Adopt OpenJDK](https://adoptopenjdk.net/) or any other OpenJDK distribution) + + >**Info:** You can also use [Oracle JDK](https://www.oracle.com/java/technologies/javase-downloads.html). Set the JAVA_HOME environment variable to the pathname of the directory into which you installed JDK. + +2. Export GitHub Personal access token with read package permissions as follows, + ``` + export packageUser= + export packagePAT= + ``` + +### Building the Source + +Execute the commands below to build from the source. + +1. To build the library: + + ./gradlew clean build + +2. To run the integration tests: + + ./gradlew clean test + +3. To build the module without the tests: + + ./gradlew clean build -x test + +4. To publish to maven local: + + ./gradlew clean build publishToMavenLocal + +5. Publish the generated artifacts to the local Ballerina central repository: + + ./gradlew clean build -PpublishToLocalCentral=true + +6. Publish the generated artifacts to the Ballerina central repository: + + ./gradlew clean build -PpublishToCentral=true + +## Contributing to Ballerina + +As an open-source project, Ballerina welcomes contributions from the community. + +You can also check for [open issues](https://github.com/ballerina-platform/xsd-tools/issues) that +interest you. We look forward to receiving your contributions. + +For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). + +## Code of Conduct + +All contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). + +## Useful Links + +* Chat live with us via our [Discord server](https://discord.gg/ballerinalang). +* Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. +* View the [Ballerina performance test results](https://github.com/ballerina-platform/ballerina-lang/blob/master/performance/benchmarks/summary.md). diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle index da05a18..9f9de56 100644 --- a/build-config/checkstyle/build.gradle +++ b/build-config/checkstyle/build.gradle @@ -14,6 +14,8 @@ * limitations under the License. * */ +import groovy.xml.XmlParser +import groovy.xml.XmlUtil plugins { id "de.undercouch.download" @@ -21,6 +23,7 @@ plugins { apply plugin: 'java' +// Task to download the Checkstyle rule files task downloadCheckstyleRuleFiles(type: Download) { src([ 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', @@ -31,14 +34,17 @@ task downloadCheckstyleRuleFiles(type: Download) { dest buildDir } +// Disable jar task jar { enabled = false } +// Disable clean task clean { enabled = false } +// Add modified checkstyle.xml and suppressions.xml as artifacts artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { builtBy('downloadCheckstyleRuleFiles') } diff --git a/build-config/spotbugs-exclude.xml b/build-config/spotbugs-exclude.xml new file mode 100644 index 0000000..e6abf7d --- /dev/null +++ b/build-config/spotbugs-exclude.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index 62b9cde..5c9c360 100644 --- a/build.gradle +++ b/build.gradle @@ -87,7 +87,7 @@ tasks.register('clean') { } tasks.register('build') { - dependsOn(":module-ballerina-xsd:build") + dependsOn(":module-ballerina-xsd:build") } tasks.register('test') { diff --git a/gradle.properties b/gradle.properties index 8cef826..6338317 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,9 +3,9 @@ group=io.ballerina version=0.1.0-SNAPSHOT # Dependencies -ballerinaLangVersion=2201.10.0 +ballerinaLangVersion=2201.11.0 checkstylePluginVersion=10.12.0 -spotbugsPluginVersion=5.0.14 +spotbugsPluginVersion=6.0.18 shadowJarPluginVersion=8.1.1 downloadPluginVersion=5.4.0 releasePluginVersion=2.8.0 diff --git a/module-ballerina-xsd/Dependencies.toml b/module-ballerina-xsd/Dependencies.toml index 0767bad..1de1aac 100644 --- a/module-ballerina-xsd/Dependencies.toml +++ b/module-ballerina-xsd/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0" +distribution-version = "2201.11.0" [[package]] org = "ballerina" diff --git a/module-ballerina-xsd/Module.md b/module-ballerina-xsd/Module.md index 5c46f48..0d07f48 100644 --- a/module-ballerina-xsd/Module.md +++ b/module-ballerina-xsd/Module.md @@ -1 +1,81 @@ -## Module Overview +## Overview + +`XSD` (XML Schema Definition) is an approach to define the structure, elements, and constraints of XML documents. It is widely used for validating the content and structure of XML files. + +The Ballerina XSD Tool simplifies the generation of Ballerina record types from an XSD specification, improving the user experience when integrating with XML-based operations in Ballerina. + +### Installation + +Execute the command below to pull the XSD tool from Ballerina Central. + +```bash +$ bal tool pull xsd +``` + +### Usage + +The XSD tool allows you to generate Ballerina record types from an XSD specification. + +To generate Ballerina types, use the following command. It is mandatory to run the command inside a Ballerina project. + +```bash +$ bal xsd + [--module ] +``` + +#### Command options + +| Option | Description | Mandatory/Optional | +|--------|-------------|--------------------| +| `` | The path to the XSD file | Mandatory | +| `-m`, `--module` | The name of the module in which the Ballerina record types are generated | Optional | + +### Generate types for the given XSD file + +Use the following command to generate Ballerina record types for all elements defined in the specified XSD file. By default, the generated `types.bal` file will be placed in the default module of the current Ballerina project. + +```bash +$ bal xsd +``` + +For example, + +```bash +$ bal xsd sample.xsd +``` + +If successful, you will see the following output. + +```bash +The 'types.bal' file is written to the default module +``` + +#### Generate types in a specific module + +To generate the Ballerina record types in a specific module, use the `--module` option. + +```bash +$ bal xsd --module +``` + +For example, + +```bash +$ bal xsd sample.xsd --module custom +``` + +This will generate a `types.bal` file inside the `custom` submodule within the Ballerina project. + +The following output will be displayed. + +```bash +The 'types.bal' file is written to 'modules/custom' +``` + +Upon successful execution, the generated files will include, + +```bash +modules/ +└── custom/ + └── types.bal +``` diff --git a/module-ballerina-xsd/Package.md b/module-ballerina-xsd/Package.md index de78db2..c22b448 100644 --- a/module-ballerina-xsd/Package.md +++ b/module-ballerina-xsd/Package.md @@ -1 +1,81 @@ ## Package Overview + +`XSD` (XML Schema Definition) is an approach to define the structure, elements, and constraints of XML documents. It is widely used for validating the content and structure of XML files. + +The Ballerina XSD Tool simplifies the generation of Ballerina record types from an XSD specification, improving the user experience when integrating with XML-based operations in Ballerina. + +### Installation + +Execute the command below to pull the XSD tool from Ballerina Central. + +```bash +$ bal tool pull xsd +``` + +### Usage + +The XSD tool allows you to generate Ballerina record types from an XSD specification. + +To generate Ballerina types, use the following command. It is mandatory to run the command inside a Ballerina project. + +```bash +$ bal xsd + [--module ] +``` + +#### Command options + +| Option | Description | Mandatory/Optional | +|--------|-------------|--------------------| +| `` | (Required) The path to the XSD file | Mandatory | +| `-m`, `--module` | The name of the module in which the Ballerina record types are generated | Optional | + +### Generate types for the given XSD file + +Use the following command to generate Ballerina record types for all elements defined in the specified XSD file. By default, the generated `types.bal` file will be placed in the default module of the current Ballerina project. + +```bash +$ bal xsd +``` + +For example, + +```bash +$ bal xsd sample.xsd +``` + +If successful, you will see the following output. + +```bash +The 'types.bal' file is written to the default module +``` + +#### Generate types in a specific module + +To generate the Ballerina record types in a specific module, use the `--module` option. + +```bash +$ bal xsd --module +``` + +For example, + +```bash +$ bal xsd sample.xsd --module custom +``` + +This will generate a `types.bal` file inside the `custom` submodule within the Ballerina project. + +The following output will be displayed. + +```bash +The 'types.bal' file is written to 'modules/custom' +``` + +Upon successful execution, the generated files will include, + +```bash +modules/ +└── custom/ + └── types.bal +``` diff --git a/xsd-cli/build.gradle b/xsd-cli/build.gradle index 5ee4d31..9d4ae7b 100644 --- a/xsd-cli/build.gradle +++ b/xsd-cli/build.gradle @@ -11,6 +11,8 @@ configurations.all { dependencies { implementation group: 'org.ballerinalang', name: 'ballerina-cli', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" implementation group: 'info.picocli', name: 'picocli', version: "${picocliVersion}" testImplementation group: 'junit', name: 'junit', version: "${junitVersion}" testImplementation group: 'org.testng', name: 'testng', version: "${testngVersion}" diff --git a/xsd-cli/src/main/java/io/ballerina/xsd/cmd/XsdCmd.java b/xsd-cli/src/main/java/io/ballerina/xsd/cli/XsdCmd.java similarity index 62% rename from xsd-cli/src/main/java/io/ballerina/xsd/cmd/XsdCmd.java rename to xsd-cli/src/main/java/io/ballerina/xsd/cli/XsdCmd.java index a0becdf..fd21afb 100644 --- a/xsd-cli/src/main/java/io/ballerina/xsd/cmd/XsdCmd.java +++ b/xsd-cli/src/main/java/io/ballerina/xsd/cli/XsdCmd.java @@ -16,8 +16,9 @@ * under the License. */ -package io.ballerina.xsd.cmd; +package io.ballerina.xsd.cli; +import io.ballerina.projects.util.ProjectUtils; import io.ballerina.xsd.core.Response; import io.ballerina.xsd.core.XSDToRecord; import org.w3c.dom.Document; @@ -29,7 +30,6 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -40,6 +40,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Scanner; import javax.xml.parsers.DocumentBuilder; @@ -57,9 +58,14 @@ ) public class XsdCmd implements BLauncherCmd { private static final String CMD_NAME = "xsd"; - private static final String FILE_OVERWRITE_PROMPT = "File already exists at %s. Overwrite? (y/N): "; + private static final String FILE_OVERWRITE_PROMPT = "The file '%s' already exists at %s. Overwrite? (y/N): "; public static final String INVALID_BALLERINA_DIRECTORY_ERROR = "Invalid Ballerina package directory: %s, cannot find 'Ballerina.toml' file"; + public static final String INVALID_DIRECTORY_PATH = "Error: Invalid directory path has been provided. " + + "Output path '%s' is a file"; + private static final String AUTO_GENERATED_MESSAGE = "// AUTO-GENERATED FILE. DO NOT MODIFY.\n" + + "// This file is auto-generated by the Ballerina XSD tool."; + public static final String TYPES_FILE_NAME = "types.bal"; private final PrintStream outStream; private final boolean exitWhenFinish; @CommandLine.Option(names = {"-h", "--help"}, hidden = true) @@ -68,9 +74,9 @@ public class XsdCmd implements BLauncherCmd { @CommandLine.Parameters(description = "Input file path of the XSD schema") private final List argList = new ArrayList<>(); - @CommandLine.Option(names = {"-o", "--output"}, description = "Destination file path of the generated types from " + - "the XSD file") - private String outputPath = "types.bal"; + @CommandLine.Option(names = {"-m", "--module"}, description = "The name of the module in which the Ballerina " + + "client and record types are generated.") + private String outputPath = ""; public XsdCmd() { this.outStream = System.err; @@ -86,9 +92,29 @@ public void execute() { return; } Path currentDir = Paths.get("").toAbsolutePath(); - Path commandPath = currentDir.resolve("Ballerina.toml"); - if (!Files.exists(commandPath)) { - outStream.printf((INVALID_BALLERINA_DIRECTORY_ERROR) + "%n", commandPath); + if (!ProjectUtils.isBallerinaProject(currentDir)) { + outStream.printf(INVALID_BALLERINA_DIRECTORY_ERROR + "%n", currentDir); + exitOnError(); + return; + } + if (!ProjectUtils.validateModuleName(outputPath)) { + outStream.println("ERROR: invalid module name : '" + outputPath + "' :\n" + + "module name can only contain alphanumerics, underscores and periods"); + exitOnError(); + return; + } else if (!ProjectUtils.validateNameLength(outputPath)) { + outStream.println("ERROR: invalid module name : '" + outputPath + "' :\n" + + "maximum length of module name is 256 characters"); + exitOnError(); + return; + } + Path outputDirPath = Paths.get(outputPath).toAbsolutePath(); + if (!Objects.equals(outputPath, EMPTY_STRING)) { + Path basePath = Paths.get("modules").toAbsolutePath(); + outputDirPath = basePath.resolve(outputPath).normalize(); + } + if (Files.exists(outputDirPath) && !Files.isDirectory(outputDirPath)) { + outStream.printf(INVALID_DIRECTORY_PATH + "%n", outputPath); exitOnError(); return; } @@ -99,6 +125,9 @@ public void execute() { return; } try { + if (Files.notExists(outputDirPath)) { + Files.createDirectories(outputDirPath); + } if (!Files.exists(Path.of(argList.get(0)))) { outStream.println(argList.get(0) + " file does not exist."); return; @@ -111,14 +140,7 @@ public void execute() { exitOnError(); return; } - Path destinationFile = Files.exists(Paths.get(outputPath)) - ? handleFileOverwrite(Paths.get(outputPath), outStream) : Paths.get(outputPath); - Path parentDirectory = destinationFile.getParent(); - if (parentDirectory != null && !Files.exists(parentDirectory)) { - Files.createDirectories(parentDirectory); - } - Files.writeString(destinationFile, result.types()); - outStream.println("Output is successfully written to " + destinationFile); + writeSourceToFiles(outputDirPath, result); } catch (ParserConfigurationException | SAXException e) { outStream.println("XSD file contains errors. " + e.getLocalizedMessage()); } catch (IOException e) { @@ -129,28 +151,47 @@ public void execute() { } } - public static Path handleFileOverwrite(Path destinationFile, PrintStream outStream) { - if (!Files.exists(destinationFile)) { - return destinationFile; - } - String filePath = destinationFile.toString(); - outStream.printf(FILE_OVERWRITE_PROMPT, filePath); - String response = new Scanner(System.in).nextLine().trim().toLowerCase(); - if (response.equals("y")) { - return destinationFile; + private void writeSourceToFiles(Path outputPath, Response response) throws IOException { + Path clientPath = outputPath.resolve(TYPES_FILE_NAME); + String fileName = clientPath.getFileName().toString(); + if (Files.exists(clientPath)) { + outStream.printf(FILE_OVERWRITE_PROMPT, fileName, getModuleName(clientPath)); + String overwriteAccess = new Scanner(System.in).nextLine().trim().toLowerCase(); + if (overwriteAccess.equals("y")) { + generateFile(response.types(), clientPath, fileName); + } else { + outStream.printf("The operation is cancelled %n"); + } + } else { + generateFile(response.types(), clientPath, fileName); } - int counter = 1; - String fileName = new File(filePath).getName(); - int dotIndex = fileName.lastIndexOf('.'); - String baseName = dotIndex == -1 ? fileName : fileName.substring(0, dotIndex); - String extension = dotIndex == -1 ? EMPTY_STRING : fileName.substring(dotIndex); - String parentPath = new File(filePath).getParent() != null ? new File(filePath).getParent() : EMPTY_STRING; - while (Files.exists(destinationFile)) { - String newFileName = baseName + "." + counter + extension; - destinationFile = Path.of(parentPath, newFileName); - counter++; + } + + private void generateFile(String content, Path clientPath, String fileName) throws IOException { + Files.writeString(clientPath, addAutoGeneratedMessage(content)); + String outputModule = getModuleName(clientPath); + outStream.printf("The '%s' file is written to %s %n", fileName, outputModule); + } + + private static String getModuleName(Path clientPath) { + String outputModule; + String destinationPath = clientPath.toString(); + if (destinationPath.contains("modules")) { + int startIndex = destinationPath.indexOf("modules"); + int endIndex = destinationPath.lastIndexOf("/"); + if (endIndex > startIndex) { + outputModule = destinationPath.substring(startIndex, endIndex); + } else { + outputModule = destinationPath.substring(startIndex); + } + } else { + outputModule = "the default module"; } - return destinationFile; + return outputModule; + } + + private String addAutoGeneratedMessage(String content) { + return AUTO_GENERATED_MESSAGE + "\n\n" + content; } private static Document parseXSD(String xsdData) throws Exception { diff --git a/xsd-cli/src/main/java/module-info.java b/xsd-cli/src/main/java/module-info.java index d10399b..151bef9 100644 --- a/xsd-cli/src/main/java/module-info.java +++ b/xsd-cli/src/main/java/module-info.java @@ -1,7 +1,28 @@ -module io.ballerina.xsd.cmd { +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. 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. + */ + +module io.ballerina.xsd.cli { requires io.ballerina.xsd.core; requires info.picocli; requires io.ballerina.cli; requires io.ballerina.tools.api; requires java.xml; + requires io.ballerina.lang; + + exports io.ballerina.xsd.cli; } diff --git a/xsd-cli/src/main/resources/META-INF/services/io.ballerina.cli.BLauncherCmd b/xsd-cli/src/main/resources/META-INF/services/io.ballerina.cli.BLauncherCmd index cb23102..b348298 100644 --- a/xsd-cli/src/main/resources/META-INF/services/io.ballerina.cli.BLauncherCmd +++ b/xsd-cli/src/main/resources/META-INF/services/io.ballerina.cli.BLauncherCmd @@ -1 +1 @@ -io.ballerina.xsd.cmd.XsdCmd +io.ballerina.xsd.cli.XsdCmd diff --git a/xsd-cli/src/main/resources/cli-docs/xsd-help.help b/xsd-cli/src/main/resources/cli-docs/xsd-help.help index e542419..0bee3de 100644 --- a/xsd-cli/src/main/resources/cli-docs/xsd-help.help +++ b/xsd-cli/src/main/resources/cli-docs/xsd-help.help @@ -1,8 +1,8 @@ NAME - bal xsd - Generate Ballerina types from a XSD file. + bal xsd - Generate Ballerina types from an XSD file. SYNOPSIS - bal xsd [-o | --output] + bal xsd [-m | --module] DESCRIPTION The 'bal xsd' command is used to generate records for a given XSD file. @@ -11,15 +11,14 @@ OPTIONS Path to the XSD file. This is a mandatory input. - -o, --output - Location of the generated Ballerina source code. If this path is not - specified, the output will be written to the same directory from - which the command is run. + -m, --module + The name of the module in which the Ballerina client and record types are generated. EXAMPLE Generate types from an XSD schema. The output will be saved to the types.bal file in the same directory where the command is executed. $ bal xsd schema.xsd - Generate types from an XSD schema with a specified relative path of the output file. - $ bal xsd schema.xsd -o modules/xsd/records.bal + Generate types from an XSD schema with a specified relative path of the output file. The output will be saved + to the 'custom' submodule in the Ballerina project. + $ bal xsd schema.xsd --module custom diff --git a/xsd-core/build.gradle b/xsd-core/build.gradle index 920adaa..c1c9ffd 100644 --- a/xsd-core/build.gradle +++ b/xsd-core/build.gradle @@ -1,5 +1,7 @@ plugins { id 'java' + id 'checkstyle' + id 'com.github.spotbugs' } group = 'io.ballerina' @@ -10,6 +12,9 @@ repositories { } dependencies { + checkstyle project(':checkstyle') + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'formatter-core', version: "${ballerinaLangVersion}" @@ -25,3 +30,48 @@ dependencies { test { useJUnitPlatform() } + + +checkstyle { + toolVersion "${project.checkstylePluginVersion}" + configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +def excludePattern = '**/module-info.java' +tasks.withType(Checkstyle) { + exclude excludePattern +} + +checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") +checkstyleTest.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} + +spotbugsMain { + def classLoader = plugins["com.github.spotbugs"].class.classLoader + def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") + def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") + effort = SpotBugsEffort.MAX + reportLevel = SpotBugsConfidence.LOW + reportsDir = file("$project.buildDir/reports/spotbugs") + reports { + html.enabled true + text.enabled = true + } + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if(excludeFile.exists()) { + excludeFilter = excludeFile + } +} + +spotbugsTest { + enabled = false +} diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/Response.java b/xsd-core/src/main/java/io/ballerina/xsd/core/Response.java index 63faa68..b98805d 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/Response.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/Response.java @@ -23,10 +23,12 @@ import java.util.List; /** - * Represents the response object generated by the XSD-to-Ballerina converter. + * Represents a response that contains the generated types and associated diagnostics. * + * @param types The generated types as a string representation. + * @param diagnostics A list of {@link XsdDiagnostic} objects containing any diagnostic + * messages related to the XSD processing. + * * @since 0.1.0 */ -public record Response(String types, List diagnostics) { - -} +public record Response(String types, List diagnostics) { } diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/Utils.java b/xsd-core/src/main/java/io/ballerina/xsd/core/Utils.java index 318504b..b27ae48 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/Utils.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/Utils.java @@ -38,7 +38,6 @@ import java.util.Map; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.CLOSE_BRACES; -import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.CONTENT_FIELD; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.RECORD_WITH_OPEN_BRACE; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.SEMICOLON; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.STRING; @@ -65,29 +64,29 @@ static ModulePartNode generateModulePartNode(Map nodes, - String element, String type) { + String element, String type, String contentField) { String fields = extractSubstring(nodes.get(type).toString(), RECORD_WITH_OPEN_BRACE, - VERTICAL_BAR + CLOSE_BRACES + SEMICOLON); + VERTICAL_BAR + CLOSE_BRACES + SEMICOLON, contentField); String extendedValue = nodes.get(element) - .toString().replace(type + WHITESPACE + CONTENT_FIELD + SEMICOLON, fields); + .toString().replace(type + WHITESPACE + contentField + SEMICOLON, fields); ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(extendedValue); nodes.put(element, moduleNode); } static void processSingleTypeElements(Map nodes, - String element, String type, String[] tokens) { + String element, String type, String[] tokens, String contentField) { String token = (!nodes.containsKey(type)) || nodes.get(type).toString().contains(XSDVisitorImpl.ENUM) ? STRING : tokens[tokens.length - 2]; - String rootElement = nodes.get(element).toString().replace(type + WHITESPACE + CONTENT_FIELD, - token + WHITESPACE + CONTENT_FIELD); + String rootElement = nodes.get(element).toString().replace(type + WHITESPACE + contentField, + token + WHITESPACE + contentField); ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(rootElement); nodes.put(element, moduleNode); } - static String extractSubstring(String baseString, String startToken, String endToken) { + static String extractSubstring(String baseString, String startToken, String endToken, String contentField) { if (!baseString.contains(startToken)) { return baseString.split(WHITESPACE)[baseString.split(WHITESPACE).length - 2] + - WHITESPACE + CONTENT_FIELD + SEMICOLON; + WHITESPACE + contentField + SEMICOLON; } int startIndex = baseString.indexOf(startToken) + startToken.length(); int endIndex = baseString.indexOf(endToken, startIndex); diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/XSDFactory.java b/xsd-core/src/main/java/io/ballerina/xsd/core/XSDFactory.java index 569c6d6..3b8d418 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/XSDFactory.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/XSDFactory.java @@ -20,8 +20,8 @@ import io.ballerina.xsd.core.component.ComplexType; import io.ballerina.xsd.core.component.Element; -import io.ballerina.xsd.core.component.XSDComponent; import io.ballerina.xsd.core.component.SimpleType; +import io.ballerina.xsd.core.component.XSDComponent; import org.w3c.dom.Node; import java.util.Optional; diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/XSDToRecord.java b/xsd-core/src/main/java/io/ballerina/xsd/core/XSDToRecord.java index 18c0703..fc5411d 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/XSDToRecord.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/XSDToRecord.java @@ -21,11 +21,11 @@ import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.NodeParser; +import io.ballerina.xsd.core.component.XSDComponent; import io.ballerina.xsd.core.diagnostic.XsdDiagnostic; +import io.ballerina.xsd.core.visitor.VisitorUtils; import io.ballerina.xsd.core.visitor.XSDVisitor; import io.ballerina.xsd.core.visitor.XSDVisitorImpl; -import io.ballerina.xsd.core.component.XSDComponent; -import io.ballerina.xsd.core.visitor.VisitorUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -43,7 +43,6 @@ import static io.ballerina.xsd.core.visitor.VisitorUtils.OPEN_BRACES; import static io.ballerina.xsd.core.visitor.VisitorUtils.QUOTATION_MARK; import static io.ballerina.xsd.core.visitor.VisitorUtils.WHITESPACE; -import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.CONTENT_FIELD; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.ENUM; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.NAME; import static io.ballerina.xsd.core.visitor.XSDVisitorImpl.RECORD_WITH_OPEN_BRACE; @@ -65,6 +64,8 @@ public final class XSDToRecord { public static final String EQUAL = "="; public static final String TARGET_NAMESPACE = "targetNamespace"; + private static final String CONTENT_FIELD = "\\#content"; + public static Response convert(Document document) throws Exception { Element rootElement = document.getDocumentElement(); if (!Objects.equals(rootElement.getLocalName(), SCHEMA)) { @@ -113,21 +114,23 @@ private static void generateNodes(Element rootElement, Map nodes, Map rootElements) { - for (String element: rootElements.keySet()) { - String type = rootElements.get(element); + for (Map.Entry entry : rootElements.entrySet()) { + String element = entry.getKey(); + String type = entry.getValue(); String[] tokens = nodes.get(type).toString().split(WHITESPACE); if (!nodes.get(type).toString().contains(RECORD_WITH_OPEN_BRACE)) { - Utils.processSingleTypeElements(nodes, element, type, tokens); + Utils.processSingleTypeElements(nodes, element, type, tokens, CONTENT_FIELD); } else { - Utils.processRecordTypeElements(nodes, element, type); + Utils.processRecordTypeElements(nodes, element, type, CONTENT_FIELD); } } } private static void processNestedElements(Map nodes, Map nestedElements) { - for (String element: nestedElements.keySet()) { - String nestedElement = nestedElements.get(element); + for (Map.Entry entry : nestedElements.entrySet()) { + String element = entry.getKey(); + String nestedElement = entry.getValue(); ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(nestedElement); nodes.put(element, moduleNode); } @@ -135,9 +138,11 @@ private static void processNestedElements(Map nodes, Map nameResolvers) { - for (String element: nameResolvers.keySet()) { + for (Map.Entry entry : nameResolvers.entrySet()) { + String element = entry.getKey(); + String annotation = entry.getValue(); String node = nodes.get(element).toString(); - String newNode = String.format(XMLDATA_NAME_ANNOTATION, nameResolvers.get(element)) + node; + String newNode = String.format(XMLDATA_NAME_ANNOTATION, annotation) + node; ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(newNode); nodes.put(element, moduleNode); } @@ -145,11 +150,12 @@ private static void processNameResolvers(Map nodes, XSDVisitor xsdVisitor) { Map extensions = xsdVisitor.getExtensions(); - for (String key: extensions.keySet()) { + for (Map.Entry entry : extensions.entrySet()) { + String key = entry.getKey(); + String baseValue = entry.getValue(); if (!nodes.containsKey(key)) { continue; } - String baseValue = extensions.get(key); if (VisitorUtils.isSimpleType(baseValue)) { String fields = RECORD_WITH_OPEN_BRACE + baseValue + WHITESPACE + CONTENT_FIELD + SEMICOLON; ModuleMemberDeclarationNode parentNode = nodes.get(key); @@ -160,7 +166,7 @@ private static void processExtensions(Map n ModuleMemberDeclarationNode baseNode = nodes.get(baseValue); ModuleMemberDeclarationNode parentNode = nodes.get(key); String fields = Utils.extractSubstring(baseNode.toString(), RECORD_WITH_OPEN_BRACE, - VERTICAL_BAR + CLOSE_BRACES + SEMICOLON); + VERTICAL_BAR + CLOSE_BRACES + SEMICOLON, CONTENT_FIELD); fields = RECORD_WITH_OPEN_BRACE + fields; String extendedValue = parentNode.toString().replace(RECORD_WITH_OPEN_BRACE, fields); ModuleMemberDeclarationNode moduleNode = NodeParser.parseModuleMemberDeclaration(extendedValue); @@ -171,10 +177,11 @@ private static void processExtensions(Map n public static void processEnumerations(Map nodes, Map> enumerations) { - for (String key: enumerations.keySet()) { - ArrayList enums = enumerations.get(key); + for (Map.Entry> entry : enumerations.entrySet()) { + String key = entry.getKey(); + ArrayList enums = entry.getValue(); StringBuilder enumBuilder = new StringBuilder(); - for (String enumValue: enums) { + for (String enumValue : enums) { if (nodes.containsKey(enumValue)) { enumValue = enumValue.toLowerCase(Locale.ROOT) + WHITESPACE + EQUAL + QUOTATION_MARK + enumValue + QUOTATION_MARK; diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/DiagnosticMessage.java b/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/DiagnosticMessage.java index 8a51032..23bb7ea 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/DiagnosticMessage.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/DiagnosticMessage.java @@ -22,6 +22,11 @@ import java.util.Objects; +/** + * Represents an error diagnostic message with a unique code, description, severity, and arguments. + * + * @since 1.0.0 + */ public class DiagnosticMessage { private final String code; private final String description; diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/XsdDiagnostic.java b/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/XsdDiagnostic.java index 57d58e4..439c2f9 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/XsdDiagnostic.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/diagnostic/XsdDiagnostic.java @@ -28,6 +28,11 @@ import java.util.Collections; import java.util.List; +/** + * Represents an error diagnostic message specific to XSD processing. + * + * @since 1.0.0 + */ public class XsdDiagnostic extends Diagnostic { private final DiagnosticInfo diagnosticInfo; private final Location location; diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/VisitorUtils.java b/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/VisitorUtils.java index a620259..9ee54f7 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/VisitorUtils.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/VisitorUtils.java @@ -117,7 +117,7 @@ public static String generateDefaultValue(String type, String value) { StringBuilder builder = new StringBuilder().append(WHITESPACE).append(EQUAL).append(WHITESPACE); switch (type) { case INT -> builder.append(value); - case STRING -> builder.append(QUOTATION_MARK).append(value).append(QUOTATION_MARK); + default -> builder.append(QUOTATION_MARK).append(value).append(QUOTATION_MARK); } return builder.toString(); } @@ -147,15 +147,12 @@ public static void handleMaxOccurrences(Node node, StringBuilder builder) { } } - public static void handleFixedValues(StringBuilder builder, Node typeNode, Node fixedNode) { - builder.append(fixedNode != null - ? generateFixedValue(deriveType(typeNode), fixedNode.getNodeValue()) - : deriveType(typeNode)).append(WHITESPACE); - } - public static String deriveType(Node node) { - String derivedType = node.getNodeValue().contains(COLON) ? - node.getNodeValue().substring(node.getNodeValue().indexOf(COLON) + 1) : node.getNodeValue(); + String derivedType = (node != null) + ? node.getNodeValue().contains(COLON) + ? node.getNodeValue().substring(node.getNodeValue().indexOf(COLON) + 1) + : node.getNodeValue() + : STRING; return typeGenerator(derivedType); } diff --git a/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java b/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java index 655e354..212e1a3 100644 --- a/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java +++ b/xsd-core/src/main/java/io/ballerina/xsd/core/visitor/XSDVisitorImpl.java @@ -23,8 +23,8 @@ import io.ballerina.xsd.core.XSDFactory; import io.ballerina.xsd.core.component.ComplexType; import io.ballerina.xsd.core.component.Element; -import io.ballerina.xsd.core.component.XSDComponent; import io.ballerina.xsd.core.component.SimpleType; +import io.ballerina.xsd.core.component.XSDComponent; import io.ballerina.xsd.core.diagnostic.DiagnosticMessage; import io.ballerina.xsd.core.diagnostic.XsdDiagnostic; import org.w3c.dom.Node; @@ -59,7 +59,6 @@ public class XSDVisitorImpl implements XSDVisitor { public static final String PUBLIC = "public"; public static final String WHITESPACE = " "; - public static final String CONTENT_FIELD = "\\#content"; public static final String SEMICOLON = ";"; public static final String RECORD = "record"; public static final String TYPE = "type"; @@ -77,6 +76,7 @@ public class XSDVisitorImpl implements XSDVisitor { public static final String SEQUENCE = "sequence"; public static final String CHOICE = "choice"; public static final String ATTRIBUTE = "attribute"; + public static final String ALL = "all"; public static final String COMPLEX_CONTENT = "complexContent"; public static final String SIMPLE_CONTENT = "simpleContent"; public static final String EXTENSION = "extension"; @@ -106,6 +106,10 @@ public class XSDVisitorImpl implements XSDVisitor { public static final String XMLDATA_SEQUENCE = "@xmldata:Sequence"; public static final String SEQUENCE_NAME = "SequenceGroup"; public static final String XMLDATA_ORDER = "@xmldata:SequenceOrder"; + public static final String RESTRICTION = "restriction"; + public static final String CONTENT_FIELD = "\\#content"; + public static final String LIST = "list"; + public static final String ITEM_TYPE = "itemType"; private final ArrayList imports = new ArrayList<>(); private final Map extensions = new LinkedHashMap<>(); @@ -113,8 +117,10 @@ public class XSDVisitorImpl implements XSDVisitor { private final Map nameResolvers = new LinkedHashMap<>(); private final Map nestedElements = new LinkedHashMap<>(); private final Map> enumerationElements = new LinkedHashMap<>(); - private List diagnostics = new ArrayList<>(); - public String targetNamespace; + private final List diagnostics = new ArrayList<>(); + private String targetNamespace; + + public XSDVisitorImpl() {} @Override public String visit(Element element) throws Exception { @@ -142,18 +148,14 @@ public String visit(Element element, boolean subElement) { } catch (Exception e) { diagnostics.add(DiagnosticMessage.xsdToBallerinaError101(e, null)); } - if (nameNode == null && typeNode == null) { + if (nameNode == null || typeNode == null) { builder.append(STRING).append(WHITESPACE).append(CONTENT_FIELD); } else { handleFixedValues(node, builder, typeNode); handleMaxOccurrences(node, builder); - if (nameNode == null) { - diagnostics.add(DiagnosticMessage.xsdToBallerinaError102(NAME, null)); - } else { - builder.append(nameNode.getNodeValue()); - handleMinOccurrences(element, builder); - handleDefaultValues(node, builder, typeNode); - } + builder.append(nameNode.getNodeValue()); + handleMinOccurrences(element, builder); + handleDefaultValues(node, builder, typeNode); } builder.append(SEMICOLON); return builder.toString(); @@ -188,7 +190,8 @@ public String visit(ComplexType element, boolean isSubType) throws Exception { case SEQUENCE -> builder.append(visitSequence(childNode, false)); case CHOICE -> builder.append(visitChoice(childNode)); case ATTRIBUTE -> builder.append(visitAttribute(childNode)); - case COMPLEX_CONTENT, SIMPLE_CONTENT -> builder.append(visitComplexContent(childNode)); + case ALL -> builder.append(visitAllContent(childNode, false)); + default -> builder.append(visitComplexContent(childNode)); } } builder.append(VERTICAL_BAR).append(CLOSE_BRACES).append(SEMICOLON); @@ -260,7 +263,13 @@ public String visitAttribute(Node attribute) { Node typeNode = attribute.getAttributes().getNamedItem(TYPE); builder.append(ATTRIBUTE_ANNOTATION).append(WHITESPACE); Node fixedNode = attribute.getAttributes().getNamedItem(FIXED); - handleFixedValues(builder, typeNode, fixedNode); + if (fixedNode != null) { + builder.append(generateFixedValue(deriveType(typeNode), fixedNode.getNodeValue())).append(WHITESPACE); + } else if (attribute.hasChildNodes()) { + builder.append(visitAttributeChildNodes(attribute.getChildNodes(), builder)).append(WHITESPACE); + } else { + builder.append(deriveType(typeNode)).append(WHITESPACE); + } builder.append(nameNode.getNodeValue()); Node attributeType = attribute.getAttributes().getNamedItem(USE); if (attributeType != null && !attributeType.getNodeValue().equals(REQUIRED)) { @@ -274,6 +283,29 @@ public String visitAttribute(Node attribute) { return builder.toString(); } + public String visitAttributeChildNodes(NodeList childNodes, StringBuilder builder) { + for (int i = 0; i < childNodes.getLength(); i++) { + if (childNodes.item(i).getNodeType() != Node.ELEMENT_NODE) { + continue; + } + Node childNode = childNodes.item(i); + if (childNode.getLocalName().equals(SIMPLE_TYPE)) { + for (Node simpleTypeNode : asIterable(childNode.getChildNodes())) { + if (simpleTypeNode.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + if (simpleTypeNode.getLocalName().equals(RESTRICTION)) { + return deriveType(simpleTypeNode.getAttributes().getNamedItem(BASE)); + } else if (simpleTypeNode.getLocalName().equals(LIST)) { + return deriveType(simpleTypeNode.getAttributes().getNamedItem(ITEM_TYPE)); + } + return STRING; + } + } + } + return STRING; + } + public String visitComplexContent(Node node) throws Exception { StringBuilder builder = new StringBuilder(); NodeList childNodes = node.getChildNodes(); @@ -281,8 +313,11 @@ public String visitComplexContent(Node node) throws Exception { if (childNode.getNodeType() == Node.ELEMENT_NODE && EXTENSION.equals(childNode.getLocalName())) { String base = deriveType(childNode.getAttributes().getNamedItem(BASE)); builder.append(visitExtension(childNode)); - String parentNodeName = deriveType(node.getParentNode().getAttributes().getNamedItem(NAME)); - extensions.put(parentNodeName, base); + Node nameNode = node.getParentNode().getAttributes().getNamedItem(NAME); + if (nameNode != null) { + String parentNodeName = deriveType(node.getParentNode().getAttributes().getNamedItem(NAME)); + extensions.put(parentNodeName, base); + } } } return builder.toString(); @@ -299,7 +334,8 @@ public String visitExtension(Node node) throws Exception { case SEQUENCE -> builder.append(visitSequence(childNode, false)); case CHOICE -> builder.append(visitChoice(childNode)); case ATTRIBUTE -> builder.append(visitAttribute(childNode)); - case COMPLEX_CONTENT, SIMPLE_CONTENT -> builder.append(visitComplexContent(childNode)); + case ALL -> builder.append(visitAllContent(childNode, false)); + default -> builder.append(visitComplexContent(childNode)); } } } @@ -343,6 +379,13 @@ public String visitSequence(Node node, boolean isOptional) throws Exception { return builder.toString(); } + public String visitAllContent(Node node, boolean isOptional) throws Exception { + NodeList childNodes = node.getChildNodes(); + StringBuilder childNodeBuilder = new StringBuilder(); + processAllChildNodes(isOptional, childNodes, childNodeBuilder); + return childNodeBuilder.toString(); + } + private String handleElementsWithChildNodes(Node node, StringBuilder builder) throws Exception { Node nameNode = node.getAttributes().getNamedItem(NAME); Node typeNode = node.getAttributes().getNamedItem(TYPE); @@ -379,7 +422,7 @@ public static String generateFixedValue(String type, String value) { StringBuilder builder = new StringBuilder(); switch (type) { case INT -> builder.append(value).append(WHITESPACE); - case STRING -> builder.append(QUOTATION_MARK).append(value).append(QUOTATION_MARK).append(WHITESPACE); + default -> builder.append(QUOTATION_MARK).append(value).append(QUOTATION_MARK).append(WHITESPACE); } return builder.toString(); } @@ -418,7 +461,8 @@ private void processChildNodeByType(Node childNode, StringBuilder builder) throw case SEQUENCE -> builder.append(visitSequence(childNode, false)); case CHOICE -> builder.append(visitChoice(childNode)); case ATTRIBUTE -> builder.append(visitAttribute(childNode)); - case COMPLEX_CONTENT, SIMPLE_CONTENT -> builder.append(visitComplexContent(childNode)); + case ALL -> builder.append(visitAllContent(childNode, false)); + default -> builder.append(visitComplexContent(childNode)); } } @@ -487,6 +531,20 @@ private void processChildNodes(boolean isOptional, NodeList childNodes, } } + private void processAllChildNodes(boolean isOptional, NodeList childNodes, + StringBuilder stringBuilder) throws Exception { + for (Node childNode : asIterable(childNodes)) { + Optional component = XSDFactory.generateComponents(childNode); + if (component.isEmpty()) { + continue; + } + component.get().setSubType(true); + component.get().setOptional(isOptional); + stringBuilder.append(addNamespace(this, getTargetNamespace())); + stringBuilder.append(component.get().accept(this)); + } + } + private void processChildNode(boolean isOptional, Node childNode, StringBuilder stringBuilder) throws Exception { Optional component = XSDFactory.generateComponents(childNode); diff --git a/xsd-core/src/main/java/module-info.java b/xsd-core/src/main/java/module-info.java index 8522964..8f71eb6 100644 --- a/xsd-core/src/main/java/module-info.java +++ b/xsd-core/src/main/java/module-info.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. 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. + */ + module io.ballerina.xsd.core { requires java.xml; requires io.ballerina.parser; diff --git a/xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java b/xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java index b4b72e4..ebade10 100644 --- a/xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java +++ b/xsd-core/src/test/java/io/ballerina/xsd/core/XSDToRecordTest.java @@ -100,10 +100,10 @@ private static Document parseXSD(String xsdData) throws Exception { @org.junit.jupiter.api.Test void testXsdSchema() throws Exception { - String sourceFile = "hyatt.xsd"; - String xmlFileContent = Files.readString(RES_DIR.resolve(XML_DIR).resolve(sourceFile)); - Document document = parseXSD(xmlFileContent); - Response result = XSDToRecord.convert(document); - Assert.assertTrue(result.diagnostics().isEmpty()); + String sourceFile = "10_complex_type_with_extensions.xml"; + String xmlFileContent = Files.readString(RES_DIR.resolve(XML_DIR).resolve(sourceFile)); + Document document = parseXSD(xmlFileContent); + Response result = XSDToRecord.convert(document); + Assert.assertTrue(result.diagnostics().isEmpty()); } } diff --git a/xsd-core/src/test/resources/xml/22_attributes_with_fixed_values.xml b/xsd-core/src/test/resources/xml/22_attributes_with_fixed_values.xml index f249c27..dd5dda5 100644 --- a/xsd-core/src/test/resources/xml/22_attributes_with_fixed_values.xml +++ b/xsd-core/src/test/resources/xml/22_attributes_with_fixed_values.xml @@ -1,7 +1,7 @@ - + diff --git a/xsd-core/src/test/resources/xml/hyatt.xsd b/xsd-core/src/test/resources/xml/hyatt.xsd deleted file mode 100644 index c1045f3..0000000 --- a/xsd-core/src/test/resources/xml/hyatt.xsd +++ /dev/null @@ -1,6755 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/xsd-core/src/test/resources/xml/phone_verify.xsd b/xsd-core/src/test/resources/xml/phone_verify.xsd new file mode 100644 index 0000000..5899570 --- /dev/null +++ b/xsd-core/src/test/resources/xml/phone_verify.xsd @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsd-core/src/test/resources/xml/reservation.xsd b/xsd-core/src/test/resources/xml/reservation.xsd new file mode 100644 index 0000000..a0130ca --- /dev/null +++ b/xsd-core/src/test/resources/xml/reservation.xsd @@ -0,0 +1,3366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +