UNPKG

@nodecfdi/cfdi-core

Version:

Librería que contiene las herramientas base para trabajar con CFDI's

620 lines (609 loc) 15.9 kB
// index.ts import { Attr, CDATASection, Document, DOMImplementation as DOMImplementation2, DOMParser as DOMParser2, Element, MIME_TYPE as MIME_TYPE2, NAMESPACE as NAMESPACE2, Node, onErrorStopParsing as onErrorStopParsing2, Text, XMLSerializer as XMLSerializer2 } from "@xmldom/xmldom"; // src/dom.ts import { DOMImplementation, DOMParser, onErrorStopParsing, XMLSerializer } from "@xmldom/xmldom"; var getParser = () => { return new DOMParser({ onError: onErrorStopParsing }); }; var getSerializer = () => { return new XMLSerializer(); }; var getDomImplementation = () => { return new DOMImplementation(); }; var isNode = (nodo) => { return typeof nodo === "object" && nodo !== null && "nodeType" in nodo && "nodeName" in nodo && typeof nodo.nodeType === "number" && nodo.nodeType >= 1 && nodo.nodeType <= 11 && typeof nodo.nodeName === "string"; }; var isElement = (nodo) => { return isNode(nodo) && nodo.nodeType === 1; }; var isAttribute = (nodo) => { return isNode(nodo) && nodo.nodeType === 2; }; var isText = (nodo) => { return isNode(nodo) && nodo.nodeType === 3; }; var isDocument = (nodo) => { return isNode(nodo) && nodo.nodeType === 9; }; var isCDataSection = (nodo) => { return isNode(nodo) && nodo.nodeType === 4; }; // src/utils/number.ts var toFloat = (value) => { const resultFloat = Number.parseFloat(value); return Number.isNaN(resultFloat) ? 0 : resultFloat; }; var roundNumber = (num, precision) => { const numSign = num >= 0 ? 1 : -1; return toFloat( (Math.round(num * Math.pow(10, precision) + numSign * 1e-4) / Math.pow(10, precision)).toFixed(precision) ); }; var formatNumber = (num, precision) => { return roundNumber(num, precision).toFixed(precision); }; // src/utils/xml.ts import { MIME_TYPE } from "@xmldom/xmldom"; var documentElement = (document) => { const rootElement = document.documentElement; if (!isElement(rootElement)) { throw new TypeError("Document does not have root element"); } return rootElement; }; var ownerDocument = (node) => { if (!node.ownerDocument) { if (isDocument(node)) { return node; } throw new TypeError("node.ownerDocument is null but node is not a Document"); } return node.ownerDocument; }; var newDocument = (document) => { const temporalDocument = document ?? getDomImplementation().createDocument(null, "", null); return temporalDocument; }; var newDocumentContent = (content) => { if (content === "") { throw new TypeError("Received xml string argument is empty"); } try { const documentParse = getParser().parseFromString(content, MIME_TYPE.XML_TEXT); return newDocument(documentParse); } catch (error) { throw new Error(`Cannot create a Document from xml string, errors: ${JSON.stringify(error)}`); } }; var isValidXmlName = (name) => { if (name === "") { return false; } return /^[\p{L}_:][\p{L}\d_:.-]*$/u.test(name); }; var createDomElement = (makeElement, errorMessage, content) => { let element; let previousException; try { element = makeElement(); } catch (error) { previousException = error; } if (!element || !isElement(element)) { throw new TypeError(`${errorMessage} on ${previousException?.message}`); } if (content !== "") { element.appendChild(ownerDocument(element).createTextNode(content)); } return element; }; var createElement = (document, name, content = "") => { return createDomElement( () => { if (!name) { throw new TypeError("Empty name"); } return document.createElement(name); }, `Cannot create element with name ${name}`, content ); }; // src/xml_nodes/xml_attributes.ts var XmlAttributes = class { _attributes = /* @__PURE__ */ new Map(); constructor(attributes = {}) { this.import(attributes); } get size() { return this._attributes.size; } get length() { return this._attributes.size; } get(name) { return this._attributes.get(name) ?? ""; } set(name, value = null) { if (value === null) { this.delete(name); return this; } if (!isValidXmlName(name)) { throw new SyntaxError(`Cannot set attribute with an invalid xml name: ${name}`); } this._attributes.set(name, value.toString()); return this; } delete(name) { return this._attributes.delete(name); } clear() { this._attributes.clear(); return this; } has(name) { return this._attributes.has(name); } import(attributes) { for (const [key, value] of Object.entries(attributes)) { const fixedValue = this.castValueToString(key, value); this.set(key, fixedValue); } return this; } export() { return Object.fromEntries(this._attributes.entries()); } exportMap() { return this._attributes; } forEach(callbackFn, thisArgument) { this._attributes.forEach(callbackFn, thisArgument); } entries() { return this._attributes.entries(); } keys() { return this._attributes.keys(); } values() { return this._attributes.values(); } [Symbol.iterator]() { return this._attributes[Symbol.iterator](); } [Symbol.toStringTag] = "XmlAttributes"; castValueToString(key, value) { if (value === null || value === void 0) { return null; } if (/boolean|number|string/u.test(typeof value)) { return `${value}`; } if (typeof value === "object" && !Array.isArray(value)) { return value.toString(); } throw new SyntaxError(`Cannot convert value of attribute ${key} to string`); } }; // src/xml_nodes/xml_nodes_sorter.ts var XmlNodesSorter = class { /** Record of key (string) value (number|int) representing the naming order. */ _order = {}; constructor(order = []) { this.setOrder(order); } setOrder(names) { const order = Object.fromEntries(this.parseNames(names).map((entry, index) => [entry, index])); if (JSON.stringify(this._order) === JSON.stringify(order)) { return false; } this._order = order; this._length = Object.keys(order).length; return true; } parseNames(names) { return [...new Set(names.filter((name) => this.isValidName(name)))]; } getOrder() { return Array.from(Object.entries(this._order), (entry) => entry[0]); } sort(nodes) { let sortedNodes = nodes; if (this._length > 0) { sortedNodes = this.stableArraySort( sortedNodes, (a, b) => this.compareNodesByName(a, b) ); } return sortedNodes; } compareNodesByName(a, b) { const aNumber = this.valueByName(a.name()); const bNumber = this.valueByName(b.name()); return Math.sign(aNumber - bNumber); } valueByName(name) { return this._order[name] ?? this._length; } isValidName(name) { return typeof name === "string" && Boolean(name) && name !== "0"; } /** * This function is a replacement for sort that try to sort * but if items are equal then uses the relative position as second argument. */ stableArraySort(input, callable) { let list = input.map((value, index) => ({ index, item: value })); list = list.sort((first, second) => { let value = callable(first.item, second.item); if (value === 0) { value = Math.sign(first.index - second.index); } return value; }); return list.map((node) => node.item); } }; // src/xml_nodes/xml_nodes.ts var XmlNodes = class _XmlNodes extends Array { _sorter; constructor(nodes = []) { super(); this._sorter = new XmlNodesSorter(); this.importFromArray(nodes); } static get [Symbol.species]() { return Array; } add(...nodes) { let somethingChange = false; for (const node of nodes) { if (!this.has(node)) { this.push(node); somethingChange = true; } } if (somethingChange) { this.order(); } return this; } delete(node) { const index = this.indexOf(node); if (index !== -1) { this.splice(index, 1); } return this; } clear() { this.splice(0); return this; } has(node) { return this.includes(node); } first() { return this.at(0); } get(position) { const node = this.at(position); if (!node) { throw new RangeError(`The index ${position} does not exists`); } return node; } firstNodeWithName(nodeName) { return this.find((node) => node.name() === nodeName); } getNodesByName(nodeName) { const nodes = new _XmlNodes(); for (const node of this) { if (node.name() === nodeName) { nodes.add(node); } } return nodes; } order() { this.splice(0, this.length, ...this._sorter.sort(this)); } /** * It takes only the unique string names and sort using the order of appearance. */ setOrder(names) { if (this._sorter.setOrder(names)) { this.order(); } } getOrder() { return this._sorter.getOrder(); } importFromArray(nodes) { for (const [index, node] of nodes.entries()) { if (typeof node.searchNodes !== "function" || typeof node.children !== "function" || typeof node.name !== "function") { throw new SyntaxError(`The element index ${index} is not a XmlNodeInterface object`); } } this.add(...nodes); return this; } }; // src/xml_nodes/xml_node.ts var XmlNode = class { _name; _attributes; _children; _value; _cdata; constructor(name, attributes = {}, children = [], value = "", cdata = "") { if (!isValidXmlName(name)) { throw new SyntaxError(`Cannot create a node with an invalid xml name: ${name}`); } this._name = name; this._attributes = new XmlAttributes(attributes); this._children = new XmlNodes(children); this._value = value; this._cdata = cdata; } get length() { return this._children.length; } name() { return this._name; } children() { return this._children; } addChild(node) { this._children.add(node); return node; } attributes() { return this._attributes; } hasAttribute(name) { return this._attributes.has(name); } setAttribute(name, value = null) { this._attributes.set(name, value); } getAttribute(name) { return this._attributes.get(name); } addAttributes(attributes) { this._attributes.import(attributes); } clear() { this._attributes.clear(); this._children.clear(); } searchAttribute(...searchPath) { const attribute = searchPath.pop(); const node = this.searchNode(...searchPath); if (!attribute || !node) { return ""; } return node.getAttribute(attribute); } searchNodes(...searchPath) { const nodes = new XmlNodes(); const nodeName = searchPath.pop(); const parent = this.searchNode(...searchPath); if (parent) { for (const child of parent.children()) { if (child.name() === nodeName) { nodes.add(child); } } } return nodes; } searchNode(...searchPath) { let self = this; for (const searchName of searchPath) { self = self.children().firstNodeWithName(searchName); if (!self) { break; } } return self; } value() { return this._value; } setValue(value) { this._value = value; } cdata() { return this._cdata; } setCData(cdata) { this._cdata = cdata; } [Symbol.iterator]() { return this._children[Symbol.iterator](); } }; // src/xml_nodes/xml_node_exporter.ts var XmlNodeExporter = class { constructor(node) { this.node = node; } export() { const document = newDocument(); const rootElement = this.exportRecursive(document, this.node); document.appendChild(rootElement); return rootElement; } exportRecursive(document, node) { const element = document.createElement(node.name()); for (const [key, value] of node.attributes().entries()) { element.setAttribute(key, value); } for (const child of node.children()) { const childElement = this.exportRecursive(document, child); element.appendChild(childElement); } if (node.value() !== "") { element.appendChild(document.createTextNode(node.value())); } if (node.cdata() !== "") { element.appendChild(document.createCDATASection(node.cdata())); } return element; } }; // src/xml_nodes/xml_node_importer.ts import { NAMESPACE } from "@xmldom/xmldom"; var XmlNodeImporter = class { constructor(element) { this.element = element; } /** * Local record for registered namespaces to avoid set the namespace declaration in every child */ registeredNamespaces = {}; import() { return this.importRecursive(this.element); } importRecursive(element) { const node = new XmlNode(element.tagName); node.setValue(this.extractValue(element)); node.setCData(this.extractCData(element)); if (element.prefix && element.prefix !== "") { this.registerNamespace(node, `xmlns:${element.prefix}`, element.namespaceURI); this.registerNamespace(node, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); } for (const attribute of element.attributes) { node.setAttribute(attribute.nodeName, attribute.nodeValue); } if (element.hasAttributeNS(NAMESPACE.XMLNS, "")) { node.setAttribute("xmlns", element.getAttributeNS(NAMESPACE.XMLNS, "")); } for (const childElement of element.childNodes) { if (!isElement(childElement)) { continue; } const childNode = this.importRecursive(childElement); node.children().add(childNode); } return node; } registerNamespace(node, prefix, uri) { if (this.registeredNamespaces[prefix]) { return; } this.registeredNamespaces[prefix] = uri; node.setAttribute(prefix, uri); } extractValue(element) { const values = []; for (const children of element.childNodes) { if (!isText(children)) { continue; } values.push(children.data); } return values.join(""); } extractCData(element) { const values = []; for (const children of element.childNodes) { if (!isCDataSection(children)) { continue; } values.push(children.data); } return values.join(""); } }; // src/xml_nodes/xml_node_utils.ts var nodeToXmlElement = (node) => { return new XmlNodeExporter(node).export(); }; var nodeToXmlString = (node, withXmlHeader = false) => { const element = nodeToXmlElement(node); const document = element.ownerDocument; if (withXmlHeader) { const pi = document.createProcessingInstruction("xml", 'version="1.0" encoding="UTF-8"'); document.insertBefore(pi, document.firstChild); return getSerializer().serializeToString(document); } return getSerializer().serializeToString(document); }; var nodeFromXmlElement = (element) => { return new XmlNodeImporter(element).import(); }; var nodeFromXmlString = (content) => { return nodeFromXmlElement(documentElement(newDocumentContent(content))); }; export { Attr, CDATASection, DOMImplementation2 as DOMImplementation, DOMParser2 as DOMParser, Document, Element, MIME_TYPE2 as MIME_TYPE, NAMESPACE2 as NAMESPACE, Node, Text, XMLSerializer2 as XMLSerializer, XmlAttributes, XmlNode, XmlNodeExporter, XmlNodeImporter, XmlNodes, XmlNodesSorter, createDomElement, createElement, documentElement, formatNumber, getDomImplementation, getParser, getSerializer, isAttribute, isCDataSection, isDocument, isElement, isNode, isText, isValidXmlName, newDocument, newDocumentContent, nodeFromXmlElement, nodeFromXmlString, nodeToXmlElement, nodeToXmlString, onErrorStopParsing2 as onErrorStopParsing, ownerDocument, roundNumber, toFloat }; /* istanbul ignore if -- @preserve Hard of test */