Class: Leptris::XML::SAX::Parser

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handler = Leptris::XML::SAX::Document.new, encoding = nil, streaming: true) ⇒ Parser

streaming: true (default, since 1.9.40) delivers through the engine transports — bulk/callback picked by override weight (leptris-ruby#95's attribute corruption is fixed upstream in libleptris 1.9.18, verified on the issue fixture). streaming: false delivers from a DOM parse (SAX::DomDispatch) — the correctness-first fallback kept for engines older than 1.9.18 and for consumers wanting the DOM view's shapes.



18
19
20
21
22
23
# File 'lib/leptris/xml/sax/parser.rb', line 18

def initialize(handler = Leptris::XML::SAX::Document.new,
               encoding = nil, streaming: true)
  @document = handler
  @encoding = encoding
  @streaming = streaming
end

Instance Attribute Details

#documentObject

Returns the value of attribute document.



9
10
11
# File 'lib/leptris/xml/sax/parser.rb', line 9

def document
  @document
end

#encodingObject (readonly)

Returns the value of attribute encoding.



9
10
11
# File 'lib/leptris/xml/sax/parser.rb', line 9

def encoding
  @encoding
end

Instance Method Details

#bulk_dispatch?Boolean

Returns:

  • (Boolean)


143
144
145
146
147
# File 'lib/leptris/xml/sax/parser.rb', line 143

def bulk_dispatch?
  dispatched_kinds.keys.sum do |kind|
    HOT_KIND_WEIGHTS.fetch(kind, 0.0)
  end >= DISPATCH_WEIGHT_THRESHOLD
end

#dispatched_kindsObject

The handler's overridden-kind map — the same decisions build_handler_struct makes, in dispatch form. Memoized with the handler struct; document= invalidates both.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/leptris/xml/sax/parser.rb', line 114

def dispatched_kinds
  @dispatched_kinds ||= begin
    kinds = {}
    {
      start_document: :start_document,
      end_document: :end_document,
      start_element: :start_element,
      end_element: :end_element,
      characters: :characters,
      comment: :comment,
      cdata_block: :cdata,
      processing_instruction: :pi,
      start_prefix_mapping: :start_prefix,
      end_prefix_mapping: :end_prefix,
      error: :error,
    }.each do |method_name, kind|
      next unless overridden?(method_name)
      kinds[kind] =
        if method_name == :start_element &&
            @document.method(:start_element).arity == 1
          :one_arg
        else
          true
        end
    end
    kinds
  end
end

#handler_structObject

The handler struct (eleven FFI::Function callbacks + the C struct) is built once per handler and reused across parses; the memoized struct is anchored against GC by the instance itself.



36
37
38
# File 'lib/leptris/xml/sax/parser.rb', line 36

def handler_struct
  @handler_struct ||= build_handler_struct
end

#parse(input) ⇒ Object

Parse a string, IO, or file path. Dispatches to parse_memory / parse_io / parse_file based on the argument type.



42
43
44
45
46
47
48
49
# File 'lib/leptris/xml/sax/parser.rb', line 42

def parse(input)
  case input
  when String                then parse_memory(input)
  when ->(x) { x.respond_to?(:read) } then parse_io(input)
  else
    raise ArgumentError, "SAX parser expects a String or IO, got #{input.class}"
  end
end

#parse_file(path) ⇒ Object



179
180
181
182
183
184
185
# File 'lib/leptris/xml/sax/parser.rb', line 179

def parse_file(path)
  if @streaming
    File.open(path, "r") { |f| parse_io(f) }
  else
    parse_memory(File.read(path))
  end
end

#parse_io(io) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/leptris/xml/sax/parser.rb', line 149

def parse_io(io)
  unless @streaming
    parse_memory(io.read)
    return self
  end
  handler_struct = self.handler_struct
  parser_ptr = Leptris::XML::FFI.leptris_sax_parser_create(
    handler_struct.pointer, nil)
  if parser_ptr.null?
    raise Leptris::XML::Error, "leptris_sax_parser_create failed"
  end
  begin
    while (chunk = io.read(CHUNK_SIZE))
      rc = Leptris::XML::FFI.leptris_sax_parser_feed(
        parser_ptr, chunk, chunk.bytesize, 0)
      if rc != 0
        raise Leptris::XML::ParseError, "leptris_sax_parser_feed failed (rc=#{rc})"
      end
    end
    # Final flush
    rc = Leptris::XML::FFI.leptris_sax_parser_feed(parser_ptr, "", 0, 1)
    if rc != 0
      raise Leptris::XML::ParseError, "leptris_sax_parser_feed (final) failed (rc=#{rc})"
    end
  ensure
    Leptris::XML::FFI.leptris_sax_parser_free(parser_ptr)
  end
  self
end

#parse_memory(string) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/leptris/xml/sax/parser.rb', line 51

def parse_memory(string)
  string = string.dup.force_encoding("UTF-8")
  unless @streaming
    Leptris::XML::SAX::DomDispatch.parse(
      @document, dispatched_kinds, string)
    return self
  end
  if bulk_dispatch?
    parse_memory_bulk(string)
  else
    handler_struct = self.handler_struct
    rc = Leptris::XML::FFI.leptris_sax_parse(
      string, string.bytesize, handler_struct.pointer, nil)
    if rc != 0
      raise Leptris::XML::ParseError,
        "leptris_sax_parse failed (rc=#{rc})"
    end
  end
  self
end

#parse_memory_bulk(string) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/leptris/xml/sax/parser.rb', line 96

def parse_memory_bulk(string)
  recorder = Leptris::XML::SAX::Recorder.open
  begin
    rc = recorder.feed(string, final: true)
    recorder.dispatch(@document, dispatched_kinds)
    if rc != 0
      raise Leptris::XML::ParseError,
        "leptris_sax_parse failed (rc=#{rc})"
    end
  ensure
    recorder.free
  end
  self
end