From b7d2f47116dcd29bdadd013e6b3f2f8799d5b80b Mon Sep 17 00:00:00 2001 From: Tony Novak Date: Sat, 7 Feb 2026 23:02:37 -0500 Subject: [PATCH] Add addClone method to XmlElement Call `xmlElement.addClone(otherElement)` to add a clone of `otherElement` (possibly from a different XmlDocument) to the children of `xlmElement`. --- binding/exported-functions.txt | 1 + src/libxml2.mts | 26 ++++++++++++++++++++++++++ src/libxml2raw.d.mts | 11 +++++++++++ src/nodes.mts | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/binding/exported-functions.txt b/binding/exported-functions.txt index 460231f..1799549 100644 --- a/binding/exported-functions.txt +++ b/binding/exported-functions.txt @@ -11,6 +11,7 @@ _xmlCtxtSetErrorHandler _xmlCtxtValidateDtd _xmlDocGetRootElement _xmlDocSetRootElement +_xmlDOMWrapCloneNode _xmlFreeDoc _xmlFreeDtd _xmlFreeNode diff --git a/src/libxml2.mts b/src/libxml2.mts index 64d3e70..fe6c0f6 100644 --- a/src/libxml2.mts +++ b/src/libxml2.mts @@ -79,6 +79,31 @@ function allocUTF8Buffer(str: string | null) { return [buf, len]; } +class PointerRef { + ptr: Pointer; + + constructor(ptr: Pointer) { + this.ptr = ptr; + } + + dereference(): T { + return libxml2.getValue(this.ptr, '*') as T; + } +} + +// TODO: find out actual pointer size +const POINTER_SIZE = 8; + +export function withPointerRef(process: (pointerRef: PointerRef) => R): R { + const buf = libxml2._malloc(POINTER_SIZE); + + try { + return process(new PointerRef(buf)); + } finally { + libxml2._free(buf); + } +} + function withStrings(process: (...buf: number[]) => R, ...strings: (string | null)[]): R { const args = strings.map((str) => { const [buf] = allocUTF8Buffer(str); @@ -725,6 +750,7 @@ export const xmlXPathFreeContext = libxml2._xmlXPathFreeContext; export const xmlXPathFreeObject = libxml2._xmlXPathFreeObject; export const xmlXPathNewContext = libxml2._xmlXPathNewContext; export const xmlXPathSetContextNode = libxml2._xmlXPathSetContextNode; +export const xmlDOMWrapCloneNode = libxml2._xmlDOMWrapCloneNode; /** * Create an output buffer using I/O callbacks (same pattern as xmlSaveToIO) diff --git a/src/libxml2raw.d.mts b/src/libxml2raw.d.mts index e0e3c09..49bf8c2 100644 --- a/src/libxml2raw.d.mts +++ b/src/libxml2raw.d.mts @@ -2,6 +2,7 @@ type Pointer = number; type CString = Pointer; type XmlAttrPtr = Pointer; type XmlCharEncodingHandlerPtr = Pointer; +type XMLDomWrapCtxtPtr = Pointer; type XmlParserCtxtPtr = Pointer; type XmlParserInputBufferPtr = Pointer; type XmlDocPtr = Pointer; @@ -62,6 +63,16 @@ export class LibXml2 { _xmlCtxtValidateDtd(ctxt: XmlParserCtxtPtr, doc: XmlDocPtr, dtd: XmlDtdPtr): number; _xmlFreeNode(node: XmlNodePtr): void; _xmlFreeParserCtxt(ctxt: XmlParserCtxtPtr): void; + _xmlDOMWrapCloneNode( + ctxt: XMLDomWrapCtxtPtr, + sourceDoc: XmlDocPtr, + node: XmlNodePtr, + resNode: Pointer, + destDoc: XmlDocPtr, + destParent: XmlDocPtr, + deep: number, + options: number, + ): number; _xmlDocGetRootElement(doc: XmlDocPtr): XmlNodePtr; _xmlDocSetRootElement(doc: XmlDocPtr, root: XmlNodePtr): XmlNodePtr; _xmlFreeDoc(Doc: XmlDocPtr): void; diff --git a/src/nodes.mts b/src/nodes.mts index 3c2c9b0..0b62001 100644 --- a/src/nodes.mts +++ b/src/nodes.mts @@ -8,9 +8,11 @@ import { XmlNsStruct, XmlOutputBufferHandler, XmlXPathObjectStruct, + withPointerRef, xmlAddChild, xmlAddNextSibling, xmlAddPrevSibling, + xmlDOMWrapCloneNode, xmlFreeNode, xmlGetNsList, xmlHasNsProp, @@ -798,6 +800,37 @@ export class XmlElement extends XmlTreeNode { ); } + /** + * Insert a clone of the given source node to the end of the children list. + */ + addClone(srcNode: XmlElement, deep: boolean = true): XmlElement { + return withPointerRef((resNodePtr) => { + const result = xmlDOMWrapCloneNode( + 0, + srcNode.doc._ptr, + srcNode._nodePtr, + resNodePtr.ptr, + this.doc._ptr, + this._nodePtr, + deep ? 1 : 0, + 0, + ); + + switch (result) { + case 0: + { + const resNode = resNodePtr.dereference(); + xmlAddChild(this._nodePtr, resNode); + return new XmlElement(resNode); + } + case 1: + throw new Error('Unsupported node type given to addClone'); + default: + throw new Error('Internal error in addClone'); + } + }); + } + /** * Save the XmlElement to a buffer and invoke the callbacks to process. *