Class: Leptris::XML::Iterparse

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

Overview

Incremental tree iteration (libleptris v1.6.0): yields each completed TOP-LEVEL child element of the document root as it parses. The previous element's whole subtree is released when the next is produced — memory stays bounded by the largest subtree, not the document.

Leptris::XML::Iterparse.parse(huge_xml) do |element|
  process(element)   # subtree valid until the next iteration
end

v2 (libleptris 1.9.4, #586): an explicit yield mode — :top_level (v1: the root's completed children) or :full_document (every element in document-order completion — post-order, child before parent, subtree attached) — plus namespace resolution on the last yielded element and an error channel for truncated input:

it = Leptris::XML::Iterparse.parse(xml, mode: :full_document)
it.run do |el|
  it.namespace_uri("p")   # in scope on el, the last yielded
end
it.free

Leptris::XML::Iterparse.parse(xml) { |el| ... }.error
# => nil, or the parse failure (readable after the run: the
#    block form frees the iterator on return, snapshotting it)

The yielded elements have no parent Document; #document returns nil and they must not outlive the iteration.

Constant Summary collapse

MODES =
{
  top_level: Leptris::XML::FFI::ITERPARSE_TOP_LEVEL,
  full_document: Leptris::XML::FFI::ITERPARSE_FULL_DOCUMENT,
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handle) ⇒ Iterparse

Returns a new instance of Iterparse.



76
77
78
79
# File 'lib/leptris/xml/iterparse.rb', line 76

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

Class Method Details

.parse(xml_or_io, mode: :top_level, &block) ⇒ Object

NOTE: an IO argument is read fully into memory before the C iterator starts — the bounded-memory path is parse_file (C-side file streaming). The C API takes one (xml, len) buffer.



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/leptris/xml/iterparse.rb', line 43

def self.parse(xml_or_io, mode: :top_level, &block)
  xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
  iterator = new(Leptris::XML::FFI.leptris_iterparse_new_ex(
    xml, xml.bytesize, mode_code(mode)))
  return iterator unless block
  begin
    iterator.run(&block)
  ensure
    iterator.free
  end
  iterator
end

.parse_file(path, mode: :top_level, &block) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/leptris/xml/iterparse.rb', line 56

def self.parse_file(path, mode: :top_level, &block)
  iterator = new(Leptris::XML::FFI.leptris_iterparse_new_file_ex(
    path, mode_code(mode)))
  return iterator unless block
  begin
    iterator.run(&block)
  ensure
    iterator.free
  end
  iterator
end

Instance Method Details

#errorObject

The parse error for malformed/truncated input, or nil when the document is well-formed and exhausted. Yielded elements stop at the failure point; #free snapshots the message so this stays readable after the block form of .parse returns.



117
118
119
# File 'lib/leptris/xml/iterparse.rb', line 117

def error
  @handle ? live_error : @last_error
end

#freeObject

Releases the C iterator. Safe to call twice.



122
123
124
125
126
127
# File 'lib/leptris/xml/iterparse.rb', line 122

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

#namespace_countObject

Number of namespace bindings in the last yielded element's scope. Valid only while the iterator is live; 0 after #free.



108
109
110
111
# File 'lib/leptris/xml/iterparse.rb', line 108

def namespace_count
  return 0 if @handle.nil?
  Leptris::XML::FFI.leptris_iterparse_ns_count(@handle)
end

#namespace_uri(prefix) ⇒ Object

The URI bound to prefix ("" for the default namespace) in the last yielded element's in-scope namespace snapshot, or nil when unbound (v2, #586). Valid only while the iterator is live — call it inside the #run block; nil after #free.



101
102
103
104
# File 'lib/leptris/xml/iterparse.rb', line 101

def namespace_uri(prefix)
  return nil if @handle.nil?
  Leptris::XML::FFI.leptris_iterparse_ns_uri(@handle, prefix.to_s)
end

#runObject

Yields completed elements until the document is exhausted or the parse fails (see #error). Does not free the iterator — the block form of .parse/.parse_file does, or call #free explicitly.



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/leptris/xml/iterparse.rb', line 84

def run
  return enum_for(:run) unless block_given?
  while @handle
    ptr = Leptris::XML::FFI.leptris_iterparse_next(@handle)
    break if ptr.null?
    # document: nil — the subtree belongs to the iterator's pool,
    # not a long-lived Document, so wrapper caching is skipped and
    # #document is nil on the yielded elements.
    yield Leptris::XML::Node.wrap(ptr, nil)
  end
  self
end