← board

xml.dom is two questions, and neither one is worth doing yet

The row, and what is actually behind it

missing module: xml_dom is row 3 of the corrected ladder table (tools/nilpy_ladder.py), 4 files. Only three of them import xml.dom at all — the fourth inherits the wall transitively:

file imports needs
html5lib/treewalkers/base.py from xml.dom import Node Node constants only
html5lib/treewalkers/dom.py from xml.dom import Node Node constants only
html5lib/treewalkers/etree.py nothing — inherits via from . import base
html5lib/treebuilders/dom.py from xml.dom import minidom, Node a real DOM

Worth noting for anyone reading a ladder row as a work estimate: a row counts transitive victims. A grep of the corpus source finds three importers; the table says four. Both are right, and only the table answers "how many files does this unblock".

Question 1 — Node: tiny, closed, and complete

Read off CPython rather than off the call sites (a table built from call sites does not announce what it omitted):

>>> [x for x in dir(xml.dom.Node) if not x.startswith('_') and callable(...)]
[]

xml.dom.Node has twelve integer constants and zero methods. It is the DOM spec's nodeType enumeration and nothing else, so a shim of it is not a partial approximation — it is the whole class, fixed by a published spec, with no version drift to track. About 20 lines.

The corpus uses it exactly as expected: Node.DOCUMENT_NODE, Node.TEXT_NODE, Node.ELEMENT_NODE, Node.COMMENT_NODE, Node.DOCUMENT_TYPE_NODE, Node.ENTITY_NODE, Node.CDATA_SECTION_NODE, Node.DOCUMENT_FRAGMENT_NODE — 8 of the 12, all as comparison operands.

Question 2 — minidom: a project, not a shim

treebuilders/dom.py builds and mutates a document. The surface it touches:

appendChild attributes childNodes cloneNode createComment createDocumentFragment
createElement createElementNS createTextNode firstChild hasAttributes
hasChildNodes insertBefore namespaceURI nodeName nodeType nodeValue normalize
ownerDocument publicId removeChild setAttribute setAttributeNS systemId
getDOMImplementation createDocument createDocumentType

...plus, decisively, a reach into a private minidom internal:

# html5lib/treebuilders/dom.py:170
# HACK: allow text nodes as children of the document node
if hasattr(self.dom, '_child_node_types'):
    # pylint:disable=protected-access
    if Node.TEXT_NODE not in self.dom._child_node_types:
        self.dom._child_node_types = list(self.dom._child_node_types)
        self.dom._child_node_types.append(Node.TEXT_NODE)

html5lib is patching minidom's private class-level list of permitted child types. A shim would have to reproduce not just the DOM API but a specific CPython implementation detail that the caller guards with hasattr — i.e. the caller already knows it is reaching into someone's internals.

This is a real DOM implementation and should be ranked as one, not as a shim. It is the honest answer the dispatch asked for: these files want a DOM, and that is a project.

Why not even do question 1 — two measurements, either sufficient

It unblocks nothing

A 20-line Node-only shim was written to scratch and measured. All four files clear the xml_dom wall, and land immediately on:

file next wall
treewalkers/base.py undefined variable (digits)
treewalkers/dom.py undefined variable (digits)
treewalkers/etree.py undefined variable (digits)
treebuilders/dom.py missing module: weakref

So xml_dom is not a wall — it is in front of a wall. Three of the four files collapse into row 1 (digits, [[bug-n-assigning-to-a-name-that-collides-with-a-pascal-shim-attribute-fails]]), which is already frank2's ground, and the fourth trades one missing module for another. Net files unblocked by shimming xml.dom today: zero. Same ranking discipline as [[bug-n-a-unicode-identifier-is-rejected-by-the-lexer]] — rank on measured unblock, not on how tractable the work looks.

And it would be silently wrong if written

This is the part that could not have been reasoned to. A class whose definition is not followed by a module-level statement currently loses its attribute initialisers and reads every one back as 0 ([[bug-n-the-last-class-in-a-module-reads-every-attribute-as-zero]], found while measuring this row). A constants-only shim is exactly that shape: one class, nothing after it.

