moxml 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +23 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +2 -0
- data/.rubocop_todo.yml +65 -0
- data/.ruby-version +1 -0
- data/Gemfile +10 -3
- data/README.adoc +401 -594
- data/lib/moxml/adapter/base.rb +102 -0
- data/lib/moxml/adapter/customized_oga/xml_declaration.rb +18 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +104 -0
- data/lib/moxml/adapter/nokogiri.rb +319 -0
- data/lib/moxml/adapter/oga.rb +318 -0
- data/lib/moxml/adapter/ox.rb +325 -0
- data/lib/moxml/adapter.rb +26 -170
- data/lib/moxml/attribute.rb +47 -14
- data/lib/moxml/builder.rb +64 -0
- data/lib/moxml/cdata.rb +4 -26
- data/lib/moxml/comment.rb +6 -22
- data/lib/moxml/config.rb +39 -15
- data/lib/moxml/context.rb +29 -0
- data/lib/moxml/declaration.rb +16 -26
- data/lib/moxml/doctype.rb +9 -0
- data/lib/moxml/document.rb +51 -63
- data/lib/moxml/document_builder.rb +87 -0
- data/lib/moxml/element.rb +63 -97
- data/lib/moxml/error.rb +20 -0
- data/lib/moxml/namespace.rb +12 -37
- data/lib/moxml/node.rb +78 -58
- data/lib/moxml/node_set.rb +19 -222
- data/lib/moxml/processing_instruction.rb +6 -25
- data/lib/moxml/text.rb +4 -26
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xml_utils/encoder.rb +55 -0
- data/lib/moxml/xml_utils.rb +80 -0
- data/lib/moxml.rb +33 -33
- data/moxml.gemspec +1 -1
- data/spec/moxml/adapter/nokogiri_spec.rb +14 -0
- data/spec/moxml/adapter/oga_spec.rb +14 -0
- data/spec/moxml/adapter/ox_spec.rb +49 -0
- data/spec/moxml/all_with_adapters_spec.rb +46 -0
- data/spec/moxml/config_spec.rb +55 -0
- data/spec/moxml/error_spec.rb +71 -0
- data/spec/moxml/examples/adapter_spec.rb +27 -0
- data/spec/moxml_spec.rb +50 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/shared_examples/attribute.rb +165 -0
- data/spec/support/shared_examples/builder.rb +25 -0
- data/spec/support/shared_examples/cdata.rb +70 -0
- data/spec/support/shared_examples/comment.rb +65 -0
- data/spec/support/shared_examples/context.rb +35 -0
- data/spec/support/shared_examples/declaration.rb +93 -0
- data/spec/support/shared_examples/doctype.rb +25 -0
- data/spec/support/shared_examples/document.rb +110 -0
- data/spec/support/shared_examples/document_builder.rb +43 -0
- data/spec/support/shared_examples/edge_cases.rb +185 -0
- data/spec/support/shared_examples/element.rb +130 -0
- data/spec/support/shared_examples/examples/attribute.rb +42 -0
- data/spec/support/shared_examples/examples/basic_usage.rb +67 -0
- data/spec/support/shared_examples/examples/memory.rb +54 -0
- data/spec/support/shared_examples/examples/namespace.rb +65 -0
- data/spec/support/shared_examples/examples/readme_examples.rb +100 -0
- data/spec/support/shared_examples/examples/thread_safety.rb +43 -0
- data/spec/support/shared_examples/examples/xpath.rb +39 -0
- data/spec/support/shared_examples/integration.rb +135 -0
- data/spec/support/shared_examples/namespace.rb +96 -0
- data/spec/support/shared_examples/node.rb +110 -0
- data/spec/support/shared_examples/node_set.rb +90 -0
- data/spec/support/shared_examples/processing_instruction.rb +88 -0
- data/spec/support/shared_examples/text.rb +66 -0
- data/spec/support/shared_examples/xml_adapter.rb +191 -0
- data/spec/support/xml_matchers.rb +27 -0
- metadata +55 -6
- data/.github/workflows/main.yml +0 -27
- data/lib/moxml/error_handler.rb +0 -77
- data/lib/moxml/errors.rb +0 -169
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require_relative "customized_oga/xml_generator"
|
5
|
+
require_relative "customized_oga/xml_declaration"
|
6
|
+
require "oga"
|
7
|
+
|
8
|
+
module Moxml
|
9
|
+
module Adapter
|
10
|
+
class Oga < Base
|
11
|
+
class << self
|
12
|
+
def set_root(doc, element)
|
13
|
+
doc.children.clear # Clear any existing children
|
14
|
+
doc.children << element
|
15
|
+
end
|
16
|
+
|
17
|
+
def parse(xml, options = {})
|
18
|
+
native_doc = begin
|
19
|
+
::Oga.parse_xml(xml, strict: options[:strict])
|
20
|
+
rescue LL::ParserError => e
|
21
|
+
raise Moxml::ParseError, e.message
|
22
|
+
end
|
23
|
+
|
24
|
+
DocumentBuilder.new(Context.new(:oga)).build(native_doc)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_document
|
28
|
+
::Oga::XML::Document.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_native_element(name)
|
32
|
+
::Oga::XML::Element.new(name: name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_native_text(content)
|
36
|
+
::Oga::XML::Text.new(text: content)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_native_cdata(content)
|
40
|
+
::Oga::XML::Cdata.new(text: content)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_native_comment(content)
|
44
|
+
::Oga::XML::Comment.new(text: content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_native_doctype(name, external_id, system_id)
|
48
|
+
::Oga::XML::Doctype.new(
|
49
|
+
name: name, public_id: external_id, system_id: system_id, type: "PUBLIC"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_native_processing_instruction(target, content)
|
54
|
+
::Oga::XML::ProcessingInstruction.new(name: target, text: content)
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_native_declaration(version, encoding, standalone)
|
58
|
+
attrs = {
|
59
|
+
version: version,
|
60
|
+
encoding: encoding,
|
61
|
+
standalone: standalone
|
62
|
+
}.compact
|
63
|
+
::Moxml::Adapter::CustomizedOga::XmlDeclaration.new(attrs)
|
64
|
+
end
|
65
|
+
|
66
|
+
def declaration_attribute(declaration, attr_name)
|
67
|
+
return unless ::Moxml::Declaration::ALLOWED_ATTRIBUTES.include?(attr_name.to_s)
|
68
|
+
|
69
|
+
declaration.public_send(attr_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_declaration_attribute(declaration, attr_name, value)
|
73
|
+
return unless ::Moxml::Declaration::ALLOWED_ATTRIBUTES.include?(attr_name.to_s)
|
74
|
+
|
75
|
+
declaration.public_send("#{attr_name}=", value)
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_native_namespace(element, prefix, uri)
|
79
|
+
ns = element.available_namespaces[prefix]
|
80
|
+
return ns unless ns.nil?
|
81
|
+
|
82
|
+
# Oga creates an attribute and registers a namespace
|
83
|
+
set_attribute(element, [::Oga::XML::Element::XMLNS_PREFIX, prefix].compact.join(":"), uri)
|
84
|
+
element.register_namespace(prefix, uri)
|
85
|
+
::Oga::XML::Namespace.new(name: prefix, uri: uri)
|
86
|
+
end
|
87
|
+
|
88
|
+
def set_namespace(element, ns_or_string)
|
89
|
+
element.namespace_name = ns_or_string.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def namespace(element)
|
93
|
+
if element.respond_to?(:namespace)
|
94
|
+
element.namespace
|
95
|
+
elsif element.respond_to?(:namespaces)
|
96
|
+
element.namespaces.values.last
|
97
|
+
end
|
98
|
+
rescue NoMethodError
|
99
|
+
# Oga attributes fail with NoMethodError:
|
100
|
+
# undefined method `available_namespaces' for nil:NilClass
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def processing_instruction_target(node)
|
105
|
+
node.name
|
106
|
+
end
|
107
|
+
|
108
|
+
def node_type(node)
|
109
|
+
case node
|
110
|
+
when ::Oga::XML::Element then :element
|
111
|
+
when ::Oga::XML::Text then :text
|
112
|
+
when ::Oga::XML::Cdata then :cdata
|
113
|
+
when ::Oga::XML::Comment then :comment
|
114
|
+
when ::Oga::XML::ProcessingInstruction then :processing_instruction
|
115
|
+
when ::Oga::XML::Document then :document
|
116
|
+
when ::Oga::XML::Doctype then :doctype
|
117
|
+
else :unknown
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def node_name(node)
|
122
|
+
node.name
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_node_name(node, name)
|
126
|
+
node.name = name
|
127
|
+
end
|
128
|
+
|
129
|
+
def children(node)
|
130
|
+
all_children = []
|
131
|
+
|
132
|
+
all_children += [node.xml_declaration, node.doctype].compact if node.is_a?(::Oga::XML::Document)
|
133
|
+
|
134
|
+
return all_children unless node.respond_to?(:children)
|
135
|
+
|
136
|
+
all_children + node.children.reject do |child|
|
137
|
+
child.is_a?(::Oga::XML::Text) &&
|
138
|
+
child.text.strip.empty? &&
|
139
|
+
!(child.previous.nil? && child.next.nil?)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def parent(node)
|
144
|
+
node.parent if node.respond_to?(:parent)
|
145
|
+
end
|
146
|
+
|
147
|
+
def next_sibling(node)
|
148
|
+
node.next
|
149
|
+
end
|
150
|
+
|
151
|
+
def previous_sibling(node)
|
152
|
+
node.previous
|
153
|
+
end
|
154
|
+
|
155
|
+
def document(node)
|
156
|
+
current = node
|
157
|
+
current = current.parent while parent(current)
|
158
|
+
|
159
|
+
current
|
160
|
+
end
|
161
|
+
|
162
|
+
def root(document)
|
163
|
+
document.children.find { |node| node.is_a?(::Oga::XML::Element) }
|
164
|
+
end
|
165
|
+
|
166
|
+
def attribute_element(attr)
|
167
|
+
attr.element
|
168
|
+
end
|
169
|
+
|
170
|
+
def attributes(element)
|
171
|
+
return [] unless element.respond_to?(:attributes)
|
172
|
+
|
173
|
+
# remove attributes-namespaces
|
174
|
+
element.attributes.reject do |attr|
|
175
|
+
attr.name == ::Oga::XML::Element::XMLNS_PREFIX || attr.namespace_name == ::Oga::XML::Element::XMLNS_PREFIX
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def set_attribute(element, name, value)
|
180
|
+
namespace_name = nil
|
181
|
+
namespace_name, name = name.to_s.split(":", 2) if name.to_s.include?(":")
|
182
|
+
|
183
|
+
attr = ::Oga::XML::Attribute.new(
|
184
|
+
name: name.to_s,
|
185
|
+
namespace_name: namespace_name,
|
186
|
+
value: value.to_s
|
187
|
+
)
|
188
|
+
element.add_attribute(attr)
|
189
|
+
end
|
190
|
+
|
191
|
+
def get_attribute(element, name)
|
192
|
+
element.attribute(name.to_s)
|
193
|
+
end
|
194
|
+
|
195
|
+
def get_attribute_value(element, name)
|
196
|
+
element[name.to_s]
|
197
|
+
end
|
198
|
+
|
199
|
+
def remove_attribute(element, name)
|
200
|
+
attr = element.attribute(name.to_s)
|
201
|
+
element.attributes.delete(attr) if attr
|
202
|
+
end
|
203
|
+
|
204
|
+
def add_child(element, child_or_text)
|
205
|
+
child =
|
206
|
+
if child_or_text.is_a?(String)
|
207
|
+
create_native_text(child_or_text)
|
208
|
+
else
|
209
|
+
child_or_text
|
210
|
+
end
|
211
|
+
|
212
|
+
element.children << child
|
213
|
+
end
|
214
|
+
|
215
|
+
def add_previous_sibling(node, sibling)
|
216
|
+
node.before(sibling)
|
217
|
+
end
|
218
|
+
|
219
|
+
def add_next_sibling(node, sibling)
|
220
|
+
node.after(sibling)
|
221
|
+
end
|
222
|
+
|
223
|
+
def remove(node)
|
224
|
+
node.remove
|
225
|
+
end
|
226
|
+
|
227
|
+
def replace(node, new_node)
|
228
|
+
node.replace(new_node)
|
229
|
+
end
|
230
|
+
|
231
|
+
def replace_children(node, new_children)
|
232
|
+
node.inner_text = ""
|
233
|
+
new_children.each { |child| add_child(node, child) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def text_content(node)
|
237
|
+
node.text
|
238
|
+
end
|
239
|
+
|
240
|
+
def inner_text(node)
|
241
|
+
if node.respond_to?(:inner_text)
|
242
|
+
node.inner_text
|
243
|
+
else
|
244
|
+
# Oga::XML::Text node for example
|
245
|
+
node.text
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def set_text_content(node, content)
|
250
|
+
if node.respond_to?(:inner_text)
|
251
|
+
node.inner_text = content
|
252
|
+
else
|
253
|
+
# Oga::XML::Text node for example
|
254
|
+
node.text = content
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def cdata_content(node)
|
259
|
+
node.text
|
260
|
+
end
|
261
|
+
|
262
|
+
def set_cdata_content(node, content)
|
263
|
+
node.text = content
|
264
|
+
end
|
265
|
+
|
266
|
+
def comment_content(node)
|
267
|
+
node.text
|
268
|
+
end
|
269
|
+
|
270
|
+
def set_comment_content(node, content)
|
271
|
+
node.text = content
|
272
|
+
end
|
273
|
+
|
274
|
+
def processing_instruction_content(node)
|
275
|
+
node.text
|
276
|
+
end
|
277
|
+
|
278
|
+
def set_processing_instruction_content(node, content)
|
279
|
+
node.text = content
|
280
|
+
end
|
281
|
+
|
282
|
+
def namespace_prefix(namespace)
|
283
|
+
# nil for the default namespace
|
284
|
+
return if namespace.name == ::Oga::XML::Element::XMLNS_PREFIX
|
285
|
+
|
286
|
+
namespace.name
|
287
|
+
end
|
288
|
+
|
289
|
+
def namespace_uri(namespace)
|
290
|
+
namespace.uri
|
291
|
+
end
|
292
|
+
|
293
|
+
def namespace_definitions(node)
|
294
|
+
return [] unless node.respond_to?(:namespaces)
|
295
|
+
|
296
|
+
node.namespaces.values
|
297
|
+
end
|
298
|
+
|
299
|
+
def xpath(node, expression, _namespaces = {})
|
300
|
+
node.xpath(expression).to_a
|
301
|
+
rescue ::LL::ParserError => e
|
302
|
+
raise Moxml::XPathError, e.message
|
303
|
+
end
|
304
|
+
|
305
|
+
def at_xpath(node, expression, _namespaces = {})
|
306
|
+
node.at_xpath(expression)
|
307
|
+
rescue ::Oga::XPath::Error => e
|
308
|
+
raise Moxml::XPathError, e.message
|
309
|
+
end
|
310
|
+
|
311
|
+
def serialize(node, _options = {})
|
312
|
+
# Expand empty tags, encode attributes, etc
|
313
|
+
::Moxml::Adapter::CustomizedOga::XmlGenerator.new(node).to_xml
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "ox"
|
5
|
+
|
6
|
+
module Moxml
|
7
|
+
module Adapter
|
8
|
+
class Ox < Base
|
9
|
+
class << self
|
10
|
+
def set_root(doc, element)
|
11
|
+
replace_children(doc, [element])
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(xml, _options = {})
|
15
|
+
native_doc = begin
|
16
|
+
result = ::Ox.parse(xml)
|
17
|
+
|
18
|
+
# result can be either Document or Element
|
19
|
+
if result.is_a?(::Ox::Document)
|
20
|
+
result
|
21
|
+
else
|
22
|
+
doc = ::Ox::Document.new
|
23
|
+
doc << result
|
24
|
+
doc
|
25
|
+
end
|
26
|
+
rescue ::Ox::ParseError => e
|
27
|
+
raise Moxml::ParseError, e.message
|
28
|
+
end
|
29
|
+
|
30
|
+
DocumentBuilder.new(Context.new(:ox)).build(native_doc)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_document
|
34
|
+
::Ox::Document.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_native_element(name)
|
38
|
+
element = ::Ox::Element.new(name)
|
39
|
+
element.instance_variable_set(:@attributes, {})
|
40
|
+
element
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_native_text(content)
|
44
|
+
content
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_native_cdata(content)
|
48
|
+
::Ox::CData.new(content)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_native_comment(content)
|
52
|
+
::Ox::Comment.new(content)
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_native_processing_instruction(target, content)
|
56
|
+
inst = ::Ox::Instruction.new(target)
|
57
|
+
inst.value = content
|
58
|
+
inst
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: compare to create_native_declaration
|
62
|
+
def create_native_declaration2(version, encoding, standalone)
|
63
|
+
inst = ::Ox::Instruct.new("xml")
|
64
|
+
inst.value = build_declaration_attrs(version, encoding, standalone)
|
65
|
+
inst
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_native_declaration(version, encoding, standalone)
|
69
|
+
doc = ::Ox::Document.new
|
70
|
+
doc.version = version
|
71
|
+
doc.encoding = encoding
|
72
|
+
doc.standalone = standalone
|
73
|
+
doc
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_native_namespace(element, prefix, uri)
|
77
|
+
element.attributes ||= {}
|
78
|
+
attr_name = prefix ? "xmlns:#{prefix}" : "xmlns"
|
79
|
+
element.attributes[attr_name] = uri
|
80
|
+
[prefix, uri]
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_namespace(element, ns)
|
84
|
+
prefix, uri = ns
|
85
|
+
element.attributes ||= {}
|
86
|
+
attr_name = prefix ? "xmlns:#{prefix}" : "xmlns"
|
87
|
+
element.attributes[attr_name] = uri
|
88
|
+
end
|
89
|
+
|
90
|
+
def namespace(element)
|
91
|
+
return nil unless element.attributes
|
92
|
+
|
93
|
+
xmlns_attr = element.attributes.find { |k, _| k.start_with?("xmlns:") || k == "xmlns" }
|
94
|
+
return nil unless xmlns_attr
|
95
|
+
|
96
|
+
prefix = xmlns_attr[0] == "xmlns" ? nil : xmlns_attr[0].sub("xmlns:", "")
|
97
|
+
[prefix, xmlns_attr[1]]
|
98
|
+
end
|
99
|
+
|
100
|
+
def processing_instruction_target(node)
|
101
|
+
node.name
|
102
|
+
end
|
103
|
+
|
104
|
+
def node_type(node)
|
105
|
+
case node
|
106
|
+
when ::Ox::Document then :document
|
107
|
+
when String then :text
|
108
|
+
when ::Ox::CData then :cdata
|
109
|
+
when ::Ox::Comment then :comment
|
110
|
+
when ::Ox::Instruct then :processing_instruction
|
111
|
+
when ::Ox::Element then :element
|
112
|
+
else :unknown
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def node_name(node)
|
117
|
+
node.value
|
118
|
+
rescue StandardError
|
119
|
+
node.name
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_node_name(node, name)
|
123
|
+
node.value = name if node.respond_to?(:value=)
|
124
|
+
node.name = name if node.respond_to?(:name=)
|
125
|
+
end
|
126
|
+
|
127
|
+
def children(node)
|
128
|
+
return [] unless node.respond_to?(:nodes)
|
129
|
+
|
130
|
+
node.nodes || []
|
131
|
+
end
|
132
|
+
|
133
|
+
def parent(node)
|
134
|
+
node.parent if node.respond_to?(:parent)
|
135
|
+
end
|
136
|
+
|
137
|
+
def next_sibling(node)
|
138
|
+
return unless (parent = parent(node))
|
139
|
+
|
140
|
+
siblings = parent.nodes
|
141
|
+
idx = siblings.index(node)
|
142
|
+
idx ? siblings[idx + 1] : nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def previous_sibling(node)
|
146
|
+
return unless (parent = parent(node))
|
147
|
+
|
148
|
+
siblings = parent.nodes
|
149
|
+
idx = siblings.index(node)
|
150
|
+
idx&.positive? ? siblings[idx - 1] : nil
|
151
|
+
end
|
152
|
+
|
153
|
+
def document(node)
|
154
|
+
current = node
|
155
|
+
current = parent(current) while parent(current)
|
156
|
+
current
|
157
|
+
end
|
158
|
+
|
159
|
+
def root(document)
|
160
|
+
document.nodes&.find { |node| node.is_a?(::Ox::Element) }
|
161
|
+
end
|
162
|
+
|
163
|
+
def attributes(element)
|
164
|
+
return {} unless element.respond_to?(:attributes) && element.attributes
|
165
|
+
|
166
|
+
element.attributes.reject { |k, _| k.start_with?("xmlns") }
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_attribute(element, name, value)
|
170
|
+
element.attributes ||= {}
|
171
|
+
element.attributes[name.to_s] = value.to_s
|
172
|
+
end
|
173
|
+
|
174
|
+
def get_attribute(element, name)
|
175
|
+
return nil unless element.respond_to?(:attributes) && element.attributes
|
176
|
+
|
177
|
+
element.attributes[name.to_s]
|
178
|
+
end
|
179
|
+
|
180
|
+
def remove_attribute(element, name)
|
181
|
+
return unless element.respond_to?(:attributes) && element.attributes
|
182
|
+
|
183
|
+
element.attributes.delete(name.to_s)
|
184
|
+
end
|
185
|
+
|
186
|
+
def add_child(element, child)
|
187
|
+
element.nodes ||= []
|
188
|
+
puts "Add child #{child} for #{element.name}: #{element.nodes.count}"
|
189
|
+
element.nodes << child
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_previous_sibling(node, sibling)
|
193
|
+
return unless parent(node)
|
194
|
+
|
195
|
+
idx = node.parent.nodes.index(node)
|
196
|
+
node.parent.nodes.insert(idx, sibling) if idx
|
197
|
+
end
|
198
|
+
|
199
|
+
def add_next_sibling(node, sibling)
|
200
|
+
return unless parent(node)
|
201
|
+
|
202
|
+
idx = node.parent.nodes.index(node)
|
203
|
+
node.parent.nodes.insert(idx + 1, sibling) if idx
|
204
|
+
end
|
205
|
+
|
206
|
+
def remove(node)
|
207
|
+
return unless parent(node)
|
208
|
+
|
209
|
+
node.parent.nodes.delete(node)
|
210
|
+
end
|
211
|
+
|
212
|
+
def replace(node, new_node)
|
213
|
+
return unless parent(node)
|
214
|
+
|
215
|
+
idx = node.parent.nodes.index(node)
|
216
|
+
node.parent.nodes[idx] = new_node if idx
|
217
|
+
end
|
218
|
+
|
219
|
+
def replace_children(node, new_children)
|
220
|
+
node.remove_children_by_path("*")
|
221
|
+
new_children.each { |child| node << child }
|
222
|
+
node
|
223
|
+
end
|
224
|
+
|
225
|
+
def text_content(node)
|
226
|
+
node.is_a?(String) ? node : node.value.to_s
|
227
|
+
end
|
228
|
+
|
229
|
+
def set_text_content(node, content)
|
230
|
+
if node.is_a?(String)
|
231
|
+
node.replace(content.to_s)
|
232
|
+
else
|
233
|
+
node.value = content.to_s
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def cdata_content(node)
|
238
|
+
node.value.to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
def set_cdata_content(node, content)
|
242
|
+
node.value = content.to_s
|
243
|
+
end
|
244
|
+
|
245
|
+
def comment_content(node)
|
246
|
+
node.value.to_s
|
247
|
+
end
|
248
|
+
|
249
|
+
def set_comment_content(node, content)
|
250
|
+
node.value = content.to_s
|
251
|
+
end
|
252
|
+
|
253
|
+
def processing_instruction_content(node)
|
254
|
+
node.value.to_s
|
255
|
+
end
|
256
|
+
|
257
|
+
def set_processing_instruction_content(node, content)
|
258
|
+
node.value = content.to_s
|
259
|
+
end
|
260
|
+
|
261
|
+
def namespace_definitions(node)
|
262
|
+
return [] unless node.respond_to?(:attributes) && node.attributes
|
263
|
+
|
264
|
+
node.attributes.each_with_object([]) do |(name, value), namespaces|
|
265
|
+
next unless name.start_with?("xmlns")
|
266
|
+
|
267
|
+
prefix = name == "xmlns" ? nil : name.sub("xmlns:", "")
|
268
|
+
namespaces << [prefix, value]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def xpath(node, expression, namespaces = {})
|
273
|
+
# Ox doesn't support XPath, implement basic path matching
|
274
|
+
results = []
|
275
|
+
traverse(node) do |n|
|
276
|
+
results << n if matches_xpath?(n, expression, namespaces)
|
277
|
+
end
|
278
|
+
results
|
279
|
+
end
|
280
|
+
|
281
|
+
def at_xpath(node, expression, namespaces = {})
|
282
|
+
traverse(node) do |n|
|
283
|
+
return n if matches_xpath?(n, expression, namespaces)
|
284
|
+
end
|
285
|
+
nil
|
286
|
+
end
|
287
|
+
|
288
|
+
def serialize(node, options = {})
|
289
|
+
ox_options = {
|
290
|
+
indent: options[:indent] || -1,
|
291
|
+
with_xml: true,
|
292
|
+
with_instructions: true,
|
293
|
+
encoding: options[:encoding]
|
294
|
+
}
|
295
|
+
::Ox.dump(node, ox_options)
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def traverse(node, &block)
|
301
|
+
return unless node
|
302
|
+
|
303
|
+
yield node
|
304
|
+
return unless node.respond_to?(:nodes)
|
305
|
+
|
306
|
+
node.nodes&.each { |child| traverse(child, &block) }
|
307
|
+
end
|
308
|
+
|
309
|
+
def matches_xpath?(node, expression, _namespaces = {})
|
310
|
+
case expression
|
311
|
+
when %r{//(\w+)}
|
312
|
+
node.is_a?(::Ox::Element) && node.value == ::Regexp.last_match(1)
|
313
|
+
when %r{//(\w+)\[@(\w+)='([^']+)'\]}
|
314
|
+
node.is_a?(::Ox::Element) &&
|
315
|
+
node.value == ::Regexp.last_match(1) &&
|
316
|
+
node.attributes &&
|
317
|
+
node.attributes[::Regexp.last_match(2)] == ::Regexp.last_match(3)
|
318
|
+
else
|
319
|
+
false
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|