Class: Leptris::XML::Document
- Inherits:
-
Object
- Object
- Leptris::XML::Document
show all
- Includes:
- Searchable
- Defined in:
- lib/leptris/xml/document.rb
Defined Under Namespace
Classes: Freed
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
-
#add_pi(target, data = "") ⇒ Object
Append a document-level processing instruction.
-
#canonicalize(version = Leptris::XML::FFI::C14N_1_0, inclusive_namespaces = nil, with_comments: false, exclusive: false, mode: nil) ⇒ Object
(also: #c14n)
-
#create_cdata(content) ⇒ Object
-
#create_comment(content) ⇒ Object
-
#create_element(name) ⇒ Object
-
#create_processing_instruction(target, data = "") ⇒ Object
-
#create_text_node(content) ⇒ Object
-
#doctype ⇒ Object
(also: #internal_subset)
-
#document ⇒ Object
-
#dup ⇒ Object
(also: #clone)
-
#encoding ⇒ Object
-
#exslt ⇒ Object
Enable the first-party EXSLT-style extension pack on this document: str:/set:/math: prefixed functions (replace, tokenize, split, concat, padding; distinct, intersection, difference, leading, trailing; max, min, abs, sqrt, power) as native C handlers.
-
#fragment(markup) ⇒ Object
-
#free ⇒ Object
-
#freed? ⇒ Boolean
True once #free has run (or the GC finalizer fired) — borrowed handles check this before dereferencing their c_ptr.
-
#initialize(c_ptr = nil, freed = Freed.new(:alive)) ⇒ Document
constructor
A new instance of Document.
-
#last_error ⇒ Object
The most recent error recorded against this document, or nil.
-
#last_error_position ⇒ Object
The thread-global last-failure [line, column] (1-based), or nil when no error is recorded — the position companion to Document#last_error; populated by recover parses.
-
#name ⇒ Object
-
#processing_instructions ⇒ Object
Document-level processing instructions (not tree nodes): an array of [target, data] pairs in document order.
-
#readonly! ⇒ Object
Marks the document read-only: tree mutations raise Leptris::XML::ReadOnlyError, and read paths memoize aggressively (names, content, children, attributes) since they can never go stale.
-
#readonly? ⇒ Boolean
-
#root ⇒ Object
-
#root=(element) ⇒ Object
Attach element as the document's root element.
-
#save(path, **opts) ⇒ Object
-
#to_xml(indent: 0, no_decl: false, encoding: nil) ⇒ Object
(also: #to_s, #serialize)
-
#wrapper_cache ⇒ Object
Methods included from Searchable
#at, #at_css, #at_xpath, #css, #search, wrap_xpath_result, #xpath
Constructor Details
#initialize(c_ptr = nil, freed = Freed.new(:alive)) ⇒ Document
Returns a new instance of Document.
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
# File 'lib/leptris/xml/document.rb', line 17
def initialize(c_ptr = nil, freed = Freed.new(:alive))
@c_ptr = c_ptr
@freed = freed
@readonly = false
end
|
Instance Attribute Details
#c_ptr ⇒ Object
Returns the value of attribute c_ptr.
6
7
8
|
# File 'lib/leptris/xml/document.rb', line 6
def c_ptr
@c_ptr
end
|
Class Method Details
.create ⇒ Object
Create an empty document (no root element) backed by its own memory
pool. Elements for the tree are created against it via
#create_element and friends, then attached with #root=.
98
99
100
101
102
103
|
# File 'lib/leptris/xml/document.rb', line 98
def self.create
raw = Leptris::XML::FFI.leptris_document_create
raise Leptris::XML::Error,
"leptris_document_create failed" if raw.null?
wrap(raw)
end
|
.parse(xml_or_io, options: nil, readonly: false, recover: false) ⇒ Object
43
44
45
46
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
|
# File 'lib/leptris/xml/document.rb', line 43
def self.parse(xml_or_io, options: nil, readonly: false, recover: false)
xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
if xml.empty?
raise Leptris::XML::ParseError, "empty input"
end
if options.nil?
options = Leptris::XML::ParseOptions.new(recover: recover)
elsif recover && !options.recover?
options = options | Leptris::XML::ParseOptions.new(recover: true)
elsif !options.is_a?(Leptris::XML::ParseOptions)
raise ArgumentError, "options must be a Leptris::XML::ParseOptions"
end
raw =
if options.struct_required?
options_struct = options.to_c_struct
Leptris::XML::FFI.leptris_parse_string_ex(
xml, xml.bytesize, options_struct.pointer, nil)
elsif options.flags.zero?
Leptris::XML::FFI.leptris_parse_string(xml, xml.bytesize, nil)
else
Leptris::XML::FFI.leptris_parse_string_flags(
xml, xml.bytesize, options.flags, nil)
end
if raw.null?
if options.recover?
raise Leptris::XML::Error,
"leptris_parse_string_ex returned NULL under recover"
end
raise Leptris::XML::ParseError,
"leptris_parse_string failed: " +
Leptris::XML::FFI.leptris_last_error.to_s
end
wrap(raw).tap { |doc| doc.readonly! if readonly }
end
|
.parse_file(path, readonly: false) ⇒ Object
85
86
87
88
89
90
91
92
93
|
# File 'lib/leptris/xml/document.rb', line 85
def self.parse_file(path, readonly: false)
raw = Leptris::XML::FFI.leptris_parse_file(path, nil)
if raw.null?
raise Leptris::XML::ParseError,
"leptris_parse_file failed: " +
Leptris::XML::FFI.leptris_last_error.to_s
end
wrap(raw).tap { |doc| doc.readonly! if readonly }
end
|
.wrap(raw_address) ⇒ Object
Convert a raw LeptrisDocument pointer into a Ruby Document with safe
GC lifetime management. The finalizer captures the raw address
integer (not the Document or Pointer object — those would prevent
GC) and shares a one-shot flag with the instance so explicit
#free and the GC finalizer can never both call
leptris_document_free on the same address.
111
112
113
114
115
116
117
118
|
# File 'lib/leptris/xml/document.rb', line 111
def self.wrap(raw_address)
addr = raw_address.is_a?(::FFI::Pointer) ? raw_address.address : raw_address
ptr = ::FFI::Pointer.new(addr)
freed = Freed.new(:alive)
doc = new(ptr, freed)
ObjectSpace.define_finalizer(doc, finalizer(addr, freed))
doc
end
|
Instance Method Details
#add_pi(target, data = "") ⇒ Object
Append a document-level processing instruction. Returns self.
269
270
271
272
273
274
|
# File 'lib/leptris/xml/document.rb', line 269
def add_pi(target, data = "")
witness = Leptris::XML::FFI.leptris_document_add_pi(
@c_ptr, target.to_s, data.to_s)
raise Leptris::XML::Error, "leptris_document_add_pi failed" if witness.null?
self
end
|
#canonicalize(version = Leptris::XML::FFI::C14N_1_0, inclusive_namespaces = nil, with_comments: false, exclusive: false, mode: nil) ⇒ Object
Also known as:
c14n
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
# File 'lib/leptris/xml/document.rb', line 217
def canonicalize(version = Leptris::XML::FFI::C14N_1_0,
inclusive_namespaces = nil,
with_comments: false,
exclusive: false,
mode: nil)
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
return "" if @c_ptr.nil?
resolved_mode = mode || (exclusive ? Leptris::XML::FFI::C14N_MODE_EXCLUSIVE
: Leptris::XML::FFI::C14N_MODE_CANONICAL)
Leptris::XML::Serialization.canonicalize(
Leptris::XML::FFI.method(:leptris_c14n_canonicalize_ex), @c_ptr,
version: version, mode: resolved_mode,
inclusive_namespaces: inclusive_namespaces,
with_comments: )
end
|
#create_cdata(content) ⇒ Object
166
167
168
169
170
|
# File 'lib/leptris/xml/document.rb', line 166
def create_cdata(content)
ptr = Leptris::XML::FFI.leptris_cdata_node_create(@c_ptr, content.to_s)
raise Leptris::XML::Error, "leptris_cdata_node_create failed" if ptr.null?
Leptris::XML::Node.wrap(ptr, self)
end
|
160
161
162
163
164
|
# File 'lib/leptris/xml/document.rb', line 160
def (content)
ptr = Leptris::XML::FFI.(@c_ptr, content.to_s)
raise Leptris::XML::Error, "leptris_comment_node_create failed" if ptr.null?
Leptris::XML::Node.wrap(ptr, self)
end
|
#create_element(name) ⇒ Object
148
149
150
151
152
|
# File 'lib/leptris/xml/document.rb', line 148
def create_element(name)
ptr = Leptris::XML::FFI.leptris_element_create(@c_ptr, name)
raise Leptris::XML::Error, "leptris_element_create failed" if ptr.null?
Leptris::XML::Node.wrap(ptr, self)
end
|
#create_processing_instruction(target, data = "") ⇒ Object
172
173
174
175
176
|
# File 'lib/leptris/xml/document.rb', line 172
def create_processing_instruction(target, data = "")
ptr = Leptris::XML::FFI.leptris_pi_node_create(@c_ptr, target.to_s, data.to_s)
raise Leptris::XML::Error, "leptris_pi_node_create failed" if ptr.null?
Leptris::XML::Node.wrap(ptr, self)
end
|
#create_text_node(content) ⇒ Object
154
155
156
157
158
|
# File 'lib/leptris/xml/document.rb', line 154
def create_text_node(content)
ptr = Leptris::XML::FFI.leptris_text_node_create(@c_ptr, content.to_s)
raise Leptris::XML::Error, "leptris_text_node_create failed" if ptr.null?
Leptris::XML::Node.wrap(ptr, self)
end
|
#doctype ⇒ Object
Also known as:
internal_subset
189
190
191
192
193
|
# File 'lib/leptris/xml/document.rb', line 189
def doctype
ptr = Leptris::XML::FFI.leptris_document_internal_subset(@c_ptr)
return nil if ptr.null?
Leptris::XML::DocType.new(ptr, self)
end
|
#document ⇒ Object
319
|
# File 'lib/leptris/xml/document.rb', line 319
def document; self; end
|
#dup ⇒ Object
Also known as:
clone
182
183
184
185
186
|
# File 'lib/leptris/xml/document.rb', line 182
def dup
raw = Leptris::XML::FFI.leptris_document_copy(@c_ptr)
raise Leptris::XML::Error, "leptris_document_copy failed" if raw.null?
self.class.wrap(raw)
end
|
#encoding ⇒ Object
320
321
322
323
|
# File 'lib/leptris/xml/document.rb', line 320
def encoding
return nil if @c_ptr.nil?
Leptris::XML::FFI.leptris_document_encoding(@c_ptr)
end
|
#exslt ⇒ Object
Enable the first-party EXSLT-style extension pack on this
document: str:/set:/math: prefixed functions (replace, tokenize,
split, concat, padding; distinct, intersection, difference,
leading, trailing; max, min, abs, sqrt, power) as native C
handlers. Returns self for chaining.
#fragment(markup) ⇒ Object
#free ⇒ Object
234
235
236
237
238
239
240
|
# File 'lib/leptris/xml/document.rb', line 234
def free
return if @freed.state == :freed
@freed.state = :freed
Leptris::XML::FFI.leptris_document_free(@c_ptr) unless @c_ptr.nil?
@c_ptr = nil
@wrapper_cache&.clear
end
|
#freed? ⇒ Boolean
True once #free has run (or the GC finalizer fired) — borrowed
handles check this before dereferencing their c_ptr.
292
293
294
|
# File 'lib/leptris/xml/document.rb', line 292
def freed?
@freed.state == :freed || @c_ptr.nil?
end
|
#last_error ⇒ Object
The most recent error recorded against this document, or nil.
313
314
315
316
|
# File 'lib/leptris/xml/document.rb', line 313
def last_error
msg = Leptris::XML::FFI.leptris_document_last_error(@c_ptr)
msg.nil? || msg.empty? ? nil : msg
end
|
#last_error_position ⇒ Object
The thread-global last-failure [line, column] (1-based), or nil
when no error is recorded — the position companion to
Document#last_error; populated by recover parses.
299
300
301
302
303
304
305
306
307
308
309
310
|
# File 'lib/leptris/xml/document.rb', line 299
def last_error_position
line = ::FFI::MemoryPointer.new(:int)
column = ::FFI::MemoryPointer.new(:int)
begin
Leptris::XML::FFI.leptris_last_error_position(line, column)
line.read_int.zero? && column.read_int.zero? ? nil :
[line.read_int, column.read_int]
ensure
line.free
column.free
end
end
|
#name ⇒ Object
318
|
# File 'lib/leptris/xml/document.rb', line 318
def name; "document"; end
|
#processing_instructions ⇒ Object
Document-level processing instructions (not tree nodes):
an array of [target, data] pairs in document order.
255
256
257
258
259
260
261
262
263
264
265
266
|
# File 'lib/leptris/xml/document.rb', line 255
def processing_instructions
if readonly? && instance_variable_defined?(:@processing_instructions)
return @processing_instructions
end
count = Leptris::XML::FFI.leptris_document_pi_count(@c_ptr)
result = count.times.map do |i|
[Leptris::XML::FFI.leptris_document_pi_target(@c_ptr, i),
Leptris::XML::FFI.leptris_document_pi_data(@c_ptr, i)]
end
@processing_instructions = result if readonly?
result
end
|
#readonly! ⇒ Object
Marks the document read-only: tree mutations raise
Leptris::XML::ReadOnlyError, and read paths memoize aggressively
(names, content, children, attributes) since they can never go
stale. The C document is also frozen (advisory upstream). One-way.
280
281
282
283
284
|
# File 'lib/leptris/xml/document.rb', line 280
def readonly!
Leptris::XML::FFI.leptris_document_freeze(@c_ptr)
@readonly = true
self
end
|
#readonly? ⇒ Boolean
286
287
288
|
# File 'lib/leptris/xml/document.rb', line 286
def readonly?
@readonly == true
end
|
#root=(element) ⇒ Object
Attach element as the document's root element. The element must
have been created against this document and must not already have
a parent. Any previous root is left detached (still owned by the
document's pool until #free).
#save(path, **opts) ⇒ Object
206
207
208
209
210
211
212
213
214
215
|
# File 'lib/leptris/xml/document.rb', line 206
def save(path, **opts)
opts_struct, _encoding_anchor = Leptris::XML::Serialization.build_options(
indent: opts.fetch(:indent, 0),
no_decl: opts.fetch(:no_decl, false),
encoding: opts[:encoding])
status = Leptris::XML::FFI.leptris_document_save_file(
@c_ptr, path, opts_struct.pointer)
Leptris::XML::FFI.check_status(status)
self
end
|
#to_xml(indent: 0, no_decl: false, encoding: nil) ⇒ Object
Also known as:
to_s, serialize
196
197
198
199
200
201
202
|
# File 'lib/leptris/xml/document.rb', line 196
def to_xml(indent: 0, no_decl: false, encoding: nil)
raise Leptris::XML::UseAfterFreeError if @freed.state == :freed
return "" if @c_ptr.nil?
Leptris::XML::Serialization.to_xml(
Leptris::XML::FFI.method(:leptris_document_serialize_into), @c_ptr,
indent: indent, no_decl: no_decl, encoding: encoding)
end
|
#wrapper_cache ⇒ Object
39
40
41
|
# File 'lib/leptris/xml/document.rb', line 39
def wrapper_cache
@wrapper_cache ||= {}
end
|