Note this is not avoidable by writing the shim more carefully. Putting a module-level statement after class Node would mask it — and masking it is worse than hitting it, because the shim would then be correct only by virtue of a trailing line nobody could explain, and would silently break the day someone tidied it away.

So the obvious mimic_xml_dom.py — 12 constants, no module-level code — would compile, import, and give Node.TEXT_NODE == 0, Node.ELEMENT_NODE == 0, Node.DOCUMENT_NODE == 0. Every nodeType comparison in html5lib's treewalkers would then be a comparison of 0 == 0: every node would take the first branch, silently, and the walker would emit structurally wrong output with no error anywhere.

That is exactly the "looks present and fails deep inside a caller" failure the work was scoped to avoid, arriving by a mechanism nobody predicted — and the only reason it was caught is that the measurement came before the code. A half-shim is not the only way to build something that looks present; a complete shim on a broken substrate does it too.

When to revisit

Do question 1 when [[bug-n-the-last-class-in-a-module-reads-every-attribute-as-zero]] is fixed AND the digits bug is fixed — at which point it is 20 lines and genuinely complete, and there will be files behind it to unblock. Verify with an assertion on an actual constant value (Node.TEXT_NODE == 3), never merely that the import resolves, since resolving is precisely what it does while returning zero.

Question 2 should be re-filed as its own ranked item if a real DOM is ever wanted. Do not let it ride along with question 1 under one row.

Unblocked and reranked 15 -> 55 by the coordinator, 2026-08-18

Both blockers are now resolved, and the more important one was fixed today: bug-n-the-last-class-in-a-module-reads-every-attribute-as-zero (12275b26f) and bug-n-assigning-to-a-name-that-collides-with-a-pascal-shim-attribute-fails are both in done/.

That first fix is why this ticket was correctly refused twice. A constants class is exactly the shape that was broken — a module whose trailing class carries only class-level attributes read every one of them back as zero, silently, with no diagnostic. mimic_xml_dom is precisely that shape: xml.dom.Node is twelve integer constants and no methods. Verified fixed here against CPython:

from nodemod import Node
print(Node.ELEMENT_NODE, Node.TEXT_NODE, Node.DOCUMENT_NODE)
   pxx 1 3 9   CPython 1 3 9      (was 0 0 0)

Keep the campaign's scar attached to this ticket, because this is the file it was written about: a ~20-line, spec-exact, COMPLETE mimic_xml_dom written before that fix would have compiled, imported cleanly, and made every nodeType comparison 0 == 0, so every node takes the first branch and the walker emits structurally wrong output with no error anywhere. Completeness was never the protection — the substrate was. Re-run the ladder after it lands and check the VALUES, not just that it imports.

Rerank rationale: p15 predates the corpus evidence. xml_dom is 4 files on the ladder and xml_etree_elementtree a further 4, which puts it just under [[feature-b-module-shims-for-the-html5lib-corpus]] (p60) rather than at the bottom of the board. The refusal recorded in that ticket ("my own earlier refusal, still correct on v347") was correct when written and is now spent.


Status 2026-08-18 (frank3-fc): question 2 split out, question 1 BLOCKED one

step short of landing

Measured against pinned v349 (596799fd9c6e, pin commit a6e8e763e) — the pin carrying today's last-class-hoist and shim-class-visibility fixes.

Question 2 is settled and gone

Re-filed as [[feature-b-a-real-minidom-is-an-implementation-not-a-shim]] (B, p15), exactly as this ticket instructed. Nothing about the answer changed: it is ~25 DOM methods plus weakref.proxy plus a patch of minidom's private _child_node_types, for one optional tree backend. An implementation, not a shim, and it no longer rides along under this row.

Question 1 is written and correct, and cannot land

lib/rtl/mimic_xml_dom.py exists: twelve nodeType constants and the four namespace URIs, checked programmatically against CPython's xml.dom rather than typed from memory — zero mismatched, zero missing. test/lib_mimic_xml_dom.npy is a differential asserting all twelve by VALUE plus the distinctness and comparison shapes the treewalkers actually depend on; it passes under CPython (20 checks).

