Skip to content

Commit

Permalink
parameterize for Main class
Browse files Browse the repository at this point in the history
1. Parameterize for Main class
2. Improve test case
3. Improve README.md
4. Fix bug
  • Loading branch information
beartom committed Sep 1, 2021
1 parent c5e24a3 commit ae5ec96
Show file tree
Hide file tree
Showing 54 changed files with 3,438 additions and 77 deletions.
130 changes: 128 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,128 @@
# xsd2openapischema
A tool to convert XSD to openAPI 3.0 schema
# xsd2oas3
A tool to convert XSD to openAPI 3.0 schema with a lot of limitation.

The original intention is my project need to convert validation from xsd to OAS3. So I didn't implement the xsd element those not used in my project's xsd file...

Support main xsd file import/include multiple sub xsd files. So it must distinguish the same type name in diff name space.

The Types defined in main xsd will use default prefix "**tns_**". The Types defined in sub xsd file will use the abbreviation name space in main xsd as prefix.

Code extend from [XsdParser](https://github.com/xmlet/XsdParser)

Test using [openapi4j](https://github.com/openapi4j/openapi4j)

## **Limitation**


* xs:group, xs:attributeGroup, xs:all, xs:list, xs:union, xs:complexContent are **unsupported**.


* xs:annotation, xs:appInfo, xs:documentation, xs:field, xs:key, xs:keyref, xs:notation, xs:redefine, xs:selector, xs:unique, xs:anyAttribute are **ignored**


* xs:restriction only support for base privilege type.


* xs:extension only tested inside xs:simpleContent for additional attribute. All the attributes will be added as properties of Object Type. And the original simpleType will be a 'Value' property of Object Type.


* xs:attribute only support inside xs:extension


* Others I was not realized......

## **Usage**

```java -jar ./Xsd2oas3.jar -s ./ChoiceBasicType.xsd -t ./ -config_multi_type_support=false```

## **Operations**

### **-s**
Mandatory. Specify the source xsd file path.
### **-t**
Default value: **./**

Specify the target folder path.

### **-f**
Default value: _\<The same name as input xsd file\>_

Specify the target file name without suffix.

### **-config_multi_type_support**
Default value: **DEFAULT**

Multiple type support for XSD Number type.

Number type in XSD with fractionDigits/totalDigits Restriction must be converted to a String type with pattern validation in OAS schema.

Number type in XSD with Range Restriction should be converted to Number. Pattern is hard to clarify the range.

OAS schema can also support String and Number type without validation.

###### -config_multi_type_support=DEFAULT

* Convert to String with pattern validation for Number type in XSD with fractionDigits/totalDigits Restriction.(Ignore Range Restriction)
* Convert to Number for Number type if no fractionDigits/totalDigits Restriction.

######   e.g. [MultiTypeSupport.xsd](src/test/resources/xsd/MultiTypeSupport.xsd) to [MultiTypeSupport_Default.yaml](src/test/resources/yaml/MultiTypeSupport_Default.yaml)


###### -config_multi_type_support=FORCE_TO_NUMBER
* Convert to String with pattern validation for Number type in XSD with fractionDigits/totalDigits Restriction. (Ignore Range Restriction)

######   e.g. [MultiTypeSupport.xsd](src/test/resources/xsd/MultiTypeSupport.xsd) to [MultiTypeSupport_String.yaml](src/test/resources/yaml/MultiTypeSupport_String.yaml)


###### -config_multi_type_support=FORCE_TO_NUMBER
* Convert to Number with range validation for Number type in XSD with Range Restriction. (Ignore fractionDigits/totalDigits Restriction)

######   e.g. [MultiTypeSupport.xsd](src/test/resources/xsd/MultiTypeSupport.xsd) to [MultiTypeSupport_Num.yaml](src/test/resources/yaml/MultiTypeSupport_Num.yaml)


###### -config_multi_type_support=BOTH
* Convert to both Number and String.
* Number type with range validation for Number type in XSD with Range Restriction. (Ignore fractionDigits/totalDigits Restriction)
* String type with pattern validation for Number type in XSD with fractionDigits/totalDigits Restriction. (Ignore Range Restriction)

######   e.g. [MultiTypeSupport.xsd](src/test/resources/xsd/MultiTypeSupport.xsd) to [MultiTypeSupport_Both.yaml](src/test/resources/yaml/MultiTypeSupport_Both.yaml)

### **-config_ref_prefix**
Default value: **#/components/schemas/**

Specify the prefix of a reference type.

### **-config_allow_single_object_in_array**
Default value: **false**

Allow single element in array when MinOccurs<=1 and MaxOccurs>1.

######e.g. [SingleObjectInArraySupport.xsd](src/test/resources/xsd/SingleObjectInArraySupport.xsd) to [SingleObjectInArraySupport_true.yaml](src/test/resources/yaml/SingleObjectInArraySupport_true.yaml) or [SingleObjectInArraySupport_false.yaml](src/test/resources/yaml/SingleObjectInArraySupport_false.yaml)

### **-config_choice_ref_required**
Default value: **false**

Keyword 'required' for every element in choice when the minOccurs of element gather than 0.

######e.g. [ChoiceBasicType.xsd](src/test/resources/xsd/ChoiceBasicType.xsd) to [SingleObjectInArraySupport_true.yaml](src/test/resources/yaml/SingleObjectInArraySupport_true.yaml) or [SingleObjectInArraySupport.yaml](src/test/resources/yaml/ChoiceBasicType.yaml)

### **-config_ref_anytype**
Default value: **SIMPLIFY**

###### -config_ref_anytype=SIMPLIFY
```yaml
tns_AnyType: {}
```
###### -config_ref_anytype=COMPLICATED
```yaml
tns_AnyType:
anyOf:
- type: "string"
- type: "number"
- type: "integer"
- type: "boolean"
- type: "array"
items: {}
- type: "object"
```
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<groupId>com.github.bear</groupId>
<artifactId>xsd2openapi3yaml</artifactId>
<artifactId>xsd2oas3</artifactId>
<version>1.0.0</version>
<modelVersion>4.0.0</modelVersion>

Expand Down Expand Up @@ -89,6 +89,11 @@
<artifactId>openapi-schema-validator</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>org.silentsoft</groupId>
<artifactId>arguments-parser</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>

<packaging>jar</packaging>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: org.openapi.core.Convertor

9 changes: 8 additions & 1 deletion src/main/java/org/openapi/core/ConvertConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ConvertConfig {
* 1.Convert to both String with pattern validation and Number without validation for Number type in XSD with fractionDigits/totalDigits Restriction.
* 2.Convert to both String with incomplete pattern validation and Number with validation for Number type in XSD without fractionDigits/totalDigits Restriction.
*/
public static boolean MULTI_TYPE_SUPPORT=false;
public static MULTITYPE_OPTION MULTI_TYPE_SUPPORT=MULTITYPE_OPTION.DEFAULT;

public static String REF_PREFIX="#/components/schemas/";

Expand All @@ -25,6 +25,13 @@ public class ConvertConfig {

public static ANYTYPE_OPTION REF_ANYTYPE = ANYTYPE_OPTION.SIMPLIFY;

public enum MULTITYPE_OPTION{
DEFAULT,
FORCE_TO_NUMBER,
FORCE_TO_STRING,
BOTH
}

public enum ANYTYPE_OPTION {
REFERENCE,
SIMPLIFY,
Expand Down
83 changes: 73 additions & 10 deletions src/main/java/org/openapi/core/Convertor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
import org.openapi.schemaelement.SchemaAbstractElement;
import org.openapi.schemaelement.SchemaTypeElement;
import org.openapi.visitor.DefaultSchemaVisitor;
import org.silentsoft.arguments.parser.Argument;
import org.silentsoft.arguments.parser.Arguments;
import org.silentsoft.arguments.parser.ArgumentsParser;
import org.xmlet.xsdparser.core.XsdParser;
import org.xmlet.xsdparser.xsdelements.XsdElement;
import org.xmlet.xsdparser.xsdelements.XsdSchema;

import java.io.File;
import java.io.FileOutputStream;
Expand All @@ -22,17 +26,69 @@

public class Convertor {

public static void main(String[] args) throws Exception {

String filePath;
String outputFolder = "."+File.separator;
String targetFileName = null;

Arguments parse = ArgumentsParser.parse(args).with(ArgumentsParser.ParsingOptions.REMOVE_DASH_PREFIX,
ArgumentsParser.ParsingOptions.CASE_INSENSITIVE);
Argument sourceFile = parse.get("s");

if(sourceFile == null){
throw new Exception("A source xsd file path is mandatory. Use -s specify the path.");
}

filePath = sourceFile.getValue();

Argument targetFolderPath = parse.get("t");

if(targetFolderPath != null){
outputFolder = targetFolderPath.getValue();
}

Argument fileName = parse.get("f");

if(fileName != null){
targetFileName = fileName.getValue();
}

setUpConfig(parse);
//Do the convert.
convertXSDToJsonSchema(filePath,outputFolder,targetFileName);
}

public static void setUpConfig(Arguments parse) {
//Set up convert configuration.
if(parse.containsKey("config_multi_type_support")){
ConvertConfig.MULTI_TYPE_SUPPORT = ConvertConfig.MULTITYPE_OPTION.valueOf(parse.get("config_multi_type_support").getValue());
}

if(parse.containsKey("config_ref_prefix")){
ConvertConfig.REF_PREFIX = parse.get("config_ref_prefix").getValue();
}
if(parse.containsKey("config_allow_single_object_in_array")){
ConvertConfig.ALLOW_SINGLE_OBJECT_IN_ARRAY =Boolean.parseBoolean(parse.get("config_allow_single_object_in_array").getValue());
}

if(parse.containsKey("config_choice_ref_required")){
ConvertConfig.EVERY_CHOICE_REF_REQUIRED =Boolean.parseBoolean(parse.get("config_choice_ref_required").getValue());
}

if(parse.containsKey("config_ref_anytype")){
ConvertConfig.REF_ANYTYPE = ConvertConfig.ANYTYPE_OPTION.valueOf(parse.get("config_ref_anytype").getValue());
}

public static void main(String[] args) {
String filePath = "D:\\tmp\\openapi3\\xsds\\MainCIHub.xsd";
String outputFile = "D:\\MyProjects\\Xsd2Schema\\JSONSchemaRefAny\\";
convertXSDToJsonSchema(filePath,outputFile);
}

public static Map<String, SchemaTypeElement> visitXSD(String filePath){
XsdParser parserInstance = new XsdParser(filePath,new ExtendParserConfig());
//Find the first root element.
List<XsdElement> xsdElementList = parserInstance.getResultXsdElements().collect(Collectors.toList());
List<XsdElement> xsdElementList = parserInstance.getResultXsdElements()
.filter( xsdElement -> (xsdElement.getParent() instanceof XsdSchema) &&
((XsdSchema)(xsdElement.getParent())).getFilePath().contains(filePath))
.collect(Collectors.toList());
if(xsdElementList.size()==0){
throw new UnsupportedOperationException("You must have a xs:element as root element in the schema file");
}
Expand All @@ -42,9 +98,16 @@ public static Map<String, SchemaTypeElement> visitXSD(String filePath){
return schemaVisitor.getSchemaInstances();
}

public static void convertXSDToJsonSchema(String filePath, String outputFile ) {
public static void convertXSDToJsonSchema(String filePath, String outputFolder,String targetFileName) {

Map<String, SchemaTypeElement> schemaInstances = visitXSD(filePath);

if(targetFileName==null){
targetFileName = new File(filePath).getName().replace(".xsd","");
}

String fileName=targetFileName;

if (ConvertConfig.SEPARATED_FILE) {
Map<String, ArrayList<SchemaTypeElement>> groupedElements = schemaInstances.values().stream().collect(Collectors.groupingBy(SchemaTypeElement::getPrefix, Collector.of(ArrayList<SchemaTypeElement>::new, ArrayList::add, (left, right) -> {
left.addAll(right);
Expand All @@ -53,14 +116,14 @@ public static void convertXSDToJsonSchema(String filePath, String outputFile ) {

groupedElements.forEach((prefix, list) -> {
String jsonSchema = outputJsonStr(list);
if(prefix==""){
prefix="root";
if(prefix.equals("")){
prefix=fileName;
}
writeToFile(outputFile, prefix, jsonSchema);
writeToFile(outputFolder, prefix, jsonSchema);
});
}else{
String jsonSchema = outputJsonStr(schemaInstances.values());
writeToFile(outputFile, new File(filePath).getName().replace(".xsd",""), jsonSchema);
writeToFile(outputFolder, fileName, jsonSchema);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public abstract class SchemaAbstractElement {

private int minOccurs=0;

private int maxOccors=1;
private int MaxOccurs=1;

private boolean unbounds = false;

Expand All @@ -37,12 +37,12 @@ public void setMinOccurs(int minOccurs) {
this.minOccurs = minOccurs;
}

public int getMaxOccors() {
return maxOccors;
public int getMaxOccurs() {
return MaxOccurs;
}

public void setMaxOccors(int maxOccors) {
this.maxOccors = maxOccors;
public void setMaxOccurs(int MaxOccurs) {
this.MaxOccurs = MaxOccurs;
}

public Boolean getUnbounds() {
Expand All @@ -62,7 +62,7 @@ public boolean isRequired(){
}

public boolean isArray(){
return this.maxOccors>1 || this.unbounds;
return this.MaxOccurs>1 || this.unbounds;
}

public void addSubElement(SchemaAbstractElement element){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public StringBuilder convertSchema() {
.append(" 'properties': {\n");
sb.append(retract(element.convertSchema(), 4));
sb.append("\n }");
if(EVERY_CHOICE_REF_REQUIRED){
if(EVERY_CHOICE_REF_REQUIRED && element.getMinOccurs()>0){
sb.append(",\n 'required': ['").append(((SchemaNamedRefElement) element).getPropertyName()).append("']");
}
sb.append("\n },\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@ public StringBuilder convertSchema() {
sb.append("'").append(getPropertyName()).append("': {\n");
if(this.isArray()){
if(ConvertConfig.ALLOW_SINGLE_OBJECT_IN_ARRAY && this.getMinOccurs()<=1){
sb.append(" 'oneOf': [\n {\n").append(retract(ref.getRefName(this.getPrefix()),3)).append("\n },\n {\n");
sb.append(" 'oneOf': [\n {\n").append(retract(getRefTypeSchema(),3)).append("\n },\n {\n");
}
StringBuilder arrayType = new StringBuilder();
arrayType.append("'type': 'array',\n");
if(this.getMinOccurs()>0){
arrayType.append("'minItems': ").append(this.getMinOccurs()).append(",\n");
}
if(!this.getUnbounds()){
arrayType.append("'maxItems': ").append(this.getMaxOccors()).append(",\n");
arrayType.append("'maxItems': ").append(this.getMaxOccurs()).append(",\n");
}
arrayType.append("'items': {\n")
.append(getRefTypeSchema()).append("\n }\n");
.append(getRefTypeSchema()).append("\n}");

if(ConvertConfig.ALLOW_SINGLE_OBJECT_IN_ARRAY && this.getMinOccurs()<=1) {
sb.append(retract(arrayType, 3)).append(" }\n ]\n");
sb.append(retract(arrayType, 3)).append("\n }\n ]\n");
}else{
sb.append(retract(arrayType, 1));
sb.append(retract(arrayType, 1)).append("\n");
}
}else {
sb.append(getRefTypeSchema()).append("\n");
Expand Down
Loading

0 comments on commit ae5ec96

Please sign in to comment.