UNPKG

@selenite/commons

Version:

This typescript package provides a set of frequently used utilities, types and svelte actions for building projects with Typescript and Svelte.

273 lines (272 loc) 8.43 kB
/** * Functions and types to parse XML schema definitions (ie. .xsd files). * @module */ import init, { parse_xsd } from '@selenite/commons-rs'; import { isBrowser } from './html.svelte'; import { parseXMLArray } from './xml'; export class ChildProps { type; minOccurs; maxOccurs; constructor(params) { this.type = params.type; this.minOccurs = params.minOccurs; this.maxOccurs = params.maxOccurs; } get unique() { return this.maxOccurs !== undefined && this.maxOccurs <= 1; } get required() { return this.minOccurs !== undefined && this.minOccurs >= 1; } static fromObject(obj) { return new ChildProps(obj); } toJSON() { return { type: this.type, minOccurs: this.minOccurs, maxOccurs: this.maxOccurs }; } } /** A complex type in an XML schema definition. */ export class ComplexType { /** Name of the complex type. */ name; /** Documentation of the complex type. */ doc; /** Attributes of the complex type. */ get attrs() { return [...this.attributes.values()]; } attributes; children; constructor(params) { this.name = params.name; this.doc = params.doc; this.attributes = new Map(params.attributes?.map((a) => [a.name, a])); this.children = params.children ?? []; } get childTypes() { return this.children.map((c) => c.type); } get optionalChildren() { return this.children.filter((c) => !c.required); } get optionalChildTypes() { return this.optionalChildren.map((c) => c.type); } get requiredChildren() { return this.children.filter((c) => c.required); } get uniqueChildren() { return this.children.filter((c) => c.unique); } toJSON() { return { name: this.name, doc: this.doc, attributes: Array.from(this.attrs.values()), children: this.children.map((c) => c.toJSON()) }; } static fromObject({ name, attributes, children, doc }) { const complex = new ComplexType({ name, doc, attributes, children: children.map((c) => ChildProps.fromObject(c)) }); return complex; } } /** * Type of an XML schema definition. * * It defines the types allowed in an xml file, usually derived from an xsd file. */ export class XmlSchema { static fromJSON(data) { const schema = new XmlSchema(); schema.addSimpleType(...data.simpleTypes); schema.addComplexType(...data.complexTypes.map((c) => ComplexType.fromObject(c))); return schema; } toJSON() { return { simpleTypes: Array.from(this.simpleTypes.values()), complexTypes: Array.from(this.complexTypes.values()).map((c) => c.toJSON()) }; } /** Simple types of the xml schema. */ simpleTypes = new Map(); /** Complex types of the xml schema. */ complexTypes = new Map(); /** Map which associates the names of simple XML types to their definition. */ get simpleTypeMap() { return this.simpleTypes; } /** Map which associates the names of complex XML types to their definitions. */ get typeMap() { return this.complexTypes; } addComplexType(...complexs) { for (const c of complexs) { this.complexTypes.set(c.name, c); } } addSimpleType(...simples) { for (const s of simples) { this.simpleTypes.set(s.name, s); } } /** Map which associates the names of complex XML types to their parents' names. */ get parentsMap() { const res = new Map(); for (const { name } of this.complexTypes.values()) { res.set(name, []); } for (const { name: parentName, children } of this.complexTypes.values()) { if (!children) continue; for (const c of children) { res.get(c.type)?.push(parentName); } } return res; } /** Roots of the XML schema. */ get roots() { const res = []; for (const [name, children] of this.parentsMap.entries()) { if (children.length === 0) res.push(name); } return res; } /** * Tree representation of the XML schema. * * It is a recursive structure that represents the hierarchy of the types in the schema. */ get tree() { const res = {}; const rec = (current, types, already_visited) => { // const isOneTypeAlreadyVisited = types.some((t) => already_visited.includes(t)); for (const type of types) { if (already_visited.includes(type)) { current[type] = 'recursive'; continue; } current[type] = {}; const children = this.typeMap.get(type)?.children.map((c) => c.type); if (!children) continue; rec(current[type], children, [...already_visited, type]); } }; rec(res, this.roots, []); return res; } /** * Map which associates the names of complex XML types to their possible paths in an XML file. * * The paths are represented as an array of type names. * If a type is recursive, the value is 'infinite'. */ get typePaths() { const res = new Map(); const rec = (tree, path) => { for (const [name, children] of Object.entries(tree)) { if (children === 'recursive') { res.set(name, 'infinite'); continue; } const paths = res.get(name); if (paths === 'infinite') continue; res.set(name, [...(paths || []), path]); rec(children, [...path, name]); } }; rec(this.tree, []); return res; } } /** * Parses an XML schema from an XSD string. * @param xsd - xsd string * @returns XMLSchema, or undefined if the string could not be parsed */ export async function parseXsd(xsd) { if (!isBrowser()) return; await init(import.meta.env.BASE_URL && import.meta.env.BASE_URL !== '/' ? import.meta.env.BASE_URL + '/assets/commons_rs_bg.wasm' : undefined); const schema = parse_xsd(xsd); if (!schema) return; const res = new XmlSchema(); for (const { name, doc, options, pattern } of schema.simple_types) { res.simpleTypes.set(name, { name, doc, pattern, options }); } for (const { name, children, fields, doc } of schema.complex_types) { const attrs = fields.map(({ name, doc, type_name: type, required, default: default_ }) => { let parsedDefault = default_; if (default_) { const arrayAttempt = parseXMLArray(default_); if (arrayAttempt) { parsedDefault = arrayAttempt; } else { try { parsedDefault = JSON.parse(default_); } catch { /* keep default value as is */ } } } return { name, type, required, default: parsedDefault, doc }; }); res.complexTypes.set(name, new ComplexType({ name, attributes: attrs, doc, children: children?.map((c) => new ChildProps({ type: c.type_name, maxOccurs: c.max_occurs, minOccurs: c.min_occurs })) ?? [] })); } schema.free(); return res; } /** * Parses an XML schema from the url of an XSD file. * @param url - url of the xsd file * @returns XMLSchema, or undefined if the file could not be fetched or parsed */ export async function parseXsdFromUrl(url) { if (!isBrowser()) return; const res = await fetch(url); const xsd = await res.text(); return parseXsd(xsd); }