taurus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +518 -0
- data/CLAUDE.md +104 -0
- data/LICENSE.md +33 -0
- data/README.adoc +1529 -0
- data/Rakefile +7 -0
- data/TODO.impl/01-architecture.md +217 -0
- data/TODO.impl/02-ffi-declarations.md +236 -0
- data/TODO.impl/03-document-node-element-nodeset.md +382 -0
- data/TODO.impl/04-sax-parser.md +203 -0
- data/TODO.impl/05-serialize-c14n-memory-specs-css.md +276 -0
- data/benchmark/README.md +168 -0
- data/benchmark/taurus_vs_nokogiri.rb +105 -0
- data/docs/ARCHITECTURE.adoc +559 -0
- data/docs/BUILD.md +395 -0
- data/docs/ERROR_MESSAGES.md +458 -0
- data/docs/FFI_ARCHITECTURE.md +439 -0
- data/docs/FUTURE_VISION.md +303 -0
- data/docs/GITHUB_ACTIONS.md +293 -0
- data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +459 -0
- data/docs/PERFORMANCE.adoc +668 -0
- data/docs/PERFORMANCE.md +448 -0
- data/docs/RELEASE_NOTES_v1.0.0.md +515 -0
- data/docs/XPATH_SPEC_COMPLIANCE.md +298 -0
- data/docs/completion/taurus.bash +86 -0
- data/docs/completion/taurus.zsh +74 -0
- data/docs/man/taurus-format.1 +227 -0
- data/docs/man/taurus-parse.1 +178 -0
- data/docs/man/taurus-xpath.1 +312 -0
- data/docs/man/taurus.1 +160 -0
- data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +217 -0
- data/docs/v0.9.0_RELEASE_SUMMARY.md +281 -0
- data/docs/v1.0.0_CONTINUATION_PLAN.md +172 -0
- data/docs/v1.0.0_CONTINUATION_PROMPT.md +382 -0
- data/docs/v1.0.0_SESSION_6_CONTINUATION.md +434 -0
- data/docs/v1.0.0_SESSION_6_PROMPT.md +231 -0
- data/docs/v1.0.0_STATUS_TRACKER.md +224 -0
- data/docs/v1.1.0_CONTINUATION_PLAN.md +299 -0
- data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +201 -0
- data/docs/v1.1.0_SESSION_3_PROMPT.md +223 -0
- data/docs/v1.1.0_STATUS_TRACKER.md +355 -0
- data/docs/xml-performance.adoc +115 -0
- data/docs/xpath-performance.adoc +379 -0
- data/lib/taurus/version.rb +5 -0
- data/lib/taurus/xml/attr.rb +43 -0
- data/lib/taurus/xml/c14n.rb +23 -0
- data/lib/taurus/xml/cdata.rb +16 -0
- data/lib/taurus/xml/comment.rb +16 -0
- data/lib/taurus/xml/css_to_xpath.rb +177 -0
- data/lib/taurus/xml/doc_type.rb +54 -0
- data/lib/taurus/xml/document.rb +202 -0
- data/lib/taurus/xml/document_fragment.rb +42 -0
- data/lib/taurus/xml/element.rb +278 -0
- data/lib/taurus/xml/ffi.rb +420 -0
- data/lib/taurus/xml/namespace.rb +43 -0
- data/lib/taurus/xml/node.rb +221 -0
- data/lib/taurus/xml/node_set.rb +143 -0
- data/lib/taurus/xml/parse_options.rb +19 -0
- data/lib/taurus/xml/processing_instruction.rb +26 -0
- data/lib/taurus/xml/sax/document.rb +45 -0
- data/lib/taurus/xml/sax/parser.rb +148 -0
- data/lib/taurus/xml/sax.rb +12 -0
- data/lib/taurus/xml/searchable.rb +93 -0
- data/lib/taurus/xml/text.rb +16 -0
- data/lib/taurus/xml.rb +29 -0
- data/lib/taurus.rb +7 -0
- data/taurus.gemspec +42 -0
- metadata +157 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
class Taurus::XML::Document
|
|
6
|
+
attr_reader :c_ptr, :wrapper_cache
|
|
7
|
+
|
|
8
|
+
# @api private
|
|
9
|
+
# Internal flag container shared between the Document instance and its
|
|
10
|
+
# GC finalizer. Using a one-element Array because Procs close over
|
|
11
|
+
# variables by reference — mutating freed[0] is visible from both
|
|
12
|
+
# the explicit `free` path and the finalizer. This eliminates the
|
|
13
|
+
# double-free that FFI::AutoPointer's release proc caused when
|
|
14
|
+
# `Document#free` was called explicitly and then GC ran.
|
|
15
|
+
Freed = Struct.new(:state) # state: :alive | :freed
|
|
16
|
+
|
|
17
|
+
def initialize(c_ptr = nil, freed = Freed.new(:alive))
|
|
18
|
+
@c_ptr = c_ptr
|
|
19
|
+
@freed = freed
|
|
20
|
+
# Per-document weak-ref cache for Node wrappers, keyed on c_ptr
|
|
21
|
+
# address. Eliminates re-allocation when the same node is accessed
|
|
22
|
+
# repeatedly (e.g. via children, siblings, multiple xpath calls).
|
|
23
|
+
# Dies with the Document — no stale entries pointing at freed memory.
|
|
24
|
+
@wrapper_cache = ObjectSpace::WeakMap.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.parse(xml_or_io)
|
|
28
|
+
xml = xml_or_io.respond_to?(:read) ? xml_or_io.read : xml_or_io.to_s
|
|
29
|
+
if xml.empty?
|
|
30
|
+
raise Taurus::XML::ParseError, "empty input"
|
|
31
|
+
end
|
|
32
|
+
status_ptr = ::FFI::MemoryPointer.new(:int)
|
|
33
|
+
raw = Taurus::XML::FFI.taurus_parse_string(xml, xml.bytesize, status_ptr)
|
|
34
|
+
if raw.null?
|
|
35
|
+
status = status_ptr.read_int
|
|
36
|
+
raise Taurus::XML::ParseError,
|
|
37
|
+
"taurus_parse_string failed (status=#{status})"
|
|
38
|
+
end
|
|
39
|
+
wrap(raw)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.parse_file(path)
|
|
43
|
+
status_ptr = ::FFI::MemoryPointer.new(:int)
|
|
44
|
+
raw = Taurus::XML::FFI.taurus_parse_file(path, status_ptr)
|
|
45
|
+
if raw.null?
|
|
46
|
+
status = status_ptr.read_int
|
|
47
|
+
raise Taurus::XML::ParseError,
|
|
48
|
+
"taurus_parse_file failed (status=#{status})"
|
|
49
|
+
end
|
|
50
|
+
wrap(raw)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Convert a raw TaurusDocument pointer into a Ruby Document with safe
|
|
54
|
+
# GC lifetime management. The finalizer captures the raw address
|
|
55
|
+
# integer (not the Document or Pointer object — those would prevent
|
|
56
|
+
# GC) and shares a one-shot flag with the instance so explicit
|
|
57
|
+
# `#free` and the GC finalizer can never both call
|
|
58
|
+
# `taurus_document_free` on the same address.
|
|
59
|
+
def self.wrap(raw_address)
|
|
60
|
+
addr = raw_address.is_a?(::FFI::Pointer) ? raw_address.address : raw_address
|
|
61
|
+
ptr = ::FFI::Pointer.new(addr)
|
|
62
|
+
freed = Freed.new(:alive)
|
|
63
|
+
doc = new(ptr, freed)
|
|
64
|
+
ObjectSpace.define_finalizer(doc, finalizer(addr, freed))
|
|
65
|
+
doc
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.finalizer(address, freed)
|
|
69
|
+
proc do
|
|
70
|
+
next if freed.state == :freed
|
|
71
|
+
freed.state = :freed
|
|
72
|
+
Taurus::XML::FFI.taurus_document_free(::FFI::Pointer.new(address))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
private_class_method :finalizer
|
|
76
|
+
|
|
77
|
+
def root
|
|
78
|
+
raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
|
|
79
|
+
return nil if @c_ptr.nil?
|
|
80
|
+
ptr = Taurus::XML::FFI.taurus_document_root(@c_ptr)
|
|
81
|
+
return nil if ptr.null?
|
|
82
|
+
Taurus::XML::Element.new(ptr, self)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_element(name)
|
|
86
|
+
ptr = Taurus::XML::FFI.taurus_element_create(@c_ptr, name)
|
|
87
|
+
raise Taurus::XML::Error, "taurus_element_create failed" if ptr.null?
|
|
88
|
+
Taurus::XML::Element.new(ptr, self)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create_text_node(content)
|
|
92
|
+
ptr = Taurus::XML::FFI.taurus_text_node_create(@c_ptr, content.to_s)
|
|
93
|
+
raise Taurus::XML::Error, "taurus_text_node_create failed" if ptr.null?
|
|
94
|
+
Taurus::XML::Text.new(ptr, self)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def create_comment(content)
|
|
98
|
+
ptr = Taurus::XML::FFI.taurus_comment_node_create(@c_ptr, content.to_s)
|
|
99
|
+
raise Taurus::XML::Error, "taurus_comment_node_create failed" if ptr.null?
|
|
100
|
+
Taurus::XML::Comment.new(ptr, self)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def create_cdata(content)
|
|
104
|
+
ptr = Taurus::XML::FFI.taurus_cdata_node_create(@c_ptr, content.to_s)
|
|
105
|
+
raise Taurus::XML::Error, "taurus_cdata_node_create failed" if ptr.null?
|
|
106
|
+
Taurus::XML::CDATA.new(ptr, self)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def create_processing_instruction(target, data = "")
|
|
110
|
+
ptr = Taurus::XML::FFI.taurus_pi_node_create(@c_ptr, target.to_s, data.to_s)
|
|
111
|
+
raise Taurus::XML::Error, "taurus_pi_node_create failed" if ptr.null?
|
|
112
|
+
Taurus::XML::ProcessingInstruction.new(ptr, self)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def fragment(markup)
|
|
116
|
+
Taurus::XML::DocumentFragment.parse(markup, self)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def dup
|
|
120
|
+
raw = Taurus::XML::FFI.taurus_document_copy(@c_ptr)
|
|
121
|
+
raise Taurus::XML::Error, "taurus_document_copy failed" if raw.null?
|
|
122
|
+
self.class.wrap(raw)
|
|
123
|
+
end
|
|
124
|
+
alias_method :clone, :dup
|
|
125
|
+
|
|
126
|
+
def doctype
|
|
127
|
+
ptr = Taurus::XML::FFI.taurus_document_internal_subset(@c_ptr)
|
|
128
|
+
return nil if ptr.null?
|
|
129
|
+
Taurus::XML::DocType.new(ptr, self)
|
|
130
|
+
end
|
|
131
|
+
alias_method :internal_subset, :doctype
|
|
132
|
+
|
|
133
|
+
def to_xml(indent: 0, no_decl: false, encoding: nil)
|
|
134
|
+
raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
|
|
135
|
+
return "" if @c_ptr.nil?
|
|
136
|
+
opts, enc_ptr = build_serialize_options(indent: indent, no_decl: no_decl, encoding: encoding)
|
|
137
|
+
str_ptr = Taurus::XML::FFI.taurus_document_serialize(@c_ptr, opts.pointer)
|
|
138
|
+
return "" if str_ptr.null?
|
|
139
|
+
str_ptr.read_string.tap { |s| Taurus::XML::FFI.taurus_free_string(str_ptr) }
|
|
140
|
+
end
|
|
141
|
+
alias_method :to_s, :to_xml
|
|
142
|
+
alias_method :serialize, :to_xml
|
|
143
|
+
|
|
144
|
+
def save(path, **opts)
|
|
145
|
+
opts_struct, enc_ptr = build_serialize_options(
|
|
146
|
+
indent: opts.fetch(:indent, 0),
|
|
147
|
+
no_decl: opts.fetch(:no_decl, false),
|
|
148
|
+
encoding: opts[:encoding])
|
|
149
|
+
status = Taurus::XML::FFI.taurus_document_save_file(@c_ptr, path, opts_struct.pointer)
|
|
150
|
+
raise Taurus::XML::Error,
|
|
151
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
152
|
+
self
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def canonicalize(version = Taurus::XML::FFI::C14N_1_0,
|
|
156
|
+
inclusive_namespaces = nil,
|
|
157
|
+
with_comments: false,
|
|
158
|
+
exclusive: false,
|
|
159
|
+
mode: nil)
|
|
160
|
+
raise Taurus::XML::UseAfterFreeError if @freed.state == :freed
|
|
161
|
+
return "" if @c_ptr.nil?
|
|
162
|
+
resolved_mode = mode || (exclusive ? Taurus::XML::FFI::C14N_MODE_EXCLUSIVE
|
|
163
|
+
: Taurus::XML::FFI::C14N_MODE_CANONICAL)
|
|
164
|
+
ns_ptr, _anchor = Taurus::XML.c14n_build_ns_pointer(inclusive_namespaces)
|
|
165
|
+
flags = with_comments ? 1 : 0
|
|
166
|
+
str_ptr = Taurus::XML::FFI.taurus_c14n_canonicalize_ex(
|
|
167
|
+
@c_ptr, version, resolved_mode, ns_ptr, flags)
|
|
168
|
+
return "" if str_ptr.null?
|
|
169
|
+
str_ptr.read_string.tap { Taurus::XML::FFI.taurus_free_string(str_ptr) }
|
|
170
|
+
end
|
|
171
|
+
alias_method :c14n, :canonicalize
|
|
172
|
+
|
|
173
|
+
def free
|
|
174
|
+
return if @freed.state == :freed
|
|
175
|
+
@freed.state = :freed
|
|
176
|
+
Taurus::XML::FFI.taurus_document_free(@c_ptr) unless @c_ptr.nil?
|
|
177
|
+
@c_ptr = nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def name; "document"; end
|
|
181
|
+
def document; self; end
|
|
182
|
+
def encoding
|
|
183
|
+
return nil if @c_ptr.nil?
|
|
184
|
+
Taurus::XML::FFI.taurus_document_encoding(@c_ptr)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
private
|
|
188
|
+
|
|
189
|
+
def build_serialize_options(indent:, no_decl:, encoding:)
|
|
190
|
+
opts = Taurus::XML::FFI::SerializeOptions.new
|
|
191
|
+
opts[:indent] = indent.to_i
|
|
192
|
+
opts[:xml_declaration] = no_decl ? 0 : 1
|
|
193
|
+
enc_ptr = nil
|
|
194
|
+
if encoding
|
|
195
|
+
enc_ptr = ::FFI::MemoryPointer.from_string(encoding.to_s)
|
|
196
|
+
opts[:encoding] = enc_ptr
|
|
197
|
+
end
|
|
198
|
+
[opts, enc_ptr]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
include Taurus::XML::Searchable
|
|
202
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
|
|
5
|
+
# Wraps the synthetic "#document-fragment" element returned by
|
|
6
|
+
# taurus_parse_fragment. Children of this fragment are the parsed nodes;
|
|
7
|
+
# the fragment itself isn't part of any document tree but borrows its
|
|
8
|
+
# document's lifetime.
|
|
9
|
+
class Taurus::XML::DocumentFragment
|
|
10
|
+
attr_reader :document, :c_ptr
|
|
11
|
+
|
|
12
|
+
def initialize(document, c_ptr)
|
|
13
|
+
@document = document
|
|
14
|
+
@c_ptr = c_ptr
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.parse(xml, document)
|
|
18
|
+
status_ptr = ::FFI::MemoryPointer.new(:int)
|
|
19
|
+
raw = Taurus::XML::FFI.taurus_parse_fragment(
|
|
20
|
+
xml.to_s, xml.to_s.bytesize, document.c_ptr, status_ptr)
|
|
21
|
+
if raw.null?
|
|
22
|
+
raise Taurus::XML::ParseError,
|
|
23
|
+
"taurus_parse_fragment failed (status=#{status_ptr.read_int})"
|
|
24
|
+
end
|
|
25
|
+
new(document, raw)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def children
|
|
29
|
+
count = Taurus::XML::FFI.taurus_element_child_count(@c_ptr)
|
|
30
|
+
nodes = []
|
|
31
|
+
ptr = Taurus::XML::FFI.taurus_node_first_child(@c_ptr)
|
|
32
|
+
until ptr.nil? || ptr.null?
|
|
33
|
+
nodes << Taurus::XML::Node.wrap(ptr, @document)
|
|
34
|
+
ptr = Taurus::XML::FFI.taurus_node_next_sibling(ptr)
|
|
35
|
+
end
|
|
36
|
+
Taurus::XML::NodeSet.new(@document, nodes)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def name
|
|
40
|
+
"#document-fragment"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Taurus::XML::Element < Taurus::XML::Node
|
|
4
|
+
def name
|
|
5
|
+
Taurus::XML::FFI.taurus_element_name(@c_ptr)
|
|
6
|
+
end
|
|
7
|
+
alias_method :node_name, :name
|
|
8
|
+
|
|
9
|
+
def name=(new_name)
|
|
10
|
+
status = Taurus::XML::FFI.taurus_element_set_name(@c_ptr, new_name)
|
|
11
|
+
raise Taurus::XML::Error,
|
|
12
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
13
|
+
new_name
|
|
14
|
+
end
|
|
15
|
+
alias_method :node_name=, :name=
|
|
16
|
+
|
|
17
|
+
def content
|
|
18
|
+
Taurus::XML::FFI.taurus_element_text(@c_ptr)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def content=(new_content)
|
|
22
|
+
status = Taurus::XML::FFI.taurus_element_set_text(@c_ptr, new_content.to_s)
|
|
23
|
+
raise Taurus::XML::Error,
|
|
24
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
25
|
+
new_content
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def [](key)
|
|
29
|
+
Taurus::XML::FFI.taurus_element_attribute(@c_ptr, key.to_s)
|
|
30
|
+
end
|
|
31
|
+
alias_method :attr, :[]
|
|
32
|
+
alias_method :get_attribute, :[]
|
|
33
|
+
|
|
34
|
+
def []=(key, value)
|
|
35
|
+
status = Taurus::XML::FFI.taurus_element_set_attribute(@c_ptr, key.to_s, value.to_s)
|
|
36
|
+
raise Taurus::XML::Error,
|
|
37
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
alias_method :set_attribute, :[]=
|
|
41
|
+
|
|
42
|
+
def key?(name)
|
|
43
|
+
!Taurus::XML::FFI.taurus_element_attribute(@c_ptr, name.to_s).nil?
|
|
44
|
+
end
|
|
45
|
+
alias_method :has_attribute?, :key?
|
|
46
|
+
|
|
47
|
+
def remove_attribute(name)
|
|
48
|
+
status = Taurus::XML::FFI.taurus_element_remove_attribute(@c_ptr, name.to_s)
|
|
49
|
+
raise Taurus::XML::Error,
|
|
50
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
alias_method :delete, :remove_attribute
|
|
54
|
+
|
|
55
|
+
def keys
|
|
56
|
+
count = Taurus::XML::FFI.taurus_element_attribute_count(@c_ptr)
|
|
57
|
+
count.times.map { |i| Taurus::XML::FFI.taurus_element_attribute_name_at(@c_ptr, i) }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def values
|
|
61
|
+
count = Taurus::XML::FFI.taurus_element_attribute_count(@c_ptr)
|
|
62
|
+
count.times.map { |i| Taurus::XML::FFI.taurus_element_attribute_value_at(@c_ptr, i) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def attributes
|
|
66
|
+
count = Taurus::XML::FFI.taurus_element_attribute_count(@c_ptr)
|
|
67
|
+
result = {}
|
|
68
|
+
count.times do |i|
|
|
69
|
+
name = Taurus::XML::FFI.taurus_element_attribute_name_at(@c_ptr, i)
|
|
70
|
+
value = Taurus::XML::FFI.taurus_element_attribute_value_at(@c_ptr, i)
|
|
71
|
+
result[name] = Taurus::XML::Attr.new(name, value, self)
|
|
72
|
+
end
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def attribute_nodes
|
|
77
|
+
count = Taurus::XML::FFI.taurus_element_attribute_count(@c_ptr)
|
|
78
|
+
count.times.map do |i|
|
|
79
|
+
name = Taurus::XML::FFI.taurus_element_attribute_name_at(@c_ptr, i)
|
|
80
|
+
value = Taurus::XML::FFI.taurus_element_attribute_value_at(@c_ptr, i)
|
|
81
|
+
Taurus::XML::Attr.new(name, value, self)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def add_child(node)
|
|
86
|
+
status = Taurus::XML::FFI.taurus_element_append_child(@c_ptr, node.c_ptr)
|
|
87
|
+
raise Taurus::XML::Error,
|
|
88
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
89
|
+
node
|
|
90
|
+
end
|
|
91
|
+
alias_method :<<, :add_child
|
|
92
|
+
|
|
93
|
+
def prepend_child(node)
|
|
94
|
+
status = Taurus::XML::FFI.taurus_element_prepend_child(@c_ptr, node.c_ptr)
|
|
95
|
+
raise Taurus::XML::Error,
|
|
96
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
97
|
+
node
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def add_next_sibling(node)
|
|
101
|
+
status = Taurus::XML::FFI.taurus_element_insert_after(@c_ptr, node.c_ptr)
|
|
102
|
+
raise Taurus::XML::Error,
|
|
103
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
104
|
+
node
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def add_previous_sibling(node)
|
|
108
|
+
status = Taurus::XML::FFI.taurus_element_insert_before(@c_ptr, node.c_ptr)
|
|
109
|
+
raise Taurus::XML::Error,
|
|
110
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
111
|
+
node
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def remove_child(node)
|
|
115
|
+
status = Taurus::XML::FFI.taurus_element_remove_child(@c_ptr, node.c_ptr)
|
|
116
|
+
raise Taurus::XML::Error,
|
|
117
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
118
|
+
node
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def children=(node_or_nodes)
|
|
122
|
+
# Remove existing children, then attach the new ones in source order.
|
|
123
|
+
Taurus::XML::FFI.taurus_element_remove_children(@c_ptr)
|
|
124
|
+
Array(node_or_nodes).each { |n| add_child(n) }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Replace this element with +new_node+ in the parent's child list.
|
|
128
|
+
# +new_node+ must belong to the same document. Returns +new_node+.
|
|
129
|
+
def replace(new_node)
|
|
130
|
+
parent = self.parent
|
|
131
|
+
raise Taurus::XML::Error, "cannot replace a node with no parent" unless parent
|
|
132
|
+
add_next_sibling(new_node)
|
|
133
|
+
parent.remove_child(self)
|
|
134
|
+
new_node
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Like #replace but returns self for chaining.
|
|
138
|
+
def swap(new_node)
|
|
139
|
+
replace(new_node)
|
|
140
|
+
self
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Wrap this element in a new element parsed from +markup+ or a dup of
|
|
144
|
+
# +node+. The wrapper takes this element's place in the tree, and this
|
|
145
|
+
# element becomes its only child. Returns self for chaining.
|
|
146
|
+
def wrap(node_or_markup)
|
|
147
|
+
wrapper =
|
|
148
|
+
case node_or_markup
|
|
149
|
+
when Taurus::XML::Element then node_or_markup.dup
|
|
150
|
+
when String
|
|
151
|
+
frag_doc = Taurus::XML::Document.parse(node_or_markup)
|
|
152
|
+
frag_doc.root or raise Taurus::XML::Error, "wrap markup has no root element"
|
|
153
|
+
else
|
|
154
|
+
raise ArgumentError, "wrap expects a String or Element, got #{node_or_markup.class}"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
parent = self.parent
|
|
158
|
+
raise Taurus::XML::Error, "cannot wrap a node with no parent" unless parent
|
|
159
|
+
|
|
160
|
+
# Insert wrapper at self's position, then move self into wrapper.
|
|
161
|
+
# add_child moves self (unlinks from old parent first), so no explicit
|
|
162
|
+
# remove_child needed — and trying to remove after the move corrupts
|
|
163
|
+
# the C tree (libtaurus silently handles non-child args badly).
|
|
164
|
+
add_next_sibling(wrapper)
|
|
165
|
+
wrapper.add_child(self)
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def dup
|
|
170
|
+
copy_ptr = Taurus::XML::FFI.taurus_element_copy(@c_ptr, @document.c_ptr)
|
|
171
|
+
raise Taurus::XML::Error, "taurus_element_copy failed" if copy_ptr.null?
|
|
172
|
+
Taurus::XML::Element.new(copy_ptr, @document)
|
|
173
|
+
end
|
|
174
|
+
alias_method :clone, :dup
|
|
175
|
+
|
|
176
|
+
def add_child(node_or_markup)
|
|
177
|
+
case node_or_markup
|
|
178
|
+
when Taurus::XML::Node
|
|
179
|
+
status = Taurus::XML::FFI.taurus_element_append_child(@c_ptr, node_or_markup.c_ptr)
|
|
180
|
+
raise Taurus::XML::Error,
|
|
181
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
182
|
+
node_or_markup
|
|
183
|
+
when String
|
|
184
|
+
frag = Taurus::XML::DocumentFragment.parse(node_or_markup, @document)
|
|
185
|
+
added = []
|
|
186
|
+
frag.children.each do |n|
|
|
187
|
+
status = Taurus::XML::FFI.taurus_element_append_child(@c_ptr, n.c_ptr)
|
|
188
|
+
raise Taurus::XML::Error,
|
|
189
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
190
|
+
added << n
|
|
191
|
+
end
|
|
192
|
+
Taurus::XML::NodeSet.new(@document, added)
|
|
193
|
+
else
|
|
194
|
+
raise ArgumentError, "add_child expects a Node or String, got #{node_or_markup.class}"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def namespace
|
|
199
|
+
uri = Taurus::XML::FFI.taurus_element_namespace(@c_ptr)
|
|
200
|
+
return nil if uri.nil? || uri.empty?
|
|
201
|
+
Taurus::XML::Namespace.new(self, uri)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def namespace_definitions
|
|
205
|
+
count = Taurus::XML::FFI.taurus_element_namespace_count(@c_ptr)
|
|
206
|
+
count.times.map do |i|
|
|
207
|
+
prefix = Taurus::XML::FFI.taurus_element_namespace_decl_prefix(@c_ptr, i)
|
|
208
|
+
uri = Taurus::XML::FFI.taurus_element_namespace_decl_uri(@c_ptr, i)
|
|
209
|
+
Taurus::XML::Namespace.new(self, uri, prefix: prefix)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def namespaces
|
|
214
|
+
scopes = {}
|
|
215
|
+
node = self
|
|
216
|
+
while node.is_a?(Taurus::XML::Element)
|
|
217
|
+
node.namespace_definitions.each do |ns|
|
|
218
|
+
key = ns.prefix ? "xmlns:#{ns.prefix}" : "xmlns"
|
|
219
|
+
scopes[key] ||= ns.href
|
|
220
|
+
end
|
|
221
|
+
node = node.parent
|
|
222
|
+
end
|
|
223
|
+
scopes
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def to_xml(indent: 0, no_decl: false, encoding: nil)
|
|
227
|
+
opts = Taurus::XML::FFI::SerializeOptions.new
|
|
228
|
+
opts[:indent] = indent.to_i
|
|
229
|
+
opts[:xml_declaration] = no_decl ? 0 : 1
|
|
230
|
+
enc_ptr = nil
|
|
231
|
+
if encoding
|
|
232
|
+
enc_ptr = ::FFI::MemoryPointer.from_string(encoding.to_s)
|
|
233
|
+
opts[:encoding] = enc_ptr
|
|
234
|
+
end
|
|
235
|
+
str_ptr = Taurus::XML::FFI.taurus_element_serialize(@c_ptr, opts.pointer)
|
|
236
|
+
return "" if str_ptr.null?
|
|
237
|
+
str_ptr.read_string.tap { Taurus::XML::FFI.taurus_free_string(str_ptr) }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def canonicalize(version = Taurus::XML::FFI::C14N_1_0,
|
|
241
|
+
inclusive_namespaces = nil,
|
|
242
|
+
with_comments: false,
|
|
243
|
+
exclusive: false,
|
|
244
|
+
mode: nil)
|
|
245
|
+
resolved_mode = mode || (exclusive ? Taurus::XML::FFI::C14N_MODE_EXCLUSIVE
|
|
246
|
+
: Taurus::XML::FFI::C14N_MODE_CANONICAL)
|
|
247
|
+
ns_ptr, _anchor = Taurus::XML.c14n_build_ns_pointer(inclusive_namespaces)
|
|
248
|
+
flags = with_comments ? 1 : 0
|
|
249
|
+
str_ptr = Taurus::XML::FFI.taurus_c14n_canonicalize_subtree_ex(
|
|
250
|
+
@c_ptr, version, resolved_mode, ns_ptr, flags)
|
|
251
|
+
return "" if str_ptr.null?
|
|
252
|
+
str_ptr.read_string.tap { Taurus::XML::FFI.taurus_free_string(str_ptr) }
|
|
253
|
+
end
|
|
254
|
+
alias_method :c14n, :canonicalize
|
|
255
|
+
|
|
256
|
+
def add_namespace_definition(prefix, href)
|
|
257
|
+
status = Taurus::XML::FFI.taurus_element_add_namespace_definition(
|
|
258
|
+
@c_ptr, prefix.to_s, href.to_s)
|
|
259
|
+
raise Taurus::XML::Error,
|
|
260
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
261
|
+
Taurus::XML::Namespace.new(self, href.to_s, prefix: prefix.nil? ? nil : prefix.to_s)
|
|
262
|
+
end
|
|
263
|
+
alias_method :add_namespace, :add_namespace_definition
|
|
264
|
+
|
|
265
|
+
def default_namespace=(href)
|
|
266
|
+
status = Taurus::XML::FFI.taurus_element_set_default_namespace(@c_ptr, href.to_s)
|
|
267
|
+
raise Taurus::XML::Error,
|
|
268
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
269
|
+
href
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def remove_namespace_definition(prefix)
|
|
273
|
+
status = Taurus::XML::FFI.taurus_element_remove_namespace_definition(@c_ptr, prefix.to_s)
|
|
274
|
+
raise Taurus::XML::Error,
|
|
275
|
+
Taurus::XML::FFI.taurus_status_string(status) unless status == Taurus::XML::FFI::TAURUS_OK
|
|
276
|
+
self
|
|
277
|
+
end
|
|
278
|
+
end
|