pub struct Document {Show 14 fields
pub document: Document,
pub pending: Vec<Node>,
pub node: Node,
pub node_boxes: FxHashMap<usize, Digested>,
pub node_fonts: FxHashMap<u64, Font>,
pub idstore: FxHashMap<String, Node>,
pub spilled_ids: FxHashSet<String>,
pub rewrite_labels: FxHashMap<String, String>,
pub rewrite_labels_shared: Option<Rc<FxHashMap<String, String>>>,
pub scoped_rules_strict: bool,
pub literal_placeholders: bool,
pub spill_flat: bool,
pub fragment_ancestor_ids: FxHashSet<String>,
pub fragment_parent_qname: Option<SymStr>,
/* private fields */
}Fields§
§document: Document§pending: Vec<Node>§node: Node§node_boxes: FxHashMap<usize, Digested>§node_fonts: FxHashMap<u64, Font>§idstore: FxHashMap<String, Node>§spilled_ids: FxHashSet<String>Streaming pass 1: every xml:id moved out of idstore by a spill.
The GLOBAL half of id-collision dedup — a later build-time minting of
the same id must rename exactly as eager would have (witness: the
131 MB book restarts chapter numbering per part, and the second Ch1
collided only with a SPILLED chapter).
rewrite_labels: FxHashMap<String, String>Document-wide labels SHARED by every streaming pass-2 fragment, consulted
only when rewrite_labels misses. Pass 2 used to copy the whole spilled
index into each fragment’s own map, which is quadratic in document size:
28,068 labels × 459,579 segments on the 131 MB witness = 12.9 billion
String allocations. Frag-local labels still win, preserving exactly the
entry().or_insert_with() precedence that copy had.
scoped_rules_strict: boolStreaming only: an UNRESOLVED label:/id: rewrite scope makes the
rule INERT instead of continuing unscoped. Perl (and the eager path)
continue with the remaining clauses on the same tree when a scope fails
to resolve — harmless there, because in a whole document a
scope=section id always resolves. In a fragment (or on the spine,
where sections are spilled placeholders) “not here” usually means “in
another fragment”, and continuing unscoped applies the rule to
EVERYTHING — sweep witness tests/math/declare.tex, where section-7
declarations stamped section-1 math.
literal_placeholders: boolStreaming: serialize spill placeholders LITERALLY (<_spilled_ ref=…/>)
instead of splicing the segment text. True during pass 1 (a spilling
ancestor must keep its children’s placeholders — inlining them rebuilt
multi-GB segments: an 841 MB and a 1.85 GB one on the 131 MB witness,
and pass 2 died re-parsing them) and on pass-2 fragment docs (which
re-emit the placeholders they contain). False only at final assembly,
where the splice resolves placeholders RECURSIVELY.
spill_flat: boolSerialize spill segments FLAT — no indentation, no decorative newlines.
A spilled segment’s text is an intermediate: pass 2 re-parses it,
processes it, and re-serializes with serialize_aux at the recorded
depth, and only THAT text reaches the output. The indentation pass 1 used
to emit was therefore generated, written to disk, read back, materialized
as libxml2 text nodes, deleted again by strip_indentation_whitespace,
and finally regenerated — pure round-trip tax.
Measured on the 131 MB witness: 51.2% of serialized bytes are leading
whitespace + newlines (189.4 MB of 381.5 MB sampled, 6,092,937 lines).
Over ~2.45 GB of segment text that is ~1.2 GB generated and written, read
back, and ~40M text nodes allocated in pass 2’s parse — which
strip_indentation_whitespace then unlink_nodes, and unlink does not
free, so they are orphaned for the fragment’s lifetime.
Set for pass 1 only. Fragment documents in pass 2 are separate Documents
and default to false, so the OUTPUT keeps its formatting exactly.
fragment_ancestor_ids: FxHashSet<String>Streaming pass 2 only: the fragment’s ancestor xml:ids at spill time
(SegmentMeta::ancestors). A label:/id:-scoped rewrite whose scope
resolves to one of these covers the WHOLE fragment.
fragment_parent_qname: Option<SymStr>Streaming pass 2 only: the recorded qname of the fragment’s REAL parent
(SegmentMeta::parent). finalize_rec substitutes it for the
ltx:_lxfragment parse wrapper in schema decisions, so top-level
fragment content is judged against the element it will splice back
under (e.g. the empty-ltx:text collapse needs
can_contain(parent, grandchild)).
Implementations§
Source§impl Document
impl Document
pub fn new() -> Self
Sourcepub fn from_xml_document(
doc: XmlDoc,
node_fonts: HashMap<u64, Font>,
) -> Result<Self>
pub fn from_xml_document( doc: XmlDoc, node_fonts: HashMap<u64, Font>, ) -> Result<Self>
Wrap an existing XML document (a streamed fragment from
crate::sxml::FragmentReader) as a Document, so the pass-2 phases —
rewrites, math parsing, per-fragment finalize — run on it with the same
machinery the eager path uses. The insertion point starts at the root;
the idstore is rebuilt from the fragment’s own xml:ids; node_fonts
is seeded by the caller (the hash→Font table is conversion-global and
pointer-free, so sharing its contents is safe).
Sourcepub fn get_element(&self) -> Option<Node>
pub fn get_element(&self) -> Option<Node>
Get the element at (or containing) the current insertion point.
Sourcepub fn findnodes(&mut self, xpath: &str, node_opt: Option<&Node>) -> Vec<Node>
pub fn findnodes(&mut self, xpath: &str, node_opt: Option<&Node>) -> Vec<Node>
Find the nodes according to the given xpath expression,
the xpath is relative to $node (if given), otherwise to the document node.
Sourcepub fn get_xpath(&mut self) -> &mut XPath
pub fn get_xpath(&mut self) -> &mut XPath
Get an XPath context that knows about our namespace mappings.
Sourcepub fn findnode(&mut self, xpath: &str, node: Option<&Node>) -> Option<Node>
pub fn findnode(&mut self, xpath: &str, node: Option<&Node>) -> Option<Node>
Like findnodes, but only returns the first matched node
Sourcepub fn findvalues(
&mut self,
xpath: &str,
node_opt: Option<&Node>,
) -> Vec<String>
pub fn findvalues( &mut self, xpath: &str, node_opt: Option<&Node>, ) -> Vec<String>
Like findnodes, but expects an xpath that evaluates to a literal value (e.g. for attributes)
Sourcepub fn get_node(&self) -> &Node
pub fn get_node(&self) -> &Node
The current insertion point: the node that absorbed content is appended
to, and the one open_element opens beneath.
Perl’s $document->getNode.
pub fn get_node_mut(&mut self) -> &mut Node
pub fn get_document(&self) -> &XmlDoc
pub fn get_document_mut(&mut self) -> &mut XmlDoc
pub fn finalize(&mut self) -> Result<()>
Sourcepub fn finalize_subtree(&mut self, node: &mut Node) -> Result<()>
pub fn finalize_subtree(&mut self, node: &mut Node) -> Result<()>
finalize restricted to one subtree: resolve fonts
against ancestors and strip the _font/_autoopened/… bookkeeping
attributes, without the document-level passes (idstore rebuild, XMDual
pruning, RDFa/namespace declarations) that only make sense for a complete
document.
Used by latexml_post::make_bibliography to render a .bib field value
into an XML fragment through the real engine: it absorbs into a scratch
ltx:text wrapper and serializes that wrapper’s children.
Whole-document finalize cannot serve that caller —
not because it errors (it returns Ok), but because running
finalize_rec from the ROOT legitimately UNWRAPS a redundant font-only
ltx:text: measured on such a scratch document, the content survives at
the root while the caller’s wrapper handle is left detached and childless
(wrapper_children 1 -> 0, parent = None), so it serializes to nothing.
Starting the recursion AT the wrapper keeps it addressable.
Sourcepub fn cleanup_unreferenced_xmtok_ids(&mut self)
pub fn cleanup_unreferenced_xmtok_ids(&mut self)
Remove xml:ids from XMTok elements that aren’t referenced by any idref. The Rust math parser generates xml:ids on XMTok nodes for internal XMRef linkage during parsing. After finalization (which includes prune_xmduals), some ids are no longer referenced. Perl’s parser doesn’t generate these ids.
Sourcepub fn absorb(
&mut self,
object: &Digested,
props_opt: Option<SymHashMap<Stored>>,
) -> Result<()>
pub fn absorb( &mut self, object: &Digested, props_opt: Option<SymHashMap<Stored>>, ) -> Result<()>
Document construction at the Current Insertion Point.
absorb the given $box into the DOM (called from constructors). This will return a list of whatever nodes were created. Note that this may include nodes that are children of other nodes in the list or nodes that are no longer in the document. Also, note that when a text nodes is appended to, the complete text node is in the list, not just the portion that was added. [Note that recording the nodes being constructed isn’t all that costly, but filtering them for parent/child relations IS, particularly since it usually isn’t needed]
A box that is a TBox, or List, or Whatsit, is responsible for carrying out its own insertion, but it should ultimately call methods of Document that will record the nodes that were created. $box can also be a plain string (Digested::Postponed) which will be inserted according to whatever font, mode, etc, are in %props.
pub fn get_constructed_nodes(&self) -> &[Node]
Sourcepub fn absorb_string(
&mut self,
object: &str,
props: &SymHashMap<Stored>,
) -> Result<Option<Node>>
pub fn absorb_string( &mut self, object: &str, props: &SymHashMap<Stored>, ) -> Result<Option<Node>>
This is a refactored else cases from the main absorb routine, to allow for better type
hygiene
Sourcepub fn insert_element_before(
&self,
point: &Node,
qname: &str,
attrib: Option<HashMap<String, String>>,
) -> Result<Node>
pub fn insert_element_before( &self, point: &Node, qname: &str, attrib: Option<HashMap<String, String>>, ) -> Result<Node>
Perl: insertElementBefore — insert a new element before a given point node.
Creates a new element with the given qname and attributes, inserts it
before point in the DOM tree, and returns the new node.
Sourcepub fn insert_element(
&mut self,
qname: &str,
content: Vec<&Digested>,
attrib: Option<HashMap<String, String>>,
) -> Result<Node>
pub fn insert_element( &mut self, qname: &str, content: Vec<&Digested>, attrib: Option<HashMap<String, String>>, ) -> Result<Node>
Shorthand for open,absorb,close, but returns the new node.
Sourcepub fn insert_pi(
&mut self,
op: &str,
attributes_opt: Option<HashMap<String, String>>,
) -> Result<()>
pub fn insert_pi( &mut self, op: &str, attributes_opt: Option<HashMap<String, String>>, ) -> Result<()>
Insert a ProcessingInstruction of the form Does NOT move the current insertion point to the PI, but may move up past a text node.
Sourcepub fn open_element(
&mut self,
qname: &str,
attributes: Option<HashMap<String, String>>,
font_opt: Option<&Font>,
) -> Result<Node>
pub fn open_element( &mut self, qname: &str, attributes: Option<HashMap<String, String>>, font_opt: Option<&Font>, ) -> Result<Node>
Open a new qname element and make it the current insertion point.
Where it opens is the model’s decision, not the caller’s: the insertion
point is first moved by find_insertion_point,
which auto-opens any element the schema requires in between and
auto-closes any that cannot contain qname. So a binding may open a
ltx:para without first checking what is currently open.
See open_element_at to open at an explicit node
instead, and close_element for the counterpart.
Sourcepub fn close_element(&mut self, qname: &str) -> Result<Option<Node>>
pub fn close_element(&mut self, qname: &str) -> Result<Option<Node>>
Note: This closes the deepest open node of a given type. This can cause problems with auto-opened nodes, esp. ones for fontswitches! Since this is an “explicit request”, we’re currently skipping over those nodes, ie. we’re automatically closing them, even if they’re the same type as we’re asking to close!!! This is kinda risky! Maybe we should try to request closing of specific nodes.
pub fn is_openable(&self, test_qname: &str) -> bool
Sourcepub fn is_closeable<T: IntoVDQS>(&self, tags: T) -> Option<Node>
pub fn is_closeable<T: IntoVDQS>(&self, tags: T) -> Option<Node>
Check whether it is possible to close each element in @tags, any intervening nodes must be autocloseable. returning the last Some(node) that would be closed if it is possible, otherwise None
Sourcepub fn maybe_close_element(&mut self, qname: &str) -> Result<Option<Node>>
pub fn maybe_close_element(&mut self, qname: &str) -> Result<Option<Node>>
Close qname, if it is closeable — the forgiving counterpart to
close_element.
Returns the closed node, or None when no such element is open (or it
cannot be auto-closed from here). That “or nothing happens” is the point:
it lets a binding close an element it may have opened without having to
track whether it did, where close_element would report an error.
Sourcepub fn close_to_node(&mut self, node: &Node, ifopen: bool) -> Result<()>
pub fn close_to_node(&mut self, node: &Node, ifopen: bool) -> Result<()>
Closes all nodes until $node becomes the current point.
Sourcepub fn close_node(&mut self, node: &Node) -> Result<()>
pub fn close_node(&mut self, node: &Node) -> Result<()>
Closes all nodes until $node is closed.
Sourcepub fn maybe_close_node(&mut self, node: &Node) -> Result<()>
pub fn maybe_close_node(&mut self, node: &Node) -> Result<()>
Only if needed/possible: closes all nodes until $node is closed
pub fn close_node_with_strictness( &mut self, strict: bool, node: &Node, ) -> Result<()>
Sourcepub fn get_tag_action_list_parts(
&self,
tag: SymStr,
when: TagOptionName,
) -> (Vec<TagConstructionClosure>, Vec<TagConstructionClosure>)
pub fn get_tag_action_list_parts( &self, tag: SymStr, when: TagOptionName, ) -> (Vec<TagConstructionClosure>, Vec<TagConstructionClosure>)
Like Self::get_tag_action_list, but keeps the _Late bucket
separate. Streaming needs the split: a root’s early/normal hooks
(structural — the pending-resource drain) run at open as usual, while
its _late hooks (semantically “with digestion complete” — frontmatter
placement, root classes) are deferred to end-of-digestion.
Sourcepub fn get_tag_action_list(
&self,
tag: SymStr,
when: TagOptionName,
) -> Vec<TagConstructionClosure> ⓘ
pub fn get_tag_action_list( &self, tag: SymStr, when: TagOptionName, ) -> Vec<TagConstructionClosure> ⓘ
get the actions that should be performed on afterOpen or afterClose
pub fn serialize_to_string(&self) -> String
Sourcepub fn serialize_aux(
&self,
node: &Node,
depth: usize,
noindent: bool,
heuristic: bool,
) -> String
pub fn serialize_aux( &self, node: &Node, depth: usize, noindent: bool, heuristic: bool, ) -> String
We ought to try for something close to C14N (http://www.w3.org/TR/xml-c14n), but keep XML declaration, comments and don’t convert empty elements.
Sourcepub fn set_node(&mut self, node: &Node)
pub fn set_node(&mut self, node: &Node)
Move the current insertion point to node — Perl Document.pm:setNode
L74-87.
A document fragment is not itself an insertion point: as in Perl, a
single-child fragment is descended into, and a fragment with several
children is an error (reported, not fatal — see the body comment on why
this returns ()).
pub fn insert_math_token( &mut self, text: &str, attributes: HashMap<String, String>, font_opt: Option<&Font>, ) -> Result<Node>
Sourcepub fn insert_comment(&mut self, text: &str) -> Result<Node>
pub fn insert_comment(&mut self, text: &str) -> Result<Node>
Insert a new comment, or append to previous comment. Does NOT move the current insertion point to the Comment, but may move up past a text node. Perl: Document.pm lines 678-698
pub fn open_text(&mut self, text: &str, font: &Font) -> Result<Option<Node>>
pub fn close_text_internal(&mut self) -> Result<Node>
Sourcepub fn close_node_internal(&mut self, node: &Node) -> Result<()>
pub fn close_node_internal(&mut self, node: &Node) -> Result<()>
Close node, and any current nodes below it.
No checking! Use this when you’ve already verified that node can be closed.
and, of course, node must be current or some ancestor of it!!!
pub fn merge_attributes( &mut self, from: &Node, to: &mut Node, force: Option<&HashSet<&'static str>>, ) -> Result<()>
Sourcepub fn record_constructed_node(&mut self, node: &Node)
pub fn record_constructed_node(&mut self, node: &Node)
Note that a box has been absorbed creating node;
This does book keeping so that we can return the sequence of nodes
that were added by absorbing material.
pub fn filter_deletions(&self, nodes: Vec<Node>) -> Vec<Node>
Sourcepub fn filter_children(&self, nodes: Vec<Node>) -> Vec<Node>
pub fn filter_children(&self, nodes: Vec<Node>) -> Vec<Node>
Given a list of nodes such as from ->absorb, filter out all the nodes that are children of other nodes in the list.
Sourcepub fn get_insertion_context(&self, levels_opt: Option<usize>) -> Result<String>
pub fn get_insertion_context(&self, levels_opt: Option<usize>) -> Result<String>
Return a string indicating the path to the current insertion point in the document. if $levels is defined, show only that many levels
Sourcepub fn find_insertion_point(
&mut self,
qname: &str,
has_opened_opt: Option<SymStr>,
) -> Result<Node>
pub fn find_insertion_point( &mut self, qname: &str, has_opened_opt: Option<SymStr>, ) -> Result<Node>
Find the node where an element with qualified name qname can be inserted.
This will move up the tree (closing auto-closable elements),
or down (inserting auto-openable elements), as needed.
pub fn find_insertion_point_qsym( &mut self, qsym: SymStr, has_opened_opt: Option<SymStr>, ) -> Result<Node>
pub fn node_set_attribute(&mut self, key: &str, value: &str) -> Result<()>
pub fn node_get_attribute(&mut self, name: &str) -> Option<String>
Sourcepub fn set_attribute(
&mut self,
node: &mut Node,
key: &str,
value: &str,
) -> Result<()>
pub fn set_attribute( &mut self, node: &mut Node, key: &str, value: &str, ) -> Result<()>
Set any allowed attribute on a node, decoding the prefix, if any. Also records, and checks, any id attributes. [xml:id and namespaced attributes are always allowed]
“Allowed” is the document model’s call, so this is not a plain libxml
write: an attribute the schema does not permit on node is dropped rather
than emitted, which is what keeps a binding from producing a document that
fails validation.
pub fn add_ss_values( &mut self, node: &mut Node, key: &str, values_str: &str, ) -> Result<()>
Sourcepub fn add_class(&mut self, node: &mut Node, class: &str) -> Result<()>
pub fn add_class(&mut self, node: &mut Node, class: &str) -> Result<()>
Add one or more CSS classes to a node, without disturbing the ones it already carries.
class is treated as the space-separated set it is
(add_ss_values), so several bindings can each
contribute a class to the same element — which is why this exists rather
than a plain setAttribute("class", …), and what
remove_ss_values undoes.
Sourcepub fn remove_ss_values(&mut self, node: &mut Node, key: &str, values: &str)
pub fn remove_ss_values(&mut self, node: &mut Node, key: &str, values: &str)
Remove space-separated values from an attribute. Perl: sub removeSSValues (Document.pm lines 1423-1437)
Sourcepub fn remove_class(&mut self, node: &mut Node, class: &str)
pub fn remove_class(&mut self, node: &mut Node, class: &str)
Remove CSS class from element. Perl: sub removeClass (Document.pm lines 1439-1442)
Sourcepub fn float_to_attribute(&mut self, key: &str) -> Option<Node>
pub fn float_to_attribute(&mut self, key: &str) -> Option<Node>
Float to a node that can accept the given attribute. Returns the previous node so it can be restored after setting the attribute. Perl: sub floatToAttribute (Document.pm lines 1080-1092)
Sourcepub fn is_open(&self, node: &Node) -> bool
pub fn is_open(&self, node: &Node) -> bool
Check if a node is currently open (i.e., is or contains the current node). Perl: sub isOpen (Document.pm lines 1998-2006)
pub fn unrecord_id(&mut self, id: &str)
Sourcepub fn safe_unlink(&mut self, node: Node)
pub fn safe_unlink(&mut self, node: Node)
Guardian-safe unlink: walk node’s subtree invalidating every xml:id
idstore entry, then detach it from its parent. Use this in preference
to a raw node.unlink() anywhere the node might carry an xml:id
(subtree reshuffles in math-parser, post-processing cleanup, etc.),
to prevent the dangling-Node class of bug that produced the 1605.08055
Finalizing-phase SIGSEGV (see SYNC_STATUS.md D3b).
This is the unlink-only half of remove_node — it does not adjust
self.node / the insertion point, which is correct for callers that
intend to re-parent the unlinked subtree elsewhere (the common case).
Callers that want the insertion-point bookkeeping should use
remove_node instead.
Sourcepub fn record_node_ids(&mut self, node: &Node) -> Result<()>
pub fn record_node_ids(&mut self, node: &Node) -> Result<()>
These are used to record or unrecord, in bulk, all the ids within a node (tree).
When record_id_with_node detects a duplicate it renames the id (e.g.
X.1.mf → X.1.mfa). Any sibling <ltx:XMRef idref="X.1.mf"/> in the
same subtree would otherwise become a dangling reference for the post-
processor. After re-recording IDs, sweep the subtree once and update any
XMRef whose idref matches an entry in the rename map.
Perl Core::Document::recordNodeIDs (Document.pm L1466-1472) has the
same latent bug — recording renames but XMRefs aren’t touched. The bug
surfaces in our port because the math-parser path
(parser.rs::install_replacements-style unrecord+record round-trips)
is denser than Perl’s; both end up needing this remap to keep XMRef
chains intact. Intentional surpass-Perl divergence; tracked in
SYNC_STATUS Task #10.
pub fn unrecord_node_ids(&mut self, node: &Node)
Sourcepub fn spill_closed_subtrees(
&mut self,
index: &mut FragmentIndex,
) -> Result<usize>
pub fn spill_closed_subtrees( &mut self, index: &mut FragmentIndex, ) -> Result<usize>
Spill closed subtrees to the segment store, freeing their DOM memory (streaming pass 1).
Walks the SPINE — root element down to the current insertion element —
and, at every level, spills the runs of closed element children that
precede the open (spine) child: serialize in the pre-finalize form with
the exact (depth, noindent) the eager serializer would use, write to
store, record every spilled xml:id/label in index, purge the
pointer-keyed registries (idstore via unrecord_id, node_boxes by
key — freed nodes’ pointers can be REUSED, so a stale entry is not just
dangling but can mis-associate a box with a future node), replace the
run with one <_spilled_ ref="N"/> placeholder, and unlink+free the
nodes. Serialization at assembly time splices the processed segment
where the placeholder sits, so document order, nesting and
spine-attribute finality are preserved by construction.
Two deliberate exclusions:
- ROOT-level children spill only when sectional (
ROOT_SPILLABLE): the frontmatter fallback andmaybe_promote_leading_titleoperate on the leading non-sectional root children at end-of-build. - Children of the insertion element itself never spill: in-flight construction state may still reference recently built nodes.
Returns the number of runs spilled.
Sourcepub fn purge_node_boxes_rec(&mut self, node: &Node)
pub fn purge_node_boxes_rec(&mut self, node: &Node)
Remove the pointer-keyed node_boxes entries for node and its whole
subtree. MANDATORY before freeing nodes mid-conversion: a freed node’s
address is REUSED by later allocations, and a stale entry then
mis-associates the old box (and its font) with an unrelated new node —
observed as spurious <text font="italic"> wrappers the moment the
math parser started freeing replaced trees promptly.
Sourcepub fn discard_subtree(&mut self, node: Node)
pub fn discard_subtree(&mut self, node: Node)
Discard a garbage subtree for good: purge the pointer-keyed
node_boxes entries, then free the C memory NOW via the fork’s
Node::free_subtree — the missing half of every unlink-to-discard
site (rust-libxml Linkage: a doc-created node that is unlinked but
never re-attached is freed by NOBODY; math parsing discards thousands
of replaced subtrees per document, measured ~1.4 MB/formula, ~1.8 GB
retained per 32 MB streaming segment). free_subtree also
neutralizes every live wrapper into the subtree, so stray clones in
long-lived collections (constructed_nodes, an idstore epoch) go
inert instead of firing a deferred xmlFreeNode after the document
itself is gone.
PRECONDITIONS (the caller’s contract, satisfied at current sites):
- every
xml:idin the subtree is already unrecorded or transferred — this deliberately does NOTunrecord_node_ids, because discard always follows a copy (append_tree) that re-recorded the SAME id strings for the copies, and unrecording here would kill those fresh entries; - no live handle into the subtree is used afterwards.
Sourcepub fn sweep_stale_node_boxes(&mut self)
pub fn sweep_stale_node_boxes(&mut self)
Drop every node_boxes entry whose node is no longer IN this document
tree (streaming pass 1’s self-healing sweep). Entries are written for
every constructed node and purged at the spill/discard chokepoints —
but dozens of build-time discard paths (alignment rearrangement above
all: 105k align environments on the 131 MB witness) detach nodes
without purging, and each stale entry pins a whole Digested box tree.
Measured: ~518k stale entries before the FIRST spill, growing past
1.75M — the dominant residual pass-1 creep after the C-side frees.
A mark-and-retain against the live tree is immune to every such path,
including future ones. Runs when the map is large; the post-spill
spine is small, so the mark phase is cheap.
Sourcepub fn set_spill_store(&mut self, store: SegmentStore)
pub fn set_spill_store(&mut self, store: SegmentStore)
Attach the processed-segment store for serialization: from here on,
serialize_into splices each segment’s output text where its
placeholder sits (streaming assembly).
Sourcepub fn set_defer_root_after_open(&mut self, defer: bool)
pub fn set_defer_root_after_open(&mut self, defer: bool)
Streaming pass 1 switch: defer (true) or restore (false) the ROOT
element’s after_open hook dispatch. See the field docs.
Sourcepub fn root_after_open_deferred(&self) -> bool
pub fn root_after_open_deferred(&self) -> bool
Is the root’s late-hook dispatch currently deferred (streaming pass 1)? Engine bindings consult this to defer digestion-state-dependent insertions (frontmatter) to end-of-digestion.
Sourcepub fn process_pending_resources_at_top(&mut self) -> Result<()>
pub fn process_pending_resources_at_top(&mut self) -> Result<()>
Streaming pass 1: fold freshly queued resources into the live document
as they arrive, at the top of the root. Perl’s own contract
(Package.pm:RequireResource): with a live document, addResource
inserts DIRECTLY; only a document-less preamble queues. The eager path
keeps the queue until the root’s after_open because its build starts
post-digestion; under streaming that drain is deferred to
end-of-digestion, and mid-digestion consumers — the frontmatter
fallback’s /ltx:document/ltx:resource[last()] anchor — need the
resources already placed.
Sourcepub fn dispatch_deferred_root_hooks(&mut self) -> Result<()>
pub fn dispatch_deferred_root_hooks(&mut self) -> Result<()>
Streaming: dispatch the ROOT’s deferred after-open hooks at
end-of-digestion — the eager timing (build starts only after digestion
ends) — with the insertion point at the TOP of the root, as if it had
just opened empty. after_open itself cannot be used here: it pins the
current node to the dispatched element, so everything the hooks insert
(resources, frontmatter) would APPEND after the built content instead
of leading it. A ltx:_Capture_ wrapper as first child recreates the
empty-root insertion context (the frontmatter fallback’s own
technique), and is unwrapped afterwards.
Sourcepub fn add_extra_rdfa_prefixes<'a>(
&mut self,
prefixes: impl Iterator<Item = &'a str>,
)
pub fn add_extra_rdfa_prefixes<'a>( &mut self, prefixes: impl Iterator<Item = &'a str>, )
Merge RDFa prefixes recorded from spilled content, so the root’s
prefix= attribute covers usages the live-DOM scan can no longer see.
Sourcepub fn take_spill_store(&mut self) -> Option<SegmentStore>
pub fn take_spill_store(&mut self) -> Option<SegmentStore>
Detach the spill store (the streaming driver takes it out for pass 2 — which mutates segments while fragment documents exist independently — and re-attaches it for assembly).
Sourcepub fn rebuild_idstore_from_dom(&mut self) -> Result<()>
pub fn rebuild_idstore_from_dom(&mut self) -> Result<()>
Discard the in-memory idstore cache and rebuild it from the
current DOM state. Historically guarded the 1605.08055 SIGSEGV
where mark_xmnode_visibility dereferenced dangling lookup_id
entries while recursing through XMRef targets.
As of cycle 72, the 5 call sites that previously dropped nodes
without unrecord_id — math-parser replace_tree at
parser.rs:456/690 (cascades via remove_node) and unbind_node
loops at parser.rs:639/856 + rewrite.rs:522 (all have
preceding unrecord_node_ids guards) — are ID-safe. This rebuild
is retained as a belt-and-suspenders probe until the
1605.08055 verification per SYNC_STATUS.md D3b lands.
The rebuild is a DOM walk, so live id uniqueness is restored
alongside — duplicates already in DOM are resolved with
modify_id, matching record_node_ids semantics.
Sourcepub fn modify_id(&mut self, id: String) -> String
pub fn modify_id(&mut self, id: String) -> String
Get a new, related, but unique id. Sneaky option: try “ID_SUFFIX” as a suffix for id, first. Perl: sub modifyID (Document.pm lines 1483-1494)
pub fn lookup_id(&self, id: &str) -> Option<&Node>
Sourcepub fn get_idstore_clone(&self) -> HashMap<String, Node>
pub fn get_idstore_clone(&self) -> HashMap<String, Node>
Clone the idstore for use in thread-local contexts (math parsing).
pub fn mark_xmnode_visibility(&mut self) -> Result<()>
Sourcepub fn prune_dangling_split_xmrefs(&mut self) -> Result<()>
pub fn prune_dangling_split_xmrefs(&mut self) -> Result<()>
Remove ltx:XMRef[@_split_ref="1"] whose idref no longer
resolves. These are the XMRefs minted by
amsmath::rearrange_ams_split to mirror the flattened cell
sequence inside an XMDual(XMWrap(refs), XMArray(cells)). The
math parser can later absorb some cells (typically inserted
MULOP times-ops on \mathcal{L}\rho chains) into wrapping
XMApps, dropping their xml:id from the live DOM and leaving
the sibling XMRefs dangling. Left in place, each dangling
XMRef trips three separate diagnostics later — math parser’s
read_xmref Warn, finalize’s mark_xmnode_visibility Warn, and
post-process’s mark_xm_node_visibility Error.
We restrict the sweep to the _split_ref marker so refs from
other provenance (base_xmath \lx@dual, renamed-id cases like
declare_test’s S1.Ex1.m1.1 → .1a rename) stay untouched.
Content-preserving: XMRefs are structural cross-references, not author body, and the math parser has already absorbed the referenced cell into the visible XMArray branch — no glyph or formula material is lost.
Sourcepub fn prune_xmduals(&mut self) -> Result<()>
pub fn prune_xmduals(&mut self) -> Result<()>
Reduce any ltx:XMDual’s to just the visible branch, if the other is not visible (according to markXMNodeVisibility) If we could be 100% sure that the marking had stayed consistent (after various doc surgery) we could avoid re-marking, but we’d better be sure before removing nodes!
Sourcepub fn set_node_box(&mut self, node: &Node, digested: Digested)
pub fn set_node_box(&mut self, node: &Node, digested: Digested)
Record the Box that created this node.
pub fn get_node_box(&self, node: &Node) -> Option<Digested>
Sourcepub fn set_node_font(&mut self, node: &mut Node, font: &Font) -> Result<()>
pub fn set_node_font(&mut self, node: &mut Node, font: &Font) -> Result<()>
Record the Font of a node
pub fn copy_node_font(&mut self, from: &Node, to: &mut Node) -> Result<()>
Sourcepub fn merge_node_font_rec(&mut self, node: &Node, font: &Font) -> Result<()>
pub fn merge_node_font_rec(&mut self, node: &Node, font: &Font) -> Result<()>
Possibly a sign of a design flaw; Set the node’s font & all children that HAD the same font.
pub fn set_box_font(&mut self, node: &mut Node) -> Result<()>
pub fn get_node_font(&self, node: &Node) -> &Font
Sourcepub fn decode_font(&self, font_hash: &str) -> Option<&Font>
pub fn decode_font(&self, font_hash: &str) -> Option<&Font>
Decode a _font hash string to a Font object
pub fn has_node_font(&self, node: &Node) -> bool
pub fn get_node_language(&self, node: &Node) -> String
Sourcepub fn remove_node(&mut self, node: Node)
pub fn remove_node(&mut self, node: Node)
Remove a node from the document (from it’s parent).
Two pieces of bookkeeping come with it: the xml:id of the node and of
every descendant is un-recorded, so the ids are free for reuse and no
dangling reference is left behind; and if the insertion point was inside
what is being removed, it is rescued up to the parent — otherwise the
document would go on building into a detached subtree.
Sourcepub fn open_element_at(
&mut self,
point: &mut Node,
qname: &str,
attributes: Option<HashMap<String, String>>,
font_opt: Option<Font>,
) -> Result<Node>
pub fn open_element_at( &mut self, point: &mut Node, qname: &str, attributes: Option<HashMap<String, String>>, font_opt: Option<Font>, ) -> Result<Node>
This opens a new element at the specified point, rather than the current insertion point. This is useful during document rearrangement or augmentation that may be needed later in the process.
Sourcepub fn close_element_at(&mut self, node: &mut Node) -> Result<()>
pub fn close_element_at(&mut self, node: &mut Node) -> Result<()>
Whenever a node has been created using openElementAt, closeElementAt ought to be used to close it, when you’re finished inserting into $node. Basically, this just runs any afterClose operations.
pub fn after_open(&mut self, node: &mut Node) -> Result<()>
pub fn after_close(&mut self, node: &mut Node) -> Result<()>
Sourcepub fn append_clone(
&mut self,
node: &mut Node,
new_children: Vec<Node>,
) -> Result<()>
pub fn append_clone( &mut self, node: &mut Node, new_children: Vec<Node>, ) -> Result<()>
Append COPIES of new_children under node.
Cloning rather than moving is what makes this usable on nodes that belong
to another document — moving them would remove them from it. Three things
are repaired on the way in: document fragments are expanded to their
children, the namespace structure is rebuilt clean (libxml2 otherwise has
a tendency to introduce annoying “default” namespace prefix
declarations), and every xml:id in the copy is rewritten to a fresh id,
with internal references remapped to match — otherwise the copy would
duplicate the original’s ids.
Sourcepub fn wrap_nodes(
&mut self,
qname: &str,
nodes: Vec<Node>,
) -> Result<Option<Node>>
pub fn wrap_nodes( &mut self, qname: &str, nodes: Vec<Node>, ) -> Result<Option<Node>>
Wrap nodes with an element named qname, making the new element replace
the first node, and all nodes becomes the child of the new node.
[this makes most sense if nodes are a sequence of siblings]
Returns None if qname isn’t allowed in the parent, or if nodes
aren’t allowed in qname, otherwise the newly created qname — so a
caller must treat “wrapped” as a request the model may decline, not as a
guarantee. None also covers a first node with no parent (already
detached, or the root): there is nothing to wrap it in place of. Witness
1804.09736.
The wrapper inherits the parent’s font and box, so wrapping does not
change how the enclosed material renders. unwrap_nodes
is the inverse.
Sourcepub fn unwrap_nodes(&mut self, node: Node) -> Result<()>
pub fn unwrap_nodes(&mut self, node: Node) -> Result<()>
Unwrap the children of $node, by replacing $node by its children.
Sourcepub fn replace_node(&mut self, node: Node, with: Vec<Node>) -> Result<()>
pub fn replace_node(&mut self, node: Node, with: Vec<Node>) -> Result<()>
Replace node by nodes (presumably descendants of some kind?)
Sourcepub fn rename_node(
&mut self,
node: Node,
newname: &str,
reinsert: bool,
) -> Result<Node>
pub fn rename_node( &mut self, node: Node, newname: &str, reinsert: bool, ) -> Result<Node>
Rename an element to newname, returning the new node.
Not an in-place rename: a fresh element is opened next to the original,
the attributes and (when reinsert) the children are carried over, and
the original is removed. Perl went this way “initially since
$node->setNodeName was broken in XML::LibXML 1.58”, and kept it because
building the replacement through the normal open path is what runs the
model’s checks and afterOpen hooks for the new tag — a raw rename would
leave an element the schema never vetted.
rename_node_qsym is the same for an already
interned name.
pub fn rename_node_qsym( &mut self, node: Node, newsym: SymStr, reinsert: bool, ) -> Result<Node>
pub fn trim_node_whitespace(&mut self, node: &Node) -> Result<()>
pub fn add_resource(&mut self, resource: Resource) -> Result<()>
pub fn process_pending_resources(&mut self) -> Result<()>
pub fn make_error(&mut self, error_class: &str, content: &str) -> Result<()>
Sourcepub fn float_to_element(
&mut self,
qname: &str,
closeifpossible: bool,
) -> Result<Option<Node>>
pub fn float_to_element( &mut self, qname: &str, closeifpossible: bool, ) -> Result<Option<Node>>
Find a node in the document that can contain an element qname
pub fn float_to_label(&mut self) -> Option<Node>
pub fn set_box_to_absorb(&mut self, arg: Option<Digested>)
pub fn expire_box_to_absorb(&mut self)
Sourcepub fn lookup_rewrite_label(&self, key: &str) -> Option<String>
pub fn lookup_rewrite_label(&self, key: &str) -> Option<String>
Resolve a rewrite label: this document’s own labels first, then the
shared document-wide map a streaming fragment carries
(rewrite_labels_shared). The two-level lookup replaces copying the
whole label index into every fragment — see the field docs.
pub fn load_labels_for_rewrite(&mut self) -> Result<()>
Sourcepub fn generate_id(&mut self, node: &mut Node, prefix: &str) -> Result<()>
pub fn generate_id(&mut self, node: &mut Node, prefix: &str) -> Result<()>
This function computes an xml:id for a node, if it hasn’t already got one.
It is suitable for use in Tag afterOpen as
Tag('ltx:para',afterOpen=>sub { GenerateID(@_,'p'); });
It generates an id of the form <parentid>.<prefix><number>
The parent node (the one with ID=<parentid>) also maintains a counter
stored in an attribute _ID_counter_<prefix> recording the last used
number for prefix amongst its descendents.
pub fn replace_tree(&mut self, new: Node, old: Node) -> Result<Option<Node>>
Sourcepub fn replace_tree_free(
&mut self,
new: Node,
old: Node,
) -> Result<Option<Node>>
pub fn replace_tree_free( &mut self, new: Node, old: Node, ) -> Result<Option<Node>>
replace_tree for a caller that owns BOTH trees as garbage-after-copy
(the math parser’s rebuild sites): the replacement is copied into
place exactly as replace_tree does (Perl appendTree parity —
elements are re-created, sources abandoned), and then the sources are
FREED: old’s subtree, plus new’s detached root when new is a
standalone built tree rather than a node inside old. Without the
frees every replaced formula leaks its pre-parse tree AND the built
parse tree (see discard_subtree).
On the None return (old had no parent) nothing was copied and
NOTHING is freed — the caller keeps using new as-is.
pub fn append_tree(&mut self, node: &mut Node, data: Vec<Node>) -> Result<()>
Sourcepub fn insert_xml(&mut self, xml: &str) -> Result<()>
pub fn insert_xml(&mut self, xml: &str) -> Result<()>
Parse an XML / (X)HTML markup string and splice the resulting subtree into the document at the current insertion point.
Named as the markup counterpart to Document::insert_element (Perl
insertElement): both insert an already-FINISHED thing at the current point.
Deliberately NOT an absorb* name — in Perl absorb consumes a digested
Box and has no XML::LibXML branch at all (it would die on a node), so
borrowing that verb here would imply a kinship that does not exist.
This is the Rust analog of Perl BookML’s \bmlRawHTML idiom, which composes
two mechanisms core never chains itself: XML::LibXML->parse_string
(LaTeXML::Common::XML::Parser parseChunk, Common/XML/Parser.pm:36-39)
and $document->appendTree
(Document.pm:2093, foreign-node branch :2105-2124). Both halves already
exist here — libxml’s parser (a direct latexml_core dep, used in
common/relaxng/scan.rs) and Document::append_tree — so this is pure
glue.
The markup must be WELL-FORMED, but need not be a single root: several
sibling nodes, or bare text, are accepted as a document fragment
(OXIDIZED_DESIGN #66 — Perl’s parseChunk is single-node only). The parsed
nodes are re-created one by one through append_tree’s model-aware path, so
namespaces declared in the snippet (e.g. xhtml) are preserved and xml:ids
re-registered. Malformed markup is REJECTED, never salvaged — see
crate::common::xml::parse_chunk for what libxml’s recovery mode
destroys — and surfaces as a clean Error: that inserts nothing, degrading
the offending binding rather than aborting the conversion (the
runtime-bindings failure-isolation contract).
Sourcepub fn insert_nodes(&mut self, nodes: Vec<Node>) -> Result<()>
pub fn insert_nodes(&mut self, nodes: Vec<Node>) -> Result<()>
Splice ALREADY-PARSED nodes into the document at the current insertion
point. The shared tail of Document::insert_xml and of any caller that
obtained its nodes some other way (a script that parsed once and inserts
repeatedly, or that edited the parsed tree before inserting).
The caller must keep whatever owns nodes alive across this call: libxml
nodes are pointers into their document. crate::common::xml::ParsedFragment
exists to make that ownership explicit rather than a comment.
Trait Implementations§
Source§impl Object for Document
impl Object for Document
Source§fn get_locator(&self) -> Option<Locator>
fn get_locator(&self) -> Option<Locator>
None is the honest
“no recorded source position” (replacing the old Locator::default()
file!()/line!() sentinel). For “where the parser is now” (error
reporting, box creation), use the free fn gullet::get_locator().