amis-rpc-design/libs/amis/packages/office-viewer/tools/xsd2ts.ts
2023-10-07 19:42:30 +08:00

327 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 基于 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'));