Class: Canon::Xml::TreeBuilder
- Inherits:
-
Object
- Object
- Canon::Xml::TreeBuilder
- Defined in:
- lib/canon/xml/tree_builder.rb
Overview
The one place canon tree nodes are constructed from parsed XML.
Owns construction semantics: namespace scopes, attribute normalization, node kinds, and document-level ordering. Keep/strip decisions come from WhitespacePolicy (one home for all three policies); engine walks — the Nokogiri and moxml extractors in Xml::DataModel, the HTML walk in Html::DataModel — only map their engine's shapes onto this interface.
Attribute normalization: duplicate (name, namespace) pairs are invalid XML; engines expose them differently (libxml2 lists repeats, libleptris deduplicates), so the builder resolves them once — first occurrence wins.
Constant Summary collapse
- DEFAULT =
Stateless module: one shared instance serves every feed.
new- NO_ATTRIBUTES =
[].freeze
- XML_NAMESPACE_PREFIX =
"xml"- XML_NAMESPACE_URI =
"http://www.w3.org/XML/1998/namespace"
Instance Method Summary collapse
-
#add_document_children(root, children, document_element, skip_types = []) ⇒ Object
Attach document-level children (prolog/epilog PIs, comments, document-level text) to the canon root, in document order, skipping the document element and the given types.
-
#attach_namespace_scope(element, scope) ⇒ Object
Attach an in-scope scope to an element as namespace nodes.
- #comment(content) ⇒ Object
-
#element(name:, prefix: nil, namespace_uri: nil, attributes: NO_ATTRIBUTES, namespace_scope: nil) ⇒ Object
Build an element.
-
#merge_namespace_scope(inherited, declaration_pairs) ⇒ Object
In-scope namespace bindings: the element's own declarations shadow inherited ones; xml is prebound at the base.
- #processing_instruction(target, data) ⇒ Object
-
#text(content, keep:, original: content) ⇒ Object
Build a text node;
keepcomes from WhitespacePolicy (the caller knows the policy and parent context).
Instance Method Details
#add_document_children(root, children, document_element, skip_types = []) ⇒ Object
Attach document-level children (prolog/epilog PIs, comments, document-level text) to the canon root, in document order, skipping the document element and the given types. Yields each child to the caller's converter; nil results are dropped.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/canon/xml/tree_builder.rb', line 101 def add_document_children(root, children, document_element, skip_types = []) children.each do |child| next if child.equal?(document_element) next if skip_types.any? { |type| child.is_a?(type) } node = yield child root.add_child(node) if node end end |
#attach_namespace_scope(element, scope) ⇒ Object
Attach an in-scope scope to an element as namespace nodes.
39 40 41 42 43 44 45 46 |
# File 'lib/canon/xml/tree_builder.rb', line 39 def attach_namespace_scope(element, scope) scope.each do |prefix, uri| element.add_namespace(Nodes::NamespaceNode.new( prefix: prefix, uri: uri, )) end end |
#comment(content) ⇒ Object
89 90 91 |
# File 'lib/canon/xml/tree_builder.rb', line 89 def comment(content) Nodes::CommentNode.new(value: content) end |
#element(name:, prefix: nil, namespace_uri: nil, attributes: NO_ATTRIBUTES, namespace_scope: nil) ⇒ Object
Build an element. attributes are [name, value, namespace_uri,
prefix] pairs; namespace_scope is a merged scope (or nil for
no namespace nodes).
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/canon/xml/tree_builder.rb', line 51 def element(name:, prefix: nil, namespace_uri: nil, attributes: NO_ATTRIBUTES, namespace_scope: nil) element = Nodes::ElementNode.new( name: name, namespace_uri: namespace_uri, prefix: prefix, ) attach_namespace_scope(element, namespace_scope) if namespace_scope # Duplicate (name, namespace) pairs are invalid XML — first # occurrence wins. Attributes per element are few, so a pairwise # scan beats a per-element seen-hash on allocation (this is the # hot SAX path). attributes.each_with_index do |(attr_name, value, attr_namespace_uri, attr_prefix), index| duplicate = attributes[0...index].any? do |prior_name, _v, prior_ns, _p| prior_name == attr_name && prior_ns == attr_namespace_uri end next if duplicate element.add_attribute(Nodes::AttributeNode.new( name: attr_name, value: value, namespace_uri: attr_namespace_uri, prefix: attr_prefix, )) end element end |
#merge_namespace_scope(inherited, declaration_pairs) ⇒ Object
In-scope namespace bindings: the element's own declarations shadow inherited ones; xml is prebound at the base. Declaration pairs are [prefix-or-nil, uri]; nil and "" both mean the default namespace.
30 31 32 33 34 35 36 |
# File 'lib/canon/xml/tree_builder.rb', line 30 def merge_namespace_scope(inherited, declaration_pairs) scope = inherited ? inherited.dup : { XML_NAMESPACE_PREFIX => XML_NAMESPACE_URI } declaration_pairs.each do |prefix, uri| scope[prefix || ""] = uri end scope end |
#processing_instruction(target, data) ⇒ Object
93 94 95 |
# File 'lib/canon/xml/tree_builder.rb', line 93 def processing_instruction(target, data) Nodes::ProcessingInstructionNode.new(target: target, data: data) end |
#text(content, keep:, original: content) ⇒ Object
Build a text node; keep comes from WhitespacePolicy (the
caller knows the policy and parent context).
83 84 85 86 87 |
# File 'lib/canon/xml/tree_builder.rb', line 83 def text(content, keep:, original: content) return nil unless keep Nodes::TextNode.new(value: content, original: original) end |