UNPKG

@krlwlfrt/xsdco

Version:
179 lines (158 loc) 5.7 kB
/* * Copyright (C) 2019, 2020 Karl-Philipp Wulfert * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <https://www.gnu.org/licenses/>. */ import {isEntity, ThingWithNameAndNamespace, Type} from '@krlwlfrt/tsg'; import {exit} from 'process'; import {asyncParseString} from './async'; import {logger} from './common'; /** * List of primitive types * @see https://www.w3.org/TR/xmlschema/#built-in-primitive-datatypes */ const primitiveTypes = [ 'anyURI', 'base64Binary', 'boolean', 'date', 'dateTime', 'decimal', 'double', 'duration', 'float', 'gDay', 'gMonth', 'gMonthDay', 'gYear', 'gYearMonth', 'hexBinary', 'integer', 'NOTATION', 'QName', 'string', 'time', ]; /** * Flatten part of an XML * @param xmlPart Part of XML to flatten * @param types Types to flatten with, extracted from XSD * @param rootNamespaces List of namespaces to consider as root(s) * @param root Root for flattening * @param root.element Root element * @param root.type Root type * @returns Flattened part of an XML */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function flattenXMLPart(xmlPart: any, types: Type[], rootNamespaces: string[], root: { element: string | number; type: ThingWithNameAndNamespace; // eslint-disable-next-line @typescript-eslint/no-explicit-any }): any { const type = types.find((typeInTypes) => { return typeInTypes.name === root.type.name && typeInTypes.namespace === root.type.namespace; }); if (typeof type === 'undefined') { logger.error(`Type '${root.type.name}' could not be found.`); exit(1); } if (isEntity(type)) { for (const key in xmlPart[root.element]) { if (!{}.hasOwnProperty.call(xmlPart[root.element], key)) { continue; } const property = type.properties.find((propertyInProperties) => { return propertyInProperties.name === key; }); if (typeof property === 'undefined') { logger.error(`Property '${key}' for '${root.type.name}' could not be found. Available properties are ${type.properties.map((propertyInProperties) => { return propertyInProperties.name; }) .join(', ')}`); exit(1); } for (let i = 0; i < xmlPart[root.element][key].length; i++) { if (primitiveTypes.indexOf(property.type.name) === -1 && typeof property.type.namespace === 'string' && rootNamespaces.indexOf(property.type.namespace) === -1) { xmlPart[root.element][key][i] = flattenXMLPart(xmlPart[root.element][key], types, rootNamespaces, { element: i, type: property.type, }); } // parse booleans if (property.type.name === 'boolean' && typeof property.type.namespace === 'string' && rootNamespaces.indexOf(property.type.namespace) >= 0) { xmlPart[root.element][key][i] = !!xmlPart[root.element][key][i]; } // parse float numbers if (['decimal', 'double', 'float'].indexOf(property.type.name) >= 0 && typeof property.type.namespace === 'string' && rootNamespaces.indexOf(property.type.namespace) >= 0) { xmlPart[root.element][key][i] = parseFloat(xmlPart[root.element][key][i]); } // parse integer numbers if (property.type.name === 'integer' && typeof property.type.namespace === 'string' && rootNamespaces.indexOf(property.type.namespace) >= 0) { xmlPart[root.element][key][i] = parseInt(xmlPart[root.element][key][i], 10); } } // replace multiple entries with if (property.multiple === false && Array.isArray(xmlPart[root.element][key])) { xmlPart[root.element][key] = xmlPart[root.element][key][0]; } } } return xmlPart[root.element]; } /** * Parse an XML * @param xml Content of XML to parse * @param types Types to guide the parsing with, extracted from XSD * @param rootNamespaces List of namespaces to consider as root(s) * @param intendedRoot Root for parsing * @param intendedRoot.element Root element * @param intendedRoot.type Root type * @returns Parsed XML */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function parseXML(xml: any, types: Type[], rootNamespaces: string[], intendedRoot?: { element: string | number; type: ThingWithNameAndNamespace; // eslint-disable-next-line @typescript-eslint/no-explicit-any }): Promise<any> { const parsedXML = await asyncParseString(xml, { emptyTag: undefined, ignoreAttrs: true, }); let rootElement: number | string = Object.keys(parsedXML)[0]; let rootType: ThingWithNameAndNamespace = { name: rootElement, namespace: '', }; if (typeof intendedRoot !== 'undefined') { rootElement = intendedRoot.element; rootType = intendedRoot.type; } else { logger.info(`Assuming root element and type to be '${rootElement}' with the same type.`); } logger.info(`Using root element '${rootElement}' with type '${rootType.name}' from namespace '${rootType.namespace}'.`); return flattenXMLPart(parsedXML, types, rootNamespaces, { element: rootElement, type: rootType, }); }