Module: Moxml::Materializer
- Defined in:
- lib/moxml/materializer.rb
Overview
Flattened subtree records for conversion-style consumers (issue #132): one record per node, emitted in post-order (children before parents — build with a stack), carrying everything a consumer tree needs without allocating Moxml::Node or Moxml::Attribute wrappers.
Two emission forms share one walk (issue #143):
document.materialize { |record| ... } # Hash snapshot per node
document.materialize.to_a # Enumerator of snapshots
document.materialize_fields { |kind, qname, prefix, namespace_uri,
namespaces, attributes, text, depth| }
materialize_fields is the conversion hot path: the two list arguments are flat reused buffers — attributes stride 4 (name, value, namespace_uri, prefix), namespaces stride 2 (prefix, uri; nil prefix = default) — valid only inside the block. Copy anything you intend to keep, or use materialize, whose records are independent Hash snapshots.
Record shape (both forms):
kind: :element|:text|:cdata|:comment|:processing_instruction,
qname: "tag"|"pi-target"|nil, prefix: "p"|nil,
namespace_uri: "urn:x"|nil,
namespaces: [[prefix, uri], ...], # element's OWN declarations
attributes: [[name, value, namespace_uri, prefix], ...],
text: String|nil, depth: Integer
Adapters with a bulk path (leptris: one leptris_node_traverse FFI call for the whole subtree, reading properties straight off the C handles) answer bulk_materialize?; the others walk the wrapper tree generically. Both emit identical streams — spec-pinned.
Defined Under Namespace
Modules: Record Classes: Buffers
Constant Summary collapse
- EMPTY_ATTRIBUTES =
Shared frozen empty list for records with no list fields; adapter bulk paths yield it too.
[].freeze
Class Method Summary collapse
- .fill_element_buffers(element, buffers) ⇒ Object
-
.materialize(node, &block) ⇒ Object
Hash-snapshot form: every record is an independent Hash.
-
.materialize_fields(node, buffers = Buffers.new, &block) ⇒ Object
Zero-allocation streaming form — see the module docs.
-
.walk_fields(node, depth, buffers, &block) ⇒ Object
Generic post-order walk over the wrapper tree.
Class Method Details
.fill_element_buffers(element, buffers) ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/moxml/materializer.rb', line 133 def fill_element_buffers(element, buffers) attrs = buffers.attributes attrs.clear element.attributes.each do |attr| ns = attr.namespace attrs << attr.name << attr.value << ns&.uri << ns&.prefix end ns_buf = buffers.namespaces ns_buf.clear # declared_namespaces: the element's OWN declarations ([prefix, # uri] pairs; nil prefix = default), not the in-scope set — # enough for a consumer to rebuild scope while walking (#138). element.declared_namespaces.each do |prefix, uri| ns_buf << prefix << uri end end |
.materialize(node, &block) ⇒ Object
Hash-snapshot form: every record is an independent Hash.
82 83 84 85 86 87 88 89 |
# File 'lib/moxml/materializer.rb', line 82 def materialize(node, &block) return enum_for(:materialize, node) unless block materialize_fields(node) do |kind, qname, prefix, namespace_uri, namespaces, attributes, text, depth| yield(Record.from_fields(kind, qname, prefix, namespace_uri, namespaces, attributes, text, depth)) end end |
.materialize_fields(node, buffers = Buffers.new, &block) ⇒ Object
Zero-allocation streaming form — see the module docs. Requires a block: the reused buffers are only valid inside it.
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/moxml/materializer.rb', line 93 def materialize_fields(node, buffers = Buffers.new, &block) raise ArgumentError, "materialize_fields requires a block" unless block adapter = node.context.config.adapter if adapter.bulk_materialize? && adapter.materialize_fields(node.native, buffers, &block) return end walk_fields(node, 0, buffers, &block) end |
.walk_fields(node, depth, buffers, &block) ⇒ Object
Generic post-order walk over the wrapper tree. Works on every adapter; the fast bulk path exists where the engine offers one.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/moxml/materializer.rb', line 107 def walk_fields(node, depth, buffers, &block) case node when Element node.children.each { |child| walk_fields(child, depth + 1, buffers, &block) } fill_element_buffers(node, buffers) ns = node.namespace yield(:element, node.name, node.namespace_prefix, ns&.uri, buffers.namespaces, buffers.attributes, nil, depth) when Text yield(:text, nil, nil, nil, EMPTY_ATTRIBUTES, EMPTY_ATTRIBUTES, node.content, depth) when Cdata yield(:cdata, nil, nil, nil, EMPTY_ATTRIBUTES, EMPTY_ATTRIBUTES, node.content, depth) when Comment yield(:comment, nil, nil, nil, EMPTY_ATTRIBUTES, EMPTY_ATTRIBUTES, node.content, depth) when ProcessingInstruction yield(:processing_instruction, node.target, nil, nil, EMPTY_ATTRIBUTES, EMPTY_ATTRIBUTES, node.content, depth) when EntityReference yield(:entity_reference, node.name, nil, nil, EMPTY_ATTRIBUTES, EMPTY_ATTRIBUTES, "&#{node.name};", depth) end end |