UNPKG

libxml2-wasm

Version:

WebAssembly-based libxml2 javascript wrapper

184 lines (183 loc) 6.88 kB
import { addFunction, allocCStringArray, free, xmlC14NExecute, xmlOutputBufferCreateIO, xmlOutputBufferClose, XmlError, XmlTreeCommonStruct, } from './libxml2.mjs'; import { createNode, createNullableNode } from './nodes.mjs'; import { ContextStorage } from './utils.mjs'; const c14nCallbackStorage = new ContextStorage(); /** * Global C14N visibility callback - created once at module initialization. * Signature: int(void* user_data, xmlNodePtr node, xmlNodePtr parent) * @internal */ const c14nIsVisibleCallback = addFunction((userDataIndex, nodePtr, parentPtr) => { const ctx = c14nCallbackStorage.get(userDataIndex); // Handle nodeSet mode if (ctx.rootPtrs !== null) { // Visible if node is a selected root, or lies within any selected root subtree if (ctx.rootPtrs.has(nodePtr)) return 1; let cur = parentPtr; while (cur !== 0) { if (ctx.rootPtrs.has(cur)) return 1; cur = XmlTreeCommonStruct.parent(cur); } return 0; } // Handle isVisible callback mode if (ctx.jsCallback !== null) { // Cascade invisibility check if (ctx.cascade && ctx.invisible) { if (parentPtr !== 0 && ctx.invisible.has(parentPtr)) { ctx.invisible.add(nodePtr); return 0; } } const res = ctx.jsCallback(createNode(nodePtr), createNullableNode(parentPtr)) ? 1 : 0; if (ctx.cascade && ctx.invisible && res === 0) { ctx.invisible.add(nodePtr); } return res; } /* c8 ignore next 2, callback is not registered if neither is present */ return 1; }, 'iiii'); /** * C14N (Canonical XML) modes supported by libxml2 * @see http://www.w3.org/TR/xml-c14n * @see http://www.w3.org/TR/xml-exc-c14n */ export const XmlC14NMode = { /** Original C14N 1.0 specification */ XML_C14N_1_0: 0, /** Exclusive C14N 1.0 (omits unused namespace declarations) */ XML_C14N_EXCLUSIVE_1_0: 1, /** C14N 1.1 specification */ XML_C14N_1_1: 2, }; /** * Check if a node is within a subtree rooted at a specific node by walking * up the parent chain using the libxml-provided parent pointer. * * Important: Namespace declaration nodes (xmlNs) are not part of the tree and * don't have a normal parent field. libxml2 calls the visibility callback with * the owning element as `parentPtr`, so we must start walking from `parentPtr` * rather than dereferencing the node. * @internal */ function isNodeInSubtree(nodePtr, parentPtr, rootPtr) { if (nodePtr === rootPtr) { return true; } let currentPtr = parentPtr; while (currentPtr !== 0) { if (currentPtr === rootPtr) { return true; } currentPtr = XmlTreeCommonStruct.parent(currentPtr); } return false; } /** * Internal implementation using xmlC14NExecute * @internal */ function canonicalizeInternal(handler, docPtr, options = {}, cascade = true) { const hasIsVisible = (opts) => typeof opts.isVisible === 'function'; const hasNodeSet = (opts) => opts.nodeSet instanceof Set; // Validate mutually exclusive options if (hasIsVisible(options) && hasNodeSet(options)) { throw new XmlError('Cannot specify both isVisible and nodeSet'); } let outputBufferPtr = null; let prefixArrayPtr = 0; let contextIndex = 0; try { // Create output buffer using IO callbacks outputBufferPtr = xmlOutputBufferCreateIO(handler); // Build callback context based on options if (hasIsVisible(options) || hasNodeSet(options)) { const context = { jsCallback: hasIsVisible(options) ? options.isVisible : null, rootPtrs: hasNodeSet(options) ? new Set(Array.from(options.nodeSet) .map((n) => n._nodePtr)) : null, cascade, invisible: cascade ? new Set() : null, }; contextIndex = c14nCallbackStorage.allocate(context); } // Handle inclusive namespace prefixes if (options.inclusiveNamespacePrefixes) { prefixArrayPtr = allocCStringArray(options.inclusiveNamespacePrefixes); } const mode = options.mode ?? XmlC14NMode.XML_C14N_1_0; const withComments = options.withComments ? 1 : 0; const result = xmlC14NExecute(docPtr, contextIndex !== 0 ? c14nIsVisibleCallback : 0, contextIndex, // user_data is the storage index mode, prefixArrayPtr, withComments, outputBufferPtr); /* c8 ignore next 3, defensive code */ if (result < 0) { throw new XmlError('Failed to canonicalize XML document'); } } finally { if (prefixArrayPtr) free(prefixArrayPtr); if (outputBufferPtr) { xmlOutputBufferClose(outputBufferPtr); } if (contextIndex !== 0) { c14nCallbackStorage.free(contextIndex); } } } /** * Canonicalize an entire XML document to a buffer and invoke callbacks to process. * * @param handler Callback to receive the canonicalized output * @param doc The XML document to canonicalize * @param options Canonicalization options * * @example * ```typescript * const handler = new XmlStringOutputBufferHandler(); * canonicalizeDocument(handler, doc, { * mode: XmlC14NMode.XML_C14N_1_0, * withComments: false * }); * ``` */ export function canonicalizeDocument(handler, doc, options = {}) { canonicalizeInternal(handler, doc._ptr, options); } /** * Canonicalize a subtree of an XML document to a buffer and invoke callbacks to process. * * This is a convenience helper that creates an isVisible callback to filter * only nodes within the specified subtree. * * @param handler Callback to receive the canonicalized output * @param doc The document containing the subtree * @param subtreeRoot The root node of the subtree to canonicalize * @param options Canonicalization options (cannot include isVisible or nodeSet) * * @example * ```typescript * const element = doc.get('//my-element'); * const handler = new XmlStringOutputBufferHandler(); * canonicalizeSubtree(handler, doc, element!, { * mode: XmlC14NMode.XML_C14N_EXCLUSIVE_1_0, * inclusiveNamespacePrefixes: ['ns1', 'ns2'], * withComments: false * }); * ``` */ export function canonicalizeSubtree(handler, doc, subtreeRoot, options = {}) { const subtreeRootPtr = subtreeRoot._nodePtr; const isVisible = (node, parent) => (isNodeInSubtree(node._nodePtr, parent ? parent._nodePtr : 0, subtreeRootPtr)); // Use non-cascading behavior for subtree helper canonicalizeInternal(handler, doc._ptr, { ...options, isVisible, }, /* wrapCascade */ false); } //# sourceMappingURL=c14n.mjs.map