amis-rpc-design/libs/amis/packages/office-viewer/tools/xsd2ts.ts

327 lines
10 KiB
TypeScript
Raw Normal View History

2023-10-07 19:42:30 +08:00
/**
* xsd typescript
*/
import * as fs from 'fs';
import {XMLParser} from 'fast-xml-parser';
const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
trimValues: false,
alwaysCreateTextNode: true
});
function parseXML(xml: string) {
return parser.parse(xml);
}
let outputFile: string[] = ['/** generated by tools/xsd2ts.ts, do not edit */'];
// 所有定义过的类型
const definedTypes: Record<string, boolean> = {};
// 所有使用的类型
const allusedTypes: Record<string, boolean> = {};
outputFile.push(
...convertSchema(
'./OfficeOpenXML-XMLSchema-Strict/shared-commonSimpleTypes.xsd'
)
);
outputFile.push(...convertSchema('./OfficeOpenXML-XMLSchema-Strict/wml.xsd'));
outputFile.push(
...convertSchema('./OfficeOpenXML-XMLSchema-Strict/dml-main.xsd')
);
outputFile.push(
...convertSchema(
'./OfficeOpenXML-XMLSchema-Strict/dml-wordprocessingDrawing.xsd'
)
);
function generateField(attributes: any[]) {
const result: string[] = [];
if (!Array.isArray(attributes)) {
attributes = [attributes];
}
for (const attribute of attributes) {
const attributeName = attribute['@_name'];
if (attributeName) {
let attributeType = attribute['@_type']
.replace(/s:/g, '')
.replace(/a:/g, '');
allusedTypes[attributeType] = true;
if (attributeType.startsWith('xsd:')) {
attributeType = toJavaScriptType(attributeType);
}
const use = attribute['@_use'];
if (use === 'required') {
result.push(` ${attributeName}: ${attributeType};`);
} else {
result.push(` ${attributeName}?: ${attributeType};`);
}
}
let ref = attribute['@_ref'];
if (ref) {
ref = ref
.replace(/r:/g, 'r')
.replace(/m:/g, '')
.replace(/sl:/g, '')
.replace(/a:/g, '');
const use = attribute['@_use'];
if (ref.indexOf(':') !== -1) {
continue;
}
if (use === 'required') {
result.push(` ${ref}: string;`);
} else {
result.push(` ${ref}?: string;`);
}
}
}
return result;
}
function toJavaScriptType(type: string) {
if (
type === 'xsd:string' ||
type === 'xsd:token' ||
type === 'xsd:hexBinary' ||
type === 'xsd:dateTime' ||
type === 'xsd:NCName' ||
type === 'xsd:base64Binary'
) {
return 'string';
} else if (
type === 'xsd:integer' ||
type === 'xsd:int' ||
type === 'xsd:unsignedInt' ||
type === 'xsd:unsignedLong' ||
type === 'xsd:unsignedShort' ||
type === 'xsd:long' ||
type === 'xsd:byte' ||
type === 'xsd:unsignedByte'
) {
return 'number';
} else if (type === 'xsd:boolean') {
return 'boolean';
}
throw new Error("can't replace type: " + type);
}
// 解析 schema 输出 ts 定义
function convertSchema(fileName: string) {
const wml = fs.readFileSync(fileName, 'utf8');
const schema = parseXML(wml)['xsd:schema'];
const result: string[] = [];
for (const key in schema) {
if (key.startsWith('@_')) {
continue;
}
const value = schema[key];
if (Array.isArray(value)) {
// 需要执行两边,第一遍是检查基础类型的基础类型
for (const item of value) {
const name = item['@_name'];
// 目前仅处理简单类型
if (name && !name.startsWith('ST_')) {
continue;
}
definedTypes[name] = true;
if (key === 'xsd:complexType') {
definedTypes[name] = true;
let attributesAndElements = item['xsd:attribute'];
if (attributesAndElements && !Array.isArray(attributesAndElements)) {
attributesAndElements = [attributesAndElements];
}
if (item['xsd:sequence']?.['xsd:element']) {
const elements = item['xsd:sequence']?.['xsd:element'];
if (elements) {
if (!attributesAndElements) {
attributesAndElements = [];
}
attributesAndElements = attributesAndElements.concat(elements);
}
}
if (!attributesAndElements) {
const complexContent = item['xsd:complexContent'];
if (complexContent) {
const extension = complexContent['xsd:extension'];
if (extension) {
const extensionAttributes = extension['xsd:attribute'];
const base = extension['@_base'].replace(/s:/g, '');
if (extensionAttributes) {
result.push(`export type ${name} = ${base} & {`);
result.push(...generateField(extensionAttributes));
result.push('}\n');
} else {
const sequence = extension['xsd:sequence'];
if (sequence) {
let group = sequence['xsd:group'];
let groupStr = '';
if (group) {
if (!Array.isArray(group)) {
group = [group];
}
for (const g of group) {
const ref = g['@_ref'].replace(/s:/g, '');
groupStr += ref + ' & ';
}
}
if (attributesAndElements) {
result.push(
`export type ${name} = ${base} & ${groupStr} {`
);
result.push(...generateField(attributesAndElements));
result.push('}\n');
} else {
if (groupStr) {
result.push(
`export type ${name} = ${base} & ${groupStr.slice(
0,
-3
)};`
);
} else {
if (name !== base) {
result.push(`export type ${name} = ${base};`);
}
}
}
} else {
if (name !== base) {
result.push(`export type ${name} = ${base};`);
}
}
}
} else {
result.push(`export type ${name} = any;\n`);
}
} else {
result.push(`export type ${name} = any;\n`);
}
} else {
result.push(`export type ${name} = {`);
result.push(...generateField(attributesAndElements));
result.push('}\n');
}
} else if (key === 'xsd:simpleType') {
// 简单类型有几种,
const restriction = item['xsd:restriction'];
if (restriction) {
const base = restriction['@_base'];
if (base.startsWith('xsd:')) {
allusedTypes[base] = true;
const javascriptType = toJavaScriptType(base);
if (javascriptType === 'string') {
let enumeration = restriction['xsd:enumeration'];
if (enumeration) {
if (!Array.isArray(enumeration)) {
enumeration = [enumeration];
}
// 如果是数字开头大概不会是 enum
if (enumeration[0]['@_value'].match(/^[0-9]/)) {
result.push(`export type ${name} = string;`);
} else {
const enumNames = [];
for (const enumItem of enumeration) {
const enumName = enumItem['@_value'];
enumNames.push(enumName);
}
// 这里用 union 代替 enum可以减少生成体积
result.push(
`export type ${name} = '${enumNames.join("' | '")}'`
);
}
} else {
result.push(`export type ${name} = string;`);
}
} else {
result.push(`export type ${name} = ${javascriptType};`);
}
} else if (base.startsWith('s:') || base.startsWith('ST_')) {
const baseType = base.replace('s:', '');
allusedTypes[baseType] = true;
if (name !== baseType) {
result.push(`export type ${name} = ${baseType};`);
}
} else {
throw new Error('Unknown base type: ' + base);
}
}
const union = item['xsd:union'];
if (union) {
const memberTypes = union['@_memberTypes'];
if (memberTypes) {
definedTypes[name] = true;
const members = memberTypes
.replace(/s:/g, '')
.split(' ')
.map((item: string) => {
if (item.startsWith('xsd:')) {
return toJavaScriptType(item);
} else {
return item;
}
});
for (const member of members) {
allusedTypes[member] = true;
}
const membersStr = members.join(' | ');
if (name !== membersStr) {
result.push(`export type ${name} = ${membersStr};\n`);
}
}
}
} else if (key === 'xsd:group') {
const choice = item['xsd:choice'];
if (choice) {
let elements = choice['xsd:element'];
if (elements) {
if (!Array.isArray(elements)) {
elements = [elements];
}
result.push(`export type ${name} = {`);
for (const element of elements) {
const elementName = element['@_name'];
const elementType = element['@_type'];
if (elementType) {
allusedTypes[elementType] = true;
result.push(` ${elementName}?: ${elementType};`);
} else {
console.warn("can't find type: " + name);
}
}
result.push('}\n');
}
}
}
}
}
}
return result;
}
for (const key in allusedTypes) {
if (key.startsWith('xsd:')) {
continue;
}
if (!definedTypes[key]) {
console.log('Not defined: ' + key);
}
}
fs.writeFileSync('../src/openxml/Types.ts', outputFile.join('\n'));