Class: Leptris::XML::Pull::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/leptris/xml/pull.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handle) ⇒ Parser

Returns a new instance of Parser.



50
51
52
53
# File 'lib/leptris/xml/pull.rb', line 50

def initialize(handle)
  raise Leptris::XML::ParseError, "leptris_pull_new failed" if handle.null?
  @handle = handle
end

Class Method Details

.parse(xml_or_io) ⇒ Object



41
42
43
44
# File 'lib/leptris/xml/pull.rb', line 41

def self.parse(xml_or_io)
  xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
  new(Leptris::XML::FFI.leptris_pull_new(xml, xml.bytesize))
end

.parse_file(path) ⇒ Object



46
47
48
# File 'lib/leptris/xml/pull.rb', line 46

def self.parse_file(path)
  new(Leptris::XML::FFI.leptris_pull_new_file(path))
end

Instance Method Details

#eachObject



62
63
64
65
66
67
68
69
70
71
# File 'lib/leptris/xml/pull.rb', line 62

def each
  return enum_for(:each) unless block_given?
  loop do
    event = next_event
    break if event.nil?
    yield event
    break if event.type == :end_document || event.type == :error
  end
  self
end

#each_batch(max_count = 256) ⇒ Object

Bulk-delivers events (libleptris 1.9.7, upstream #589): ONE C call stages up to max_count events — the per-event dispatch that made streaming ~145x slower than a DOM parse collapses to amortized noise. Per the engine's protocol, attributes are captured only for the LAST start_element of each batch (earlier starts in the same batch carry nil attrs — segment boundaries fall where they fall); use #each when every start's attrs must be captured.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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/leptris/xml/pull.rb', line 81

def each_batch(max_count = 256)
  return enum_for(:each_batch, max_count) unless block_given?
  stride = Leptris::XML::FFI::PullEventStruct.size
  type_off = 0
  name_off = Leptris::XML::FFI::PullEventStruct.offset_of(:name)
  text_off = Leptris::XML::FFI::PullEventStruct.offset_of(:text)
  buffer = Leptris::XML::FFI.scratch_events(max_count)
  while (n = Leptris::XML::FFI.leptris_pull_next_batch(
           @handle, buffer, max_count)) > 0
    events = Array.new(n) do |i|
      base = i * stride
      name_ptr = buffer.get_pointer(base + name_off)
      text_ptr = buffer.get_pointer(base + text_off)
      type = TYPES[buffer.get_int(base + type_off)]
      name = name_ptr.null? ? nil :
        name_ptr.read_string.force_encoding(Encoding::UTF_8)
      # Staging-corruption guard (leptris/leptris#648): the C
      # batch's staging arena misplaces record strings once the
      # staged content crosses its block boundary (~185 bytes of
      # attribute value under nested attr-carrying ancestors) —
      # names come back empty, control-byte-laced, or
      # invalid-encoding, and unknown type codes surface as nil.
      # Element names can never look like that: fail loudly
      # instead of delivering garbage, and point at the cursor
      # path, which reads the same documents correctly.
      if type.nil? ||
         (name && (!name.valid_encoding? ||
                   name.match?(/[\x00-\x08\x0e-\x1f]/))) ||
         (%i[start_element end_element pi].include?(type) &&
          (name.nil? || name.empty?))
        raise Leptris::XML::Error,
          "pull batch staging corruption (leptris/leptris#648) — " \
          "use Parser#each (cursor) for this document"
      end
      text = text_ptr.null? ? nil :
        text_ptr.read_string.force_encoding(Encoding::UTF_8)
      text = Leptris::XML::FFI.read_pi_data(text) if type == :pi
      Event.new(type, name, text, nil)
    end
    # The attr mirror holds the batch's most recent start.
    last_start = nil
    events.each_with_index do |event, i|
      last_start = i if event.type == :start_element
    end
    events[last_start].attrs = capture_attrs if last_start
    events.each { |event| yield event }
    tail = events.last.type
    break if tail == :end_document || tail == :error
  end
  self
end

#freeObject

Releases the C parser. Safe to call twice.



56
57
58
59
60
# File 'lib/leptris/xml/pull.rb', line 56

def free
  return if @handle.nil?
  Leptris::XML::FFI.leptris_pull_free(@handle)
  @handle = nil
end

#next_eventObject

Advances the cursor and returns the next Event, or nil after the end of the document. Attribute values are captured during start_element because the C accessors are valid only for the most recently returned event.



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/leptris/xml/pull.rb', line 145

def next_event
  raw = Leptris::XML::FFI.leptris_pull_next(@handle)
  return nil if raw.null?
  type = TYPES[raw.get_int(0)]
  name_ptr = raw.get_pointer(NAME_OFFSET)
  text_ptr = raw.get_pointer(TEXT_OFFSET)
  attrs = type == :start_element ? capture_attrs : nil
  text = text_ptr.null? ? nil :
    text_ptr.read_string.force_encoding(Encoding::UTF_8)
  text = Leptris::XML::FFI.read_pi_data(text) if type == :pi
  Event.new(
    type,
    name_ptr.null? ? nil : name_ptr.read_string.force_encoding(Encoding::UTF_8),
    text, attrs
  )
end