UNPKG

typesxml

Version:

Open source XML library written in TypeScript

557 lines 24.6 kB
"use strict"; /******************************************************************************* * Copyright (c) 2023-2026 Maxprograms. * * This program and the accompanying materials * are made available under the terms of the Eclipse License 1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/epl-v10.html * * Contributors: * Maxprograms - initial API and implementation *******************************************************************************/ Object.defineProperty(exports, "__esModule", { value: true }); exports.Catalog = void 0; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const DOMBuilder_js_1 = require("./DOMBuilder.js"); const SAXParser_js_1 = require("./SAXParser.js"); const XMLUtils_js_1 = require("./XMLUtils.js"); class Catalog { systemCatalog; publicCatalog; uriCatalog; dtdCatalog; systemSuffixCatalog; uriSuffixCatalog; uriRewrites; systemRewrites; delegatePublicEntries; delegateSystemEntries; delegateURIEntries; workDir; base; prefer; visitedCatalogs; constructor(catalogFile, visitedCatalogs) { if (!(0, node_path_1.isAbsolute)(catalogFile)) { throw new Error('Catalog file must be absolute: ' + catalogFile); } if (!(0, node_fs_1.existsSync)(catalogFile)) { throw new Error('Catalog file ' + catalogFile + ' not found'); } this.visitedCatalogs = visitedCatalogs ?? new Set(); if (this.visitedCatalogs.has(catalogFile)) { throw new Error('Circular catalog reference detected: ' + catalogFile); } this.visitedCatalogs.add(catalogFile); this.systemCatalog = new Map(); this.publicCatalog = new Map(); this.uriCatalog = new Map(); this.dtdCatalog = new Map(); this.systemSuffixCatalog = new Map(); this.uriSuffixCatalog = new Map(); this.uriRewrites = new Array(); this.systemRewrites = new Array(); this.delegatePublicEntries = new Array(); this.delegateSystemEntries = new Array(); this.delegateURIEntries = new Array(); this.workDir = (0, node_path_1.dirname)(catalogFile); this.base = ''; this.prefer = 'public'; let contentHandler = new DOMBuilder_js_1.DOMBuilder(); let parser = new SAXParser_js_1.SAXParser(); parser.setContentHandler(contentHandler); parser.parseFile(catalogFile); let catalogDocument = contentHandler.getDocument(); if (!catalogDocument) { throw new Error('Catalog file ' + catalogFile + ' is empty'); } let catalogRoot = catalogDocument.getRoot(); if (!catalogRoot) { throw new Error('Catalog file ' + catalogFile + ' is empty'); } if (catalogRoot.getName() !== 'catalog') { throw new Error('Catalog root element must be <catalog>'); } let preferAttr = catalogRoot.getAttribute('prefer'); if (preferAttr) { this.prefer = preferAttr.getValue(); } this.recurse(catalogRoot); } recurse(catalogRoot) { for (let child of catalogRoot.getChildren()) { let currentBase = this.base; let xmlBase = child.getAttribute("xml:base"); if (xmlBase) { this.base = xmlBase.getValue(); if (!this.base.endsWith('/')) { this.base += '/'; } if (!(0, node_path_1.isAbsolute)(this.base)) { this.base = (0, node_path_1.resolve)(this.workDir, this.base); } if (!(0, node_fs_1.existsSync)(this.base)) { throw new Error('Invalid xml:base: ' + this.base); } } if (child.getName() === 'public') { let publicIdAttribute = child.getAttribute("publicId"); if (!publicIdAttribute) { throw new Error('publicId attribute is required for <public>'); } let publicId = publicIdAttribute.getValue(); if (publicId.startsWith("urn:publicid:")) { publicId = this.unwrapUrn(publicId); } if (!this.publicCatalog.has(publicId)) { let uriAttribute = child.getAttribute("uri"); if (!uriAttribute) { throw new Error('uri attribute is required for <public>'); } let uri = this.makeAbsolute(uriAttribute.getValue()); if ((0, node_fs_1.existsSync)(uri)) { this.publicCatalog.set(publicId, uri); if (uri.endsWith(".dtd") || uri.endsWith(".ent") || uri.endsWith(".mod")) { let name = (0, node_path_1.basename)(uri); if (!this.dtdCatalog.has(name)) { this.dtdCatalog.set(name, uri); } } } } } if (child.getName() === 'system') { let uriAttribute = child.getAttribute("uri"); if (!uriAttribute) { throw new Error('uri attribute is required for <system>'); } let uri = this.makeAbsolute(uriAttribute.getValue()); if ((0, node_fs_1.existsSync)(uri)) { let systemId = child.getAttribute("systemId"); if (!systemId) { throw new Error('systemId attribute is required for <system>'); } this.systemCatalog.set(systemId.getValue(), uri); if (uri.endsWith(".dtd")) { let name = (0, node_path_1.basename)(uri); if (!this.dtdCatalog.has(name)) { this.dtdCatalog.set(name, uri); } } } } if (child.getName() === 'uri') { let uriAttribute = child.getAttribute("uri"); if (!uriAttribute) { throw new Error('uri attribute is required for <uri>'); } let uri = this.makeAbsolute(uriAttribute.getValue()); if ((0, node_fs_1.existsSync)(uri)) { let nameAttribute = child.getAttribute("name"); if (!nameAttribute) { throw new Error('name attribute is required for <uri>'); } this.uriCatalog.set(nameAttribute.getValue(), uri); if (uri.endsWith(".dtd") || uri.endsWith(".ent") || uri.endsWith(".mod")) { let name = (0, node_path_1.basename)(uri); if (!this.dtdCatalog.has(name)) { this.dtdCatalog.set(name, uri); } } } } if (child.getName() === 'rewriteURI') { let rewritePrefix = child.getAttribute("rewritePrefix"); if (!rewritePrefix) { throw new Error('rewritePrefix attribute is required for <rewriteURI>'); } let uri = this.makeAbsolute(rewritePrefix.getValue()); let uriStartString = child.getAttribute("uriStartString"); if (!uriStartString) { throw new Error('uriStartString attribute is required for <rewriteURI>'); } let pair = [uriStartString.getValue(), uri]; if (!this.uriRewrites.some(p => p[0] === pair[0])) { this.uriRewrites.push(pair); } } if (child.getName() === 'rewriteSystem') { let rewritePrefix = child.getAttribute("rewritePrefix"); if (!rewritePrefix) { throw new Error('rewritePrefix attribute is required for <rewriteSystem>'); } let uri = this.makeAbsolute(rewritePrefix.getValue()); let systemIdStartString = child.getAttribute("systemIdStartString"); if (!systemIdStartString) { throw new Error('systemIdStartString attribute is required for <rewriteSystem>'); } let pair = [systemIdStartString.getValue(), uri]; if (!this.systemRewrites.some(p => p[0] === pair[0])) { this.systemRewrites.push(pair); } } if (child.getName() === 'systemSuffix') { let systemIdSuffix = child.getAttribute("systemIdSuffix"); if (!systemIdSuffix) { throw new Error('systemIdSuffix attribute is required for <systemSuffix>'); } let uriAttribute = child.getAttribute("uri"); if (!uriAttribute) { throw new Error('uri attribute is required for <systemSuffix>'); } let uri = this.makeAbsolute(uriAttribute.getValue()); if ((0, node_fs_1.existsSync)(uri) && !this.systemSuffixCatalog.has(systemIdSuffix.getValue())) { this.systemSuffixCatalog.set(systemIdSuffix.getValue(), uri); } } if (child.getName() === 'uriSuffix') { let uriSuffix = child.getAttribute("uriSuffix"); if (!uriSuffix) { throw new Error('uriSuffix attribute is required for <uriSuffix>'); } let uriAttribute = child.getAttribute("uri"); if (!uriAttribute) { throw new Error('uri attribute is required for <uriSuffix>'); } let uri = this.makeAbsolute(uriAttribute.getValue()); if ((0, node_fs_1.existsSync)(uri) && !this.uriSuffixCatalog.has(uriSuffix.getValue())) { this.uriSuffixCatalog.set(uriSuffix.getValue(), uri); } } if (child.getName() === 'delegatePublic') { let publicIdStartString = child.getAttribute("publicIdStartString"); if (!publicIdStartString) { throw new Error('publicIdStartString attribute is required for <delegatePublic>'); } let catalogAttribute = child.getAttribute("catalog"); if (!catalogAttribute) { throw new Error('catalog attribute is required for <delegatePublic>'); } let catalogPath = this.makeAbsolute(catalogAttribute.getValue()); let pair = [publicIdStartString.getValue(), catalogPath]; if (!this.delegatePublicEntries.some(p => p[0] === pair[0])) { this.delegatePublicEntries.push(pair); } } if (child.getName() === 'delegateSystem') { let systemIdStartString = child.getAttribute("systemIdStartString"); if (!systemIdStartString) { throw new Error('systemIdStartString attribute is required for <delegateSystem>'); } let catalogAttribute = child.getAttribute("catalog"); if (!catalogAttribute) { throw new Error('catalog attribute is required for <delegateSystem>'); } let catalogPath = this.makeAbsolute(catalogAttribute.getValue()); let pair = [systemIdStartString.getValue(), catalogPath]; if (!this.delegateSystemEntries.some(p => p[0] === pair[0])) { this.delegateSystemEntries.push(pair); } } if (child.getName() === 'delegateURI') { let uriStartString = child.getAttribute("uriStartString"); if (!uriStartString) { throw new Error('uriStartString attribute is required for <delegateURI>'); } let catalogAttribute = child.getAttribute("catalog"); if (!catalogAttribute) { throw new Error('catalog attribute is required for <delegateURI>'); } let catalogPath = this.makeAbsolute(catalogAttribute.getValue()); let pair = [uriStartString.getValue(), catalogPath]; if (!this.delegateURIEntries.some(p => p[0] === pair[0])) { this.delegateURIEntries.push(pair); } } if (child.getName() === 'nextCatalog') { let catalogAttribute = child.getAttribute("catalog"); if (!catalogAttribute) { throw new Error('catalog attribute is required for <nextCatalog>'); } let nextCatalogPath = this.makeAbsolute(catalogAttribute.getValue()); if (this.visitedCatalogs.has(nextCatalogPath)) { throw new Error('Circular catalog reference detected: ' + nextCatalogPath); } let catalog = new Catalog(nextCatalogPath, new Set(this.visitedCatalogs)); let map = catalog.getSystemCatalog(); map.forEach((value, key) => { if (!this.systemCatalog.has(key)) { this.systemCatalog.set(key, value); } }); map = catalog.getPublicCatalog(); map.forEach((value, key) => { if (!this.publicCatalog.has(key)) { this.publicCatalog.set(key, value); } }); map = catalog.getUriCatalog(); map.forEach((value, key) => { if (!this.uriCatalog.has(key)) { this.uriCatalog.set(key, value); } }); map = catalog.getDtdCatalog(); map.forEach((value, key) => { if (!this.dtdCatalog.has(key)) { this.dtdCatalog.set(key, value); } }); map = catalog.getSystemSuffixCatalog(); map.forEach((value, key) => { if (!this.systemSuffixCatalog.has(key)) { this.systemSuffixCatalog.set(key, value); } }); map = catalog.getUriSuffixCatalog(); map.forEach((value, key) => { if (!this.uriSuffixCatalog.has(key)) { this.uriSuffixCatalog.set(key, value); } }); let array = catalog.getUriRewrites(); array.forEach((value) => { if (!this.uriRewrites.some(p => p[0] === value[0])) { this.uriRewrites.push(value); } }); array = catalog.getSystemRewrites(); array.forEach((value) => { if (!this.systemRewrites.some(p => p[0] === value[0])) { this.systemRewrites.push(value); } }); array = catalog.getDelegatePublicEntries(); array.forEach((value) => { if (!this.delegatePublicEntries.some(p => p[0] === value[0])) { this.delegatePublicEntries.push(value); } }); array = catalog.getDelegateSystemEntries(); array.forEach((value) => { if (!this.delegateSystemEntries.some(p => p[0] === value[0])) { this.delegateSystemEntries.push(value); } }); array = catalog.getDelegateURIEntries(); array.forEach((value) => { if (!this.delegateURIEntries.some(p => p[0] === value[0])) { this.delegateURIEntries.push(value); } }); } this.recurse(child); this.base = currentBase; } } makeAbsolute(uri) { if ((0, node_path_1.isAbsolute)(uri)) { return uri; } if (this.base !== '') { return (0, node_path_1.resolve)(this.base, uri); } return (0, node_path_1.resolve)(this.workDir, uri); } unwrapUrn(urn) { if (!urn.startsWith('urn:publicid:')) { return urn; } let publicId = urn.trim().substring('urn:publicid:'.length); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '+', ' '); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, ':', '//'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, ';', '::'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%2B', '+'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%3A', ':'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%2F', '/'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%3B', ';'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%27', '\''); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%3F', '?'); publicId = XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%23', '#'); return XMLUtils_js_1.XMLUtils.replaceAll(publicId, '%25', '%'); } getSystemCatalog() { return this.systemCatalog; } getPublicCatalog() { return this.publicCatalog; } getUriCatalog() { return this.uriCatalog; } getDtdCatalog() { return this.dtdCatalog; } getSystemSuffixCatalog() { return this.systemSuffixCatalog; } getUriSuffixCatalog() { return this.uriSuffixCatalog; } getUriRewrites() { return this.uriRewrites; } getSystemRewrites() { return this.systemRewrites; } getDelegatePublicEntries() { return this.delegatePublicEntries; } getDelegateSystemEntries() { return this.delegateSystemEntries; } getDelegateURIEntries() { return this.delegateURIEntries; } resolveEntity(publicId, systemId) { if (this.prefer === 'system') { if (systemId) { let location = this.matchSystem(systemId); if (location) { return location; } } if (publicId) { return this.matchPublic(publicId); } return undefined; } // default: prefer="public" if (publicId) { let location = this.matchPublic(publicId); if (location) { return location; } } return this.matchSystem(systemId); } matchSystem(systemId) { if (systemId) { // Apply the rewrite with the longest matching prefix (spec: longest match wins) let bestPrefix = ''; let bestRewriteUri = ''; for (let pair of this.systemRewrites) { if (systemId.startsWith(pair[0]) && pair[0].length > bestPrefix.length) { bestPrefix = pair[0]; bestRewriteUri = pair[1]; } } if (bestPrefix) { systemId = bestRewriteUri + systemId.substring(bestPrefix.length); } // If any delegateSystem entry matches, search only those catalogs (spec: do not continue here) let matchingDelegates = this.delegateSystemEntries .filter(pair => systemId.startsWith(pair[0])) .sort((a, b) => b[0].length - a[0].length); if (matchingDelegates.length > 0) { for (let pair of matchingDelegates) { if ((0, node_fs_1.existsSync)(pair[1])) { let delegateCatalog = new Catalog(pair[1], new Set(this.visitedCatalogs)); let result = delegateCatalog.matchSystem(systemId); if (result) { return result; } } } return undefined; } if (this.systemCatalog.has(systemId)) { return this.systemCatalog.get(systemId); } // systemSuffix: longest matching suffix wins let bestSuffix = ''; let bestSuffixUri; for (let [suffix, uri] of this.systemSuffixCatalog) { if (systemId.endsWith(suffix) && suffix.length > bestSuffix.length) { bestSuffix = suffix; bestSuffixUri = uri; } } if (bestSuffixUri) { return bestSuffixUri; } let fileName = (0, node_path_1.basename)(systemId); if (this.dtdCatalog.has(fileName)) { return this.dtdCatalog.get(fileName); } } return undefined; } matchPublic(publicId) { if (publicId.startsWith("urn:publicid:")) { publicId = this.unwrapUrn(publicId); } // If any delegatePublic entry matches, search only those catalogs (spec: do not continue here) let matchingDelegates = this.delegatePublicEntries .filter(pair => publicId.startsWith(pair[0])) .sort((a, b) => b[0].length - a[0].length); if (matchingDelegates.length > 0) { for (let pair of matchingDelegates) { if ((0, node_fs_1.existsSync)(pair[1])) { let delegateCatalog = new Catalog(pair[1], new Set(this.visitedCatalogs)); let result = delegateCatalog.matchPublic(publicId); if (result) { return result; } } } return undefined; } if (this.publicCatalog.has(publicId)) { return this.publicCatalog.get(publicId); } return undefined; } matchURI(uri) { if (uri) { // Apply the rewrite with the longest matching prefix (spec: longest match wins) let bestPrefix = ''; let bestRewriteUri = ''; for (let pair of this.uriRewrites) { if (uri.startsWith(pair[0]) && pair[0].length > bestPrefix.length) { bestPrefix = pair[0]; bestRewriteUri = pair[1]; } } if (bestPrefix) { uri = bestRewriteUri + uri.substring(bestPrefix.length); } // If any delegateURI entry matches, search only those catalogs (spec: do not continue here) let matchingDelegates = this.delegateURIEntries .filter(pair => uri.startsWith(pair[0])) .sort((a, b) => b[0].length - a[0].length); if (matchingDelegates.length > 0) { for (let pair of matchingDelegates) { if ((0, node_fs_1.existsSync)(pair[1])) { let delegateCatalog = new Catalog(pair[1], new Set(this.visitedCatalogs)); let result = delegateCatalog.matchURI(uri); if (result) { return result; } } } return undefined; } if (this.uriCatalog.has(uri)) { return this.uriCatalog.get(uri); } // uriSuffix: longest matching suffix wins let bestSuffix = ''; let bestSuffixUri; for (let [suffix, suffixUri] of this.uriSuffixCatalog) { if (uri.endsWith(suffix) && suffix.length > bestSuffix.length) { bestSuffix = suffix; bestSuffixUri = suffixUri; } } if (bestSuffixUri) { return bestSuffixUri; } } return undefined; } } exports.Catalog = Catalog; //# sourceMappingURL=Catalog.js.map