Module: Leptris::XML::SAX::DomDispatch

Defined in:
lib/leptris/xml/sax/dom_dispatch.rb

Overview

DOM-backed SAX dispatch (leptris-ruby#95): the engine's streaming attribute buffer corrupts leading attribute pairs when several nested ancestor levels each carry attributes — the callback transport, the recorder's packed arena, AND the pull parser all deliver the loss on the issue fixture (verified), while the DOM parser reads the same bytes correctly. Until the engine fix lands, SAX::Parser delivers from a DOM parse: same call shapes as the streaming transports — attach-only-overridden kinds, one-argument start_element arity, QName element names ("p:a"), UTF-8 strings, PI-data normalization.

The walk is lean by construction: elements wrap (identity, name and prefix memos); every other child kind is read straight from its pointer — content getters per node type, never a wrapper.

Constant Summary collapse

NODE =
Leptris::XML::FFI
ELEMENT =
Leptris::XML::FFI::NODE_ELEMENT
TEXT =
Leptris::XML::FFI::NODE_TEXT
COMMENT =
Leptris::XML::FFI::NODE_COMMENT
CDATA =
Leptris::XML::FFI::NODE_CDATA
PI =
Leptris::XML::FFI::NODE_PI

Class Method Summary collapse

Class Method Details

.attr_pairs(c_ptr) ⇒ Object

[name, value] pairs in source order, read straight from the attribute chain — no Attr objects, no hash.



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 129

def attr_pairs(c_ptr)
  pairs = []
  attr = Leptris::XML::FFI.leptris_element_first_attribute(c_ptr)
  until attr.nil? || attr.null?
    pairs << [utf8(Leptris::XML::FFI.leptris_attribute_get_name(attr)),
              utf8(Leptris::XML::FFI.leptris_attribute_get_value(
                c_ptr, attr))]
    attr = Leptris::XML::FFI.leptris_attribute_next(attr)
  end
  pairs
end

.declared_pairs(element) ⇒ Object

start_element's pairs with xmlns declarations carried — the engine's streaming contract reports declarations among the attribute pairs at their byte positions. Since libleptris 1.9.18 (upstream #635, closing leptris-ruby#99's documented difference), the raw qname-ordered list reproduces the exact interleave.



123
124
125
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 123

def declared_pairs(element)
  Leptris::XML::FFI.fetch_attributes_raw(element.c_ptr)
end

.dispatch(handler, dispatched, doc) ⇒ Object



29
30
31
32
33
34
35
36
37
38
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 29

def dispatch(handler, dispatched, doc)
  handler.start_document if dispatched[:start_document]
  doc_node = doc.node
  if doc_node
    visit_children(doc_node.c_ptr, doc, handler, dispatched,
                   element_parent: false)
  end
  handler.end_document if dispatched[:end_document]
  self
end

.parse(handler, dispatched, string) ⇒ Object



20
21
22
23
24
25
26
27
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 20

def parse(handler, dispatched, string)
  doc = Leptris::XML::Document.parse(string)
  begin
    dispatch(handler, dispatched, doc)
  ensure
    doc.free
  end
end

.utf8(str) ⇒ Object



141
142
143
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 141

def utf8(str)
  str.nil? ? nil : str.force_encoding(Encoding::UTF_8)
end

.visit_children(c_ptr, doc, handler, dispatched, element_parent: true) ⇒ Object



47
48
49
50
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
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 47

def visit_children(c_ptr, doc, handler, dispatched, element_parent: true)
  wants_text_kinds = dispatched[:characters] ||
                     dispatched[:comment] ||
                     dispatched[:cdata] || dispatched[:pi]
  if element_parent && !wants_text_kinds
    # Element-only handlers under an element: the element batch
    # hands out element pointers with no per-child get_type
    # (round XXIII's path) — text/comments/PIs are never fetched.
    # (The document node is not an element handle — the typed
    # walk serves it.)
    Leptris::XML::FFI.fetch_element_children(c_ptr).each do |child_ptr|
      visit_element(
        Leptris::XML::Node.wrap(child_ptr, doc, node_type: ELEMENT),
        handler, dispatched)
    end
    return
  end
  pointers, kinds = Leptris::XML::FFI.fetch_children(c_ptr)
  pointers.each_with_index do |child_ptr, i|
    case kinds[i]
    when ELEMENT
      visit_element(
        Leptris::XML::Node.wrap(child_ptr, doc, node_type: ELEMENT),
        handler, dispatched)
    when TEXT
      if dispatched[:characters]
        handler.characters(utf8(NODE.leptris_text_node_get_content(child_ptr)))
      end
    when CDATA
      if dispatched[:cdata]
        handler.cdata_block(utf8(NODE.leptris_cdata_node_get_content(child_ptr)))
      end
    when COMMENT
      if dispatched[:comment]
        handler.comment(utf8(NODE.leptris_comment_node_get_content(child_ptr)))
      end
    when PI
      if dispatched[:pi]
        handler.processing_instruction(
          utf8(NODE.leptris_pi_node_get_target(child_ptr)),
          Leptris::XML::FFI.read_pi_data(
            utf8(NODE.leptris_pi_node_get_data(child_ptr))))
      end
    end
  end
end

.visit_element(element, handler, dispatched) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/leptris/xml/sax/dom_dispatch.rb', line 94

def visit_element(element, handler, dispatched)
  qname = element.prefix ? "#{element.prefix}:#{element.name}" : element.name
  if dispatched[:start_prefix]
    element.namespace_definitions.each do |ns|
      handler.start_prefix_mapping(ns.prefix.to_s, ns.href.to_s)
    end
  end
  if dispatched[:start_element]
    if dispatched[:start_element] == :one_arg
      handler.start_element(qname)
    else
      handler.start_element(qname, declared_pairs(element))
    end
  end
  visit_children(element.c_ptr, element.document, handler, dispatched)
  handler.end_element(qname) if dispatched[:end_element]
  if dispatched[:end_prefix]
    element.namespace_definitions.reverse_each do |ns|
      handler.end_prefix_mapping(ns.prefix.to_s)
    end
  end
end