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.
-
#advance_version ⇒ Object
Called by the mutation gates (Node#ensure_writable!, root=, add_pi) — every memo stamped with an older version discards.
-
#canonicalize(version = Leptris::XML::FFI::C14N_1_0, inclusive_namespaces = nil, with_comments: false, exclusive: false, mode: nil) ⇒ Object
(also: #c14n)
-
#comments ⇒ Object
Document-level comments — parsed <!-- ...
-
#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)
-
#version ⇒ Object
Mutation version: advanced by every data mutation (via Node#ensure_writable!, root=, add_pi).
-
#wrapper_cache ⇒ Object
Methods included from Searchable
#at, #at_css, #at_xpath, #css, #search, wrap_xpath_first_result, wrap_xpath_result, #xpath
Constructor Details
#initialize(c_ptr = nil, freed = Freed.new(:alive)) ⇒ Document
Returns a new instance of Document.
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# File 'lib/leptris/xml/document.rb', line 33
def initialize(c_ptr = nil, freed = Freed.new(:alive))
@c_ptr = c_ptr
@freed = freed
@readonly = false
@version = 0
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=.
115
116
117
118
119
120
|
# File 'lib/leptris/xml/document.rb', line 115
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
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
93
94
95
96
97
98
99
100
|
# File 'lib/leptris/xml/document.rb', line 60
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
102
103
104
105
106
107
108
109
110
|
# File 'lib/leptris/xml/document.rb', line 102
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.
128
129
130
131
132
133
134
135
|
# File 'lib/leptris/xml/document.rb', line 128
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.
286
287
288
289
290
291
292
|
# File 'lib/leptris/xml/document.rb', line 286
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?
@version += 1
self
end
|
#advance_version ⇒ Object
Called by the mutation gates (Node#ensure_writable!, root=,
add_pi) — every memo stamped with an older version discards.
29
30
31
|
# File 'lib/leptris/xml/document.rb', line 29
def advance_version
@version += 1
end
|
#canonicalize(version = Leptris::XML::FFI::C14N_1_0, inclusive_namespaces = nil, with_comments: false, exclusive: false, mode: nil) ⇒ Object
Also known as:
c14n
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
|
# File 'lib/leptris/xml/document.rb', line 235
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
|
Document-level comments — parsed outside the
root element, prolog then epilog, in document order (the
companion reader to #processing_instructions; libleptris
1.9.3, upstream #578). Version-memoized like the PI list.
318
319
320
321
322
323
324
325
326
327
|
# File 'lib/leptris/xml/document.rb', line 318
def
return @comments if @comments_version == @version
count = Leptris::XML::FFI.(@c_ptr)
result = Array.new(count) do |i|
Leptris::XML::FFI.(@c_ptr, i)
end
@comments = result
@comments_version = @version
result
end
|
#create_cdata(content) ⇒ Object
184
185
186
187
188
|
# File 'lib/leptris/xml/document.rb', line 184
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
|
178
179
180
181
182
|
# File 'lib/leptris/xml/document.rb', line 178
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
166
167
168
169
170
|
# File 'lib/leptris/xml/document.rb', line 166
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
190
191
192
193
194
|
# File 'lib/leptris/xml/document.rb', line 190
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
172
173
174
175
176
|
# File 'lib/leptris/xml/document.rb', line 172
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
207
208
209
210
211
|
# File 'lib/leptris/xml/document.rb', line 207
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
352
|
# File 'lib/leptris/xml/document.rb', line 352
def document; self; end
|
#dup ⇒ Object
Also known as:
clone
200
201
202
203
204
|
# File 'lib/leptris/xml/document.rb', line 200
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
353
354
355
356
|
# File 'lib/leptris/xml/document.rb', line 353
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
252
253
254
255
256
257
258
|
# File 'lib/leptris/xml/document.rb', line 252
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.
310
311
312
|
# File 'lib/leptris/xml/document.rb', line 310
def freed?
@freed.state == :freed || @c_ptr.nil?
end
|
#last_error ⇒ Object
The most recent error recorded against this document, or nil.
346
347
348
349
|
# File 'lib/leptris/xml/document.rb', line 346
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.
332
333
334
335
336
337
338
339
340
341
342
343
|
# File 'lib/leptris/xml/document.rb', line 332
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
351
|
# File 'lib/leptris/xml/document.rb', line 351
def name; "document"; end
|
#processing_instructions ⇒ Object
Document-level processing instructions (not tree nodes):
an array of [target, data] pairs in document order.
273
274
275
276
277
278
279
280
281
282
283
|
# File 'lib/leptris/xml/document.rb', line 273
def processing_instructions
return @processing_instructions if @pi_version == @version
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
@pi_version = @version
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.
298
299
300
301
302
|
# File 'lib/leptris/xml/document.rb', line 298
def readonly!
Leptris::XML::FFI.leptris_document_freeze(@c_ptr)
@readonly = true
self
end
|
#readonly? ⇒ Boolean
304
305
306
|
# File 'lib/leptris/xml/document.rb', line 304
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
224
225
226
227
228
229
230
231
232
233
|
# File 'lib/leptris/xml/document.rb', line 224
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
214
215
216
217
218
219
220
|
# File 'lib/leptris/xml/document.rb', line 214
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
|
#version ⇒ Object
Mutation version: advanced by every data mutation (via
Node#ensure_writable!, root=, add_pi). Node memos stamp the
version they were computed under and recompute after any bump —
the invalidation that makes WRITABLE-document memoization sound.
Readonly documents never advance it, so their memos are forever
valid (ADR 0003 semantics, unchanged).
23
24
25
|
# File 'lib/leptris/xml/document.rb', line 23
def version
@version
end
|
#wrapper_cache ⇒ Object
56
57
58
|
# File 'lib/leptris/xml/document.rb', line 56
def wrapper_cache
@wrapper_cache ||= {}
end
|