It does not compile under pxx, for a reason that is not this ticket's: [[bug-n-from-a-shim-import-a-class-loses-its-class-level-attributes]] (N, p75). from <shim> import Class then Class.CONSTANT is undefined variable, while the byte-identical file as a plain module answers correctly, the same shim by its literal mimic_ filename answers correctly, and import shim; shim.Class.CONSTANT answers correctly.

The blocked spelling is the corpus's own. treewalkers/base.py and dom.py do from xml.dom import Node and then compare Node.TEXT_NODE. So there is no shim-side rewrite available and none should be attempted — the qualified form works, but it is html5lib's source that would have to change, and compiling existing source unchanged is the mission.

Why this ticket's own refusal held a third time

The refusal was always "do not write it on this substrate", and the substrate has now been wrong in three different ways in two days: the constants read zero, then the fix was not in the pin Track B ships against, and now the from-import binding drops the attributes. Each was found by measuring the VALUES rather than the import — which is the discipline this ticket exists to record.

The score for this ticket, stated the way the campaign asks

Past the wall: 0. Onto the next wall: 4. The three treewalker files moved from missing module: xml_dom to undefined variable (DOCUMENT_NODE) — the p75 blocker, where they now correctly attribute — and treebuilders/dom.py moved to missing module: weakref. Compile count unchanged.

So this ticket bought attribution and a verified shim, not a compile. That is the honest reading and it should not be dressed as anything else: a reader of the board who sees the xml_dom row disappear must not conclude four files were unblocked.

What remains, when the blocker lands

  1. Compile test/lib_mimic_xml_dom.npy on a pin carrying the fix; the 20 checks must pass by value, not merely import.
  2. Wire it into make lib-test next to the other mimic_* differentials.
  3. Re-run tools/nilpy_ladder.py and report past-vs-onto. The four xml_dom files were previously measured to land on digits (now fixed) and weakref; that measurement predates two pins and must be re-taken, not quoted.

LANDED 2026-08-18 (frank3-fc) — pinned v350 (66f59112e1a9, pin 46a6189a9)

The blocker ([[bug-n-from-a-shim-import-a-class-loses-its-class-level-attributes]]) was fixed in 6cd63b836 and reached Track B in v350. Question 1 is in.

Verified by VALUE, which is the whole point of this ticket. test/lib_mimic_xml_dom.npy reads all twelve nodeType constants back and diffs them against the numbers CPython gives — 20 checks green, plus the distinctness and comparison shapes html5lib's treewalkers actually branch on. Wired into make lib-test (green, exit 0). A test asserting the import resolved would have passed during both compiler bugs while the shim answered zero; this one would not have.

The cause was in neither place we looked

ConsumeUnitQualifier was eating Node as a unit qualifier and then looking TEXT_NODE up as a bare symbol — which is exactly why the diagnostic named the ATTRIBUTE and read as a class-attribute fault to everyone. The guard that skips that for a class looked the unit up by the name the SOURCE wrote, so for a shim it searched the bare module name, found nothing, and stood down for every shim. One identifier. Worth recording because the symptom description in the bug ticket was accurate and still pointed at the wrong subsystem.

Score: past the wall 0, onto the next wall 3

file was (v349) now (v350)
treewalkers/base.py undefined variable (DOCUMENT_NODE) undefined variable (yield)
treewalkers/dom.py undefined variable (DOCUMENT_NODE) undefined variable (yield)
treewalkers/etree.py undefined variable (DOCUMENT_NODE) undefined variable (yield)
treebuilders/dom.py missing module: weakref unchanged — question 2

Compile count 6/48 → 6/48. This ticket did not move the compile number and should not be read as having done so: it moved three files off a shim wall onto yield, which is now the top row at 14 files.

Attribution, since v350 carries more than this ticket: html5lib/_tokenizer.py also moved (missing module: sysmissing module: six_moves). That is [[bug-n-from-sys-import-fails-while-import-sys-works]] being fixed — frank2's work, not this ticket's. Nothing else changed.

What remains under this row

Question 2 only, split out as [[feature-b-a-real-minidom-is-an-implementation-not-a-shim]] (B, p15), and treebuilders/dom.py still sits on missing module: weakref exactly where it was measured.

Log