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)
-
#children ⇒ Object
The document's children, via the document node: prolog comments/PIs, the root element, epilog comments/PIs, in document order (Nokogiri-parity shape).
-
#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
-
#node ⇒ Object
The document node — navigation head over the whole tree chain [prolog comments/PIs, root element, epilog comments/PIs] in document order (libleptris 1.9.7, upstream #580: the libxml2 model; XPath /comment() and //processing-instruction() see the document-level nodes).
-
#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.
306
307
308
309
310
311
312
|
# File 'lib/leptris/xml/document.rb', line 306
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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
# File 'lib/leptris/xml/document.rb', line 255
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
|
#children ⇒ Object
The document's children, via the document node: prolog
comments/PIs, the root element, epilog comments/PIs, in
document order (Nokogiri-parity shape).
169
170
171
172
|
# File 'lib/leptris/xml/document.rb', line 169
def children
doc_node = node
doc_node ? doc_node.children : Leptris::XML::NodeSet.new(self, [])
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.
338
339
340
341
342
343
344
345
346
347
|
# File 'lib/leptris/xml/document.rb', line 338
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
204
205
206
207
208
|
# File 'lib/leptris/xml/document.rb', line 204
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
|
198
199
200
201
202
|
# File 'lib/leptris/xml/document.rb', line 198
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
186
187
188
189
190
|
# File 'lib/leptris/xml/document.rb', line 186
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
210
211
212
213
214
|
# File 'lib/leptris/xml/document.rb', line 210
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
192
193
194
195
196
|
# File 'lib/leptris/xml/document.rb', line 192
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
227
228
229
230
231
|
# File 'lib/leptris/xml/document.rb', line 227
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
372
|
# File 'lib/leptris/xml/document.rb', line 372
def document; self; end
|
#dup ⇒ Object
Also known as:
clone
220
221
222
223
224
|
# File 'lib/leptris/xml/document.rb', line 220
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
373
374
375
376
|
# File 'lib/leptris/xml/document.rb', line 373
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
272
273
274
275
276
277
278
|
# File 'lib/leptris/xml/document.rb', line 272
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.
330
331
332
|
# File 'lib/leptris/xml/document.rb', line 330
def freed?
@freed.state == :freed || @c_ptr.nil?
end
|
#last_error ⇒ Object
The most recent error recorded against this document, or nil.
366
367
368
369
|
# File 'lib/leptris/xml/document.rb', line 366
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.
352
353
354
355
356
357
358
359
360
361
362
363
|
# File 'lib/leptris/xml/document.rb', line 352
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
371
|
# File 'lib/leptris/xml/document.rb', line 371
def name; "document"; end
|
#node ⇒ Object
The document node — navigation head over the whole tree chain
[prolog comments/PIs, root element, epilog comments/PIs] in
document order (libleptris 1.9.7, upstream #580: the libxml2
model; XPath /comment() and //processing-instruction() see the
document-level nodes). A stable, document-owned singleton —
Node.wrap's cache keeps the returned wrapper identical.
#processing_instructions ⇒ Object
Document-level processing instructions (not tree nodes):
an array of [target, data] pairs in document order.
293
294
295
296
297
298
299
300
301
302
303
|
# File 'lib/leptris/xml/document.rb', line 293
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.
318
319
320
321
322
|
# File 'lib/leptris/xml/document.rb', line 318
def readonly!
Leptris::XML::FFI.leptris_document_freeze(@c_ptr)
@readonly = true
self
end
|
#readonly? ⇒ Boolean
324
325
326
|
# File 'lib/leptris/xml/document.rb', line 324
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
244
245
246
247
248
249
250
251
252
253
|
# File 'lib/leptris/xml/document.rb', line 244
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
234
235
236
237
238
239
240
|
# File 'lib/leptris/xml/document.rb', line 234
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
|