]> git.kaiwu.me - njs.git/commit
XML: mutate XML nodes in place
authorDmitry Volyntsev <xeioex@nginx.com>
Thu, 10 Sep 2026 03:26:26 +0000 (20:26 -0700)
committerDmitry Volyntsev <xeioexception@gmail.com>
Fri, 11 Sep 2026 01:29:10 +0000 (18:29 -0700)
commit3b4d715e988ba1642dbea093c28654923b30ea82
tree1619d03c7cffa93340408be11fcbe3abd48cc024
parentd7bff7e0ac1926c9140135e24af2485236beb98f
XML: mutate XML nodes in place

Previously, text and structural mutators copied and replaced the receiver's
libxml2 node.  Existing XMLNode wrappers continued to point at the detached
original, while subsequent lookups returned the replacement.  Wrappers for
children preserved by the operation likewise pointed into the detached old
subtree.  In QuickJS, mutating the detached receiver again could dereference
the NULL result of xmlReplaceNode().

The fix is to modify the existing receiver's child list directly.  Existing
wrappers and subsequent lookups therefore continue to refer to the same
libxml2 node.  Removed child lists remain readable until the QuickJS
document or njs VM is destroyed.  A node is considered live only when
following its parent links reaches its xmlDoc.  Replacement children and
cleanup records are prepared before the live tree is modified.

Changed behaviour:
- A removed child has an undefined $parent, while its descendants retain
  their internal parent links.
- Mutating or canonicalizing a removed XMLNode throws TypeError.
- Text and attribute values must be primitive strings; null and undefined
  retain their deletion semantics.
- C14N rejects an excluding node from another document or a detached list.
- addChild() and $tags reject namespace-bearing or unsupported source trees
  before mutation.
- Named $tags assignments are consistently rejected by both engines.

While here, use an unsigned QuickJS $tags array length, support assigning
$tags on an empty njs XMLNode, and make deleting $tags from an empty node
a no-op.
external/njs_xml_module.c
external/qjs_xml_module.c
test/xml/xml.t.mjs