@rxap/xml-parser
Version:
Provides a set of decorators and services for parsing and serializing XML documents into TypeScript classes. It simplifies the process of mapping XML elements and attributes to class properties, handling data validation, and serializing objects back into
177 lines • 8.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.XmlParserService = void 0;
const reflect_metadata_1 = require("@rxap/reflect-metadata");
const utilities_1 = require("@rxap/utilities");
const metadata_keys_1 = require("./decorators/metadata-keys");
const default_to_json_1 = require("./default-to-json");
const element_1 = require("./element");
const error_1 = require("./error");
class XmlParserService {
get rootElement() {
return (0, element_1.normalizeNodeName)(this._rootElement, this.elementOptions);
}
constructor(DOMParser, elementOptions = {}) {
this.DOMParser = DOMParser;
this.elementOptions = elementOptions;
this.parsers = new Map();
this._rootElement = 'definition';
this._rootParser = null;
this.parse = this.parse.bind(this);
}
register(...elementParsers) {
for (const elementParser of elementParsers) {
if (!elementParser) {
throw new Error('Element Parser is undefined or null');
}
const elementName = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.NAME, elementParser);
const parsers = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.PARSER, elementParser);
if (!elementName) {
throw new Error('Element name is not defined. Ensure that the @ElementParser is used');
}
if (!parsers) {
throw new Error('Element parsers are not defined. Ensure that the @ElementParser is used');
}
this.parsers.set(elementName, {
elementParser,
parsers,
});
}
}
setRootElement(nameOrElementParser) {
if (typeof nameOrElementParser === 'string') {
this._rootElement = nameOrElementParser;
}
else {
const elementName = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.NAME, nameOrElementParser);
if (!elementName) {
throw new Error('Could not set the root Element. Element name is not defined. Ensure that the @ElementDef is used');
}
if (!this.parsers.has(elementName)) {
this.register(nameOrElementParser);
}
this._rootParser = nameOrElementParser;
this._rootElement = elementName;
}
}
parseAttributes(parsedElement, element) {
const attributes = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.ATTRIBUTE, Object.getPrototypeOf(parsedElement)) || [];
for (const attribute of attributes) {
const textContent = element.getChildTextContent(attribute.elementName);
if ((0, utilities_1.hasIndexSignature)(parsedElement)) {
parsedElement[attribute.propertyKey] = textContent;
}
else {
throw new Error('Parsed element has not in index signature');
}
}
}
/**
*
*
* @param element
* @param elementNameOrConstructor
* @param args Constructor parameters for the ParsedElement instance
* @param parent The parent Parsed Element
*/
parse(element, elementNameOrConstructor = element.name, parent, args = []) {
var _a, _b, _c, _d;
const { elementName, parser, } = this.determineElementNameAndParser(elementNameOrConstructor);
// create the ParsedElement instance of the current element
const instance = new parser.elementParser(...args);
Reflect.set(instance, '__tag', element.name);
Reflect.set(instance, '__parent', parent);
const xmlnsMap = new Map();
for (const xmlns of element.attributeNames.filter(name => name.startsWith('xmlns'))) {
const value = element.get(xmlns);
xmlnsMap.set(xmlns.includes(':') ? xmlns.split(':').pop() : '', value);
}
Reflect.set(instance, '__xmlns', xmlnsMap);
if (typeof instance.toJSON !== 'function') {
Reflect.set(instance, 'toJSON', default_to_json_1.defaultToJson.bind(instance));
}
this.parseAttributes(instance, element);
(_a = instance.preParse) === null || _a === void 0 ? void 0 : _a.call(instance);
for (const p of parser.parsers) {
p(this, element, instance);
}
(_b = instance.postParse) === null || _b === void 0 ? void 0 : _b.call(instance);
const requiredProperties = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.REQUIRED_ELEMENT_PROPERTIES, instance);
if (requiredProperties && (0, utilities_1.hasIndexSignature)(instance)) {
for (const [propertyKey, message] of Object.entries(requiredProperties)) {
if (instance[propertyKey] === undefined) {
console.log('prop', instance[propertyKey]);
throw new error_1.RxapXmlParserError(`[${elementName}] ${message}`, '', instance.constructor.name);
}
}
}
(_c = instance.preValidate) === null || _c === void 0 ? void 0 : _c.call(instance);
if (instance.validate && !instance.validate()) {
throw new error_1.RxapXmlParserError(`Could not parse element '${elementName}'. Parsed element is not valid`, '', instance.constructor.name);
}
(_d = instance.postValidate) === null || _d === void 0 ? void 0 : _d.call(instance);
return instance;
}
determineElementNameAndParser(elementNameOrConstructor) {
var _a;
let elementName;
let parser;
if (typeof elementNameOrConstructor === 'string') {
elementName = elementNameOrConstructor;
if (!this.parsers.has(elementName)) {
throw new Error(`Parser for element '${elementName}' is not registered`);
}
parser = this.parsers.get(elementName);
}
else {
elementName = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.NAME, elementNameOrConstructor);
parser = {
parsers: (_a = (0, reflect_metadata_1.getMetadata)(metadata_keys_1.ElementParserMetaData.PARSER, elementNameOrConstructor)) !== null && _a !== void 0 ? _a : [],
elementParser: elementNameOrConstructor,
};
}
return {
elementName,
parser,
};
}
/**
*
* @param xml
* @param args a list of args passed to the element constructor
*/
parseFromXml(xml, ...args) {
var _a;
let xmlDoc;
try {
xmlDoc = this.createDOMParser().parseFromString(xml, 'application/xml');
}
catch (e) {
throw new Error('Could not parse xml string');
}
if (!xmlDoc.childNodes.length) {
throw new Error('The parsed xml has not any element');
}
const root = this.determineRootElement(xmlDoc);
return this.parse(root, (_a = this._rootParser) !== null && _a !== void 0 ? _a : root.name, null, args);
}
createDOMParser() {
return new this.DOMParser();
}
determineRootElement(xmlDoc) {
const rootChildren = Array.from(xmlDoc.childNodes);
const rootNode = rootChildren
.filter(node => node.nodeType === 1)
.find(node => (0, element_1.normalizeNodeName)(node.nodeName, this.elementOptions) === this.rootElement);
if (!rootNode) {
throw new Error(`Could not find <${this.rootElement}> element!`);
}
const root = new element_1.RxapElement(rootNode, this.DOMParser, this.elementOptions);
if (!root.hasName(this.rootElement)) {
throw new Error(`The root node must be an <${this.rootElement}> element, but the root node is a <${root.name}> element!`);
}
return root;
}
}
exports.XmlParserService = XmlParserService;
//# sourceMappingURL=xml-parser.service.js.map