moxml 0.5.113 → 0.5.115
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 +4 -4
- data/.rubocop.yml +4 -0
- data/Rakefile +6 -1
- data/lib/moxml/adapter/base.rb +12 -0
- data/lib/moxml/adapter/leptris.rb +67 -0
- data/lib/moxml/builder.rb +183 -0
- data/lib/moxml/element.rb +6 -3
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/shared_examples/node_wrappers/element_behavior.rb +19 -0
- data/spec/moxml/node_spec.rb +3 -2
- data/spec/moxml/opal_oga_adapter_spec.rb +12 -6
- data/spec/moxml/sax_entity_parity_spec.rb +5 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3269b76bcc581101e3b7f6219c58fc5010752ab1af4f4bb35b8e505989f08e04
|
|
4
|
+
data.tar.gz: 1c255835027897f446de2bb88b175a5b71432636b2a7ab3810480323865a4dac
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 12d97fd4ae488aee81c5e04e5b55be1336bea7bad60f14fc2481ee9fdd80689edac7dfc3113a6dc65be605a555d388d10e89566a206993c84438d0fcbd9e8d41
|
|
7
|
+
data.tar.gz: 1bf50772dde870a02dd80c1d4d686d2d47149b1d4b9210113bed8e2732dec6c48291105ee26969c92c1d46d6f7086931516c0f6110ce868d662cb2bfa1ea2871
|
data/.rubocop.yml
CHANGED
|
@@ -23,6 +23,10 @@ AllCops:
|
|
|
23
23
|
# the zero-allocation streaming form (issue #143). One parameter per
|
|
24
24
|
# record key is the interface; a shorter list would need a wrapper
|
|
25
25
|
# object, which is the allocation the API exists to avoid.
|
|
26
|
+
Metrics/BlockLength:
|
|
27
|
+
Exclude:
|
|
28
|
+
- 'Rakefile'
|
|
29
|
+
|
|
26
30
|
Metrics/ParameterLists:
|
|
27
31
|
Exclude:
|
|
28
32
|
- 'lib/moxml/materializer.rb'
|
data/Rakefile
CHANGED
|
@@ -190,7 +190,12 @@ namespace :spec do
|
|
|
190
190
|
|
|
191
191
|
desc "Run unit tests only"
|
|
192
192
|
RSpec::Core::RakeTask.new(:unit) do |t|
|
|
193
|
-
|
|
193
|
+
# Unit specs live under spec/moxml/ — the stale glob matched
|
|
194
|
+
# nothing (spec/unit/ holds only the rexml_isolated_test.rb
|
|
195
|
+
# entity-era investigation artifact), so this leg ran zero
|
|
196
|
+
# examples and hid the moxml suite from the fast gate.
|
|
197
|
+
t.pattern = "spec/moxml/**/*_spec.rb"
|
|
198
|
+
t.exclude_pattern = "spec/moxml/adapter/**/*_spec.rb"
|
|
194
199
|
end
|
|
195
200
|
|
|
196
201
|
desc "Run adapter tests only"
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -13,6 +13,18 @@ module Moxml
|
|
|
13
13
|
class << self
|
|
14
14
|
include XmlUtils
|
|
15
15
|
|
|
16
|
+
# Binding-level markup-accumulating builder face
|
|
17
|
+
# (leptris 1.9.313, #374 lever 2): nil = the adapter has no
|
|
18
|
+
# face and the Builder takes the per-node tree path.
|
|
19
|
+
def builder_face(_native_document)
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# A built subtree carried entity references as marker text —
|
|
24
|
+
# arm the document's marker restoration (the entity
|
|
25
|
+
# preservation contract).
|
|
26
|
+
def mark_entity_markers(_native_document); end
|
|
27
|
+
|
|
16
28
|
def preprocess_entities(xml)
|
|
17
29
|
Entity.preprocess_entities(xml)
|
|
18
30
|
end
|
|
@@ -161,6 +161,16 @@ module Moxml
|
|
|
161
161
|
::Leptris::XML::Document.method_defined?(:absorb) &&
|
|
162
162
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.311.3")
|
|
163
163
|
|
|
164
|
+
# Markup-accumulating builder face (leptris-ruby#374 lever 2,
|
|
165
|
+
# binding 1.9.313): the DSL accumulates markup in a plain
|
|
166
|
+
# String and ONE native crossing (parse + attach) flushes the
|
|
167
|
+
# whole subtree. See Adapter::Base#builder_face.
|
|
168
|
+
NATIVE_BUILDER =
|
|
169
|
+
defined?(::Leptris::XML::Builder) &&
|
|
170
|
+
defined?(::Leptris::XML::Document) &&
|
|
171
|
+
::Leptris::XML::Document.method_defined?(:build) &&
|
|
172
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.313.0")
|
|
173
|
+
|
|
164
174
|
# XSLT 1.0/2.0/3.0 through the engine's compiled-stylesheet
|
|
165
175
|
# faces (Moxml::XSLT contract). Top-level parameters are not
|
|
166
176
|
# threaded through the faces yet (leptris-ruby#360) — non-empty
|
|
@@ -208,6 +218,27 @@ module Moxml
|
|
|
208
218
|
# (and on installs without the native layer — the constant
|
|
209
219
|
# check short-circuits), a Node.wrap over the shared C
|
|
210
220
|
# pointer for NativeNodes.
|
|
221
|
+
def builder_face(native_document)
|
|
222
|
+
return nil unless NATIVE_BUILDER && NATIVE_READ_LAYER
|
|
223
|
+
|
|
224
|
+
doc = if native_document.is_a?(NN)
|
|
225
|
+
to_binding(native_document)
|
|
226
|
+
else
|
|
227
|
+
native_document
|
|
228
|
+
end
|
|
229
|
+
::Leptris::XML::Builder.new(doc)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def mark_entity_markers(native_document)
|
|
233
|
+
doc = if native_document.is_a?(NN)
|
|
234
|
+
to_binding(native_document)
|
|
235
|
+
else
|
|
236
|
+
native_document
|
|
237
|
+
end
|
|
238
|
+
attachments.set(doc, :entity_markers, true)
|
|
239
|
+
bump_serialize_generation
|
|
240
|
+
end
|
|
241
|
+
|
|
211
242
|
def to_binding(node)
|
|
212
243
|
return node unless NATIVE_READ_LAYER &&
|
|
213
244
|
node.is_a?(::Leptris::XML::NativeNode)
|
|
@@ -1900,6 +1931,16 @@ module Moxml
|
|
|
1900
1931
|
# contract); every other kind carries no cross-document
|
|
1901
1932
|
# identity worth preserving, so reconstruct it in the parent's
|
|
1902
1933
|
# document through the binding's create faces.
|
|
1934
|
+
def contains_foreign_child?(parent_nn, children)
|
|
1935
|
+
parent_doc = NN_DOCUMENT.bind_call(parent_nn)
|
|
1936
|
+
return false if parent_doc.nil?
|
|
1937
|
+
|
|
1938
|
+
children.any? do |child|
|
|
1939
|
+
child_doc = NN_DOCUMENT.bind_call(child)
|
|
1940
|
+
child_doc && !child_doc.equal?(parent_doc)
|
|
1941
|
+
end
|
|
1942
|
+
end
|
|
1943
|
+
|
|
1903
1944
|
def rebuild_foreign_child(parent_nn, child_nn)
|
|
1904
1945
|
doc = NN_DOCUMENT.bind_call(parent_nn)
|
|
1905
1946
|
rebuilt =
|
|
@@ -2348,6 +2389,32 @@ module Moxml
|
|
|
2348
2389
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.290.0")
|
|
2349
2390
|
|
|
2350
2391
|
def replace_children(node, new_children)
|
|
2392
|
+
# Cross-pool children: the binding's bulk face adopts per
|
|
2393
|
+
# node and fails on mixed kinds (inner_xml= with
|
|
2394
|
+
# text+elements died with Memory allocation failed —
|
|
2395
|
+
# pre-existing). Rebuild foreign children in this pool
|
|
2396
|
+
# after the clear — fragment children carry no identity
|
|
2397
|
+
# worth preserving; same-document children keep the plain
|
|
2398
|
+
# add, in order.
|
|
2399
|
+
if NATIVE_MUTATIONS_COHERENT && node.is_a?(NN) &&
|
|
2400
|
+
new_children.all?(NN) &&
|
|
2401
|
+
contains_foreign_child?(node, new_children)
|
|
2402
|
+
::Leptris::XML::FFI.check_status(
|
|
2403
|
+
::Leptris::XML::FFI.leptris_element_remove_children(
|
|
2404
|
+
::FFI::Pointer.new(node.address),
|
|
2405
|
+
),
|
|
2406
|
+
)
|
|
2407
|
+
node_doc = NN_DOCUMENT.bind_call(node)
|
|
2408
|
+
new_children.each do |child|
|
|
2409
|
+
child_doc = NN_DOCUMENT.bind_call(child)
|
|
2410
|
+
if child_doc && !child_doc.equal?(node_doc)
|
|
2411
|
+
rebuild_foreign_child(node, child)
|
|
2412
|
+
else
|
|
2413
|
+
NN_ADD_CHILD.bind_call(node, child)
|
|
2414
|
+
end
|
|
2415
|
+
end
|
|
2416
|
+
return node
|
|
2417
|
+
end
|
|
2351
2418
|
if NATIVE_READ_LAYER
|
|
2352
2419
|
node = to_binding(node)
|
|
2353
2420
|
new_children = new_children.map { |child| to_binding(child) }
|
data/lib/moxml/builder.rb
CHANGED
|
@@ -11,14 +11,40 @@ module Moxml
|
|
|
11
11
|
@context = context
|
|
12
12
|
@current = @document = context.create_document
|
|
13
13
|
@namespaces = {}
|
|
14
|
+
# Markup-accumulating face (leptris 1.9.313, #374 lever 2):
|
|
15
|
+
# leaf DSL ops write markup into one String and a single
|
|
16
|
+
# native crossing flushes the whole subtree at build end.
|
|
17
|
+
# Tree-only ops (declaration/doctype/PI/entity/namespace)
|
|
18
|
+
# flush first and run on the document; the face retires after
|
|
19
|
+
# its first flush.
|
|
20
|
+
@face = context.config.adapter.builder_face(@document.native)
|
|
21
|
+
@face_stack = []
|
|
22
|
+
@face_counts = Hash.new(0)
|
|
23
|
+
@face_deferred = []
|
|
24
|
+
@face_entity_markers = false
|
|
14
25
|
end
|
|
15
26
|
|
|
16
27
|
def build(&block)
|
|
17
28
|
instance_eval(&block)
|
|
29
|
+
flush_face
|
|
18
30
|
@document
|
|
19
31
|
end
|
|
20
32
|
|
|
33
|
+
# Element ops inside a face-accumulated subtree cannot touch the
|
|
34
|
+
# tree yet (nothing exists until the flush): PIs ride the raw
|
|
35
|
+
# hatch in exact position; namespace/entity/declaration/doctype
|
|
36
|
+
# defer to the flush and re-target their element by position (an
|
|
37
|
+
# element's namespace set is unordered, so deferral is exact;
|
|
38
|
+
# deferred children land at their element's end).
|
|
39
|
+
def face_defer(operation, args)
|
|
40
|
+
@face_deferred << [operation, args, @face_stack.dup]
|
|
41
|
+
end
|
|
42
|
+
|
|
21
43
|
def declaration(version: "1.0", encoding: "UTF-8", standalone: nil)
|
|
44
|
+
args = [version, encoding, standalone]
|
|
45
|
+
return face_defer(:declaration, args) if @face && @face_stack.any?
|
|
46
|
+
|
|
47
|
+
flush_face
|
|
22
48
|
@current.add_child(
|
|
23
49
|
@document.create_declaration(version, encoding, standalone),
|
|
24
50
|
)
|
|
@@ -41,34 +67,65 @@ module Moxml
|
|
|
41
67
|
end
|
|
42
68
|
|
|
43
69
|
def text(content)
|
|
70
|
+
return face_content { @face.text(content) } if @face
|
|
71
|
+
|
|
44
72
|
@current.add_child(@document.create_text(content))
|
|
45
73
|
end
|
|
46
74
|
|
|
47
75
|
def cdata(content)
|
|
76
|
+
return face_content { @face.cdata(content) } if @face
|
|
77
|
+
|
|
48
78
|
@current.add_child(@document.create_cdata(content))
|
|
49
79
|
end
|
|
50
80
|
|
|
51
81
|
def comment(content)
|
|
82
|
+
return face_content { @face.comment(content) } if @face
|
|
83
|
+
|
|
52
84
|
@current.add_child(@document.create_comment(content))
|
|
53
85
|
end
|
|
54
86
|
|
|
55
87
|
def entity_reference(name)
|
|
88
|
+
# In-position through the marker face: the reference lands as
|
|
89
|
+
# marker text exactly where the DSL puts it, and the flush
|
|
90
|
+
# arms the document's entity-marker restoration.
|
|
91
|
+
if @face && @face_stack.any?
|
|
92
|
+
@face.raw("#{Entity::MARKER}#{name};")
|
|
93
|
+
@face_entity_markers = true
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
flush_face
|
|
56
98
|
@current.add_child(@document.create_entity_reference(name))
|
|
57
99
|
end
|
|
58
100
|
|
|
59
101
|
def processing_instruction(target, content)
|
|
102
|
+
if @face
|
|
103
|
+
if @face_stack.empty?
|
|
104
|
+
flush_face
|
|
105
|
+
else
|
|
106
|
+
@face.raw("<?#{target} #{content}?>")
|
|
107
|
+
return
|
|
108
|
+
end
|
|
109
|
+
end
|
|
60
110
|
@current.add_child(
|
|
61
111
|
@document.create_processing_instruction(target, content),
|
|
62
112
|
)
|
|
63
113
|
end
|
|
64
114
|
|
|
65
115
|
def namespace(prefix, uri)
|
|
116
|
+
return face_defer(:namespace, [prefix, uri]) if @face && @face_stack.any?
|
|
117
|
+
|
|
118
|
+
flush_face
|
|
66
119
|
@current.add_namespace(prefix, uri)
|
|
67
120
|
@namespaces[prefix] = uri
|
|
68
121
|
end
|
|
69
122
|
|
|
70
123
|
# Convenience method for DOCTYPE
|
|
71
124
|
def doctype(name, external_id = nil, system_id = nil)
|
|
125
|
+
args = [name, external_id, system_id]
|
|
126
|
+
return face_defer(:doctype, args) if @face && @face_stack.any?
|
|
127
|
+
|
|
128
|
+
flush_face
|
|
72
129
|
@current.add_child(
|
|
73
130
|
@document.create_doctype(name, external_id, system_id),
|
|
74
131
|
)
|
|
@@ -109,13 +166,139 @@ module Moxml
|
|
|
109
166
|
true
|
|
110
167
|
end
|
|
111
168
|
|
|
169
|
+
FACE_NAME = /\A[a-zA-Z_][\w.-]*\z/
|
|
170
|
+
FACE_ESCAPE = { "&" => "&", "<" => "<", ">" => ">",
|
|
171
|
+
'"' => """ }.freeze
|
|
172
|
+
FACE_ESCAPE_RE = /[&<>"]/
|
|
173
|
+
|
|
112
174
|
private
|
|
113
175
|
|
|
176
|
+
# One face.element per DSL element — the face opens/closes the
|
|
177
|
+
# markup around the block itself, so nesting is free. Prefixed
|
|
178
|
+
# names (p:tag) fall outside the face's name shape and ride the
|
|
179
|
+
# raw hatch with manual escaping. Returns no wrapper: nothing
|
|
180
|
+
# exists until the flush.
|
|
181
|
+
def face_element(tag_name, attrs, text_content, block, eval_block)
|
|
182
|
+
unless FACE_NAME.match?(tag_name)
|
|
183
|
+
return face_raw_element(tag_name, attrs, text_content, block,
|
|
184
|
+
eval_block)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
face_attrs = {}
|
|
188
|
+
attrs.each do |key, value|
|
|
189
|
+
k = key.to_s
|
|
190
|
+
if k == "xmlns" || k.start_with?("xmlns:")
|
|
191
|
+
face_attrs[k] = value.to_s
|
|
192
|
+
@namespaces[k.delete_prefix("xmlns:")] = value.to_s
|
|
193
|
+
else
|
|
194
|
+
face_attrs[k] = value
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
ordinal = @face_counts[@face_stack.length]
|
|
198
|
+
@face_counts[@face_stack.length] = ordinal + 1
|
|
199
|
+
@face_stack << ordinal
|
|
200
|
+
begin
|
|
201
|
+
@face.element(tag_name, face_attrs) do
|
|
202
|
+
@face.text(text_content) if text_content
|
|
203
|
+
run_builder_block(block, eval_block) if block
|
|
204
|
+
end
|
|
205
|
+
ensure
|
|
206
|
+
@face_stack.pop
|
|
207
|
+
@face_counts.delete(@face_stack.length + 1)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def face_raw_element(tag_name, attrs, text_content, block, eval_block)
|
|
212
|
+
open = "<#{tag_name}"
|
|
213
|
+
attrs.each do |key, value|
|
|
214
|
+
k = key.to_s
|
|
215
|
+
if k == "xmlns" || k.start_with?("xmlns:")
|
|
216
|
+
@namespaces[k.delete_prefix("xmlns:")] = value.to_s
|
|
217
|
+
end
|
|
218
|
+
open << " " << k << '="' << face_escape(value) << '"'
|
|
219
|
+
end
|
|
220
|
+
ordinal = @face_counts[@face_stack.length]
|
|
221
|
+
@face_counts[@face_stack.length] = ordinal + 1
|
|
222
|
+
@face_stack << ordinal
|
|
223
|
+
begin
|
|
224
|
+
if block || text_content
|
|
225
|
+
@face.raw("#{open}>")
|
|
226
|
+
@face.text(text_content) if text_content
|
|
227
|
+
run_builder_block(block, eval_block) if block
|
|
228
|
+
@face.raw("</#{tag_name}>")
|
|
229
|
+
else
|
|
230
|
+
@face.raw("#{open}/>")
|
|
231
|
+
end
|
|
232
|
+
ensure
|
|
233
|
+
@face_stack.pop
|
|
234
|
+
@face_counts.delete(@face_stack.length + 1)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Content at document level has no root to attach to and the
|
|
239
|
+
# Document face flush demands exactly one root — retire the face
|
|
240
|
+
# and take the tree path (legacy semantics: doc-level text is a
|
|
241
|
+
# document child). Inside a subtree the text accumulates in
|
|
242
|
+
# position; @current never leaves the document in face mode, so
|
|
243
|
+
# the face stack is the nesting test.
|
|
244
|
+
def face_content
|
|
245
|
+
return yield if @face && @face_stack.any?
|
|
246
|
+
|
|
247
|
+
flush_face if @face
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def run_builder_block(block, eval_block)
|
|
251
|
+
eval_block ? instance_eval(&block) : block.call
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def face_escape(value)
|
|
255
|
+
value.to_s.gsub(FACE_ESCAPE_RE, FACE_ESCAPE)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def flush_face
|
|
259
|
+
return unless @face
|
|
260
|
+
|
|
261
|
+
face = @face
|
|
262
|
+
@face = nil
|
|
263
|
+
face.flush
|
|
264
|
+
if @face_entity_markers
|
|
265
|
+
@context.config.adapter.mark_entity_markers(@document.native)
|
|
266
|
+
end
|
|
267
|
+
apply_face_deferred
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Deferred tree-ops re-target their element by position: the
|
|
271
|
+
# stack records each element's ordinal among its parent's
|
|
272
|
+
# elements, and the flushed tree preserves markup order.
|
|
273
|
+
def apply_face_deferred
|
|
274
|
+
@face_deferred.each do |op, args, stack|
|
|
275
|
+
target = @document.root
|
|
276
|
+
stack[1..].each do |ordinal|
|
|
277
|
+
target = target.elements.to_a[ordinal]
|
|
278
|
+
end
|
|
279
|
+
case op
|
|
280
|
+
when :namespace
|
|
281
|
+
target.add_namespace(*args)
|
|
282
|
+
@namespaces[args[0]] = args[1]
|
|
283
|
+
when :declaration
|
|
284
|
+
target.add_child(@document.create_declaration(*args))
|
|
285
|
+
when :doctype
|
|
286
|
+
target.add_child(@document.create_doctype(*args))
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
@face_deferred = []
|
|
290
|
+
end
|
|
291
|
+
|
|
114
292
|
# Single method for all element creation.
|
|
115
293
|
# eval_block: true → instance_eval (build DSL context)
|
|
116
294
|
# eval_block: false → yield (preserves caller's self)
|
|
117
295
|
def create_element_node(tag_name, attrs = {}, text_content: nil,
|
|
118
296
|
block: nil, eval_block: true)
|
|
297
|
+
if @face
|
|
298
|
+
face_element(tag_name, attrs, text_content, block, eval_block)
|
|
299
|
+
return nil
|
|
300
|
+
end
|
|
301
|
+
|
|
119
302
|
el = @document.create_element(tag_name)
|
|
120
303
|
|
|
121
304
|
attrs.each do |key, value|
|
data/lib/moxml/element.rb
CHANGED
|
@@ -400,14 +400,17 @@ module Moxml
|
|
|
400
400
|
end
|
|
401
401
|
|
|
402
402
|
def inner_xml=(xml)
|
|
403
|
-
wrapper
|
|
404
|
-
|
|
403
|
+
# Fragment parse, not a wrapper element around interpolated
|
|
404
|
+
# input (TODO.refactor/03): the fragment face is the same one
|
|
405
|
+
# add_child(String) uses, so semantics match and no wrapper
|
|
406
|
+
# name can collide with content.
|
|
407
|
+
nodes = context.parse_fragment(xml).to_a
|
|
405
408
|
@text = nil
|
|
406
409
|
# Whole-list replacement rides the children generation like
|
|
407
410
|
# children= does (moxml#310) — text and elements memos across
|
|
408
411
|
# wrappers recompute on next read.
|
|
409
412
|
context.bump_children_generation
|
|
410
|
-
adapter.replace_children(@native,
|
|
413
|
+
adapter.replace_children(@native, nodes.map(&:native))
|
|
411
414
|
invalidate_children_cache!
|
|
412
415
|
end
|
|
413
416
|
|
data/lib/moxml/version.rb
CHANGED
|
@@ -89,6 +89,25 @@ RSpec.shared_examples "Moxml::Element" do
|
|
|
89
89
|
expect(element.children.first.name).to eq("child")
|
|
90
90
|
expect(element.children.first.text).to eq("text")
|
|
91
91
|
end
|
|
92
|
+
|
|
93
|
+
it "sets inner XML with mixed top-level nodes" do
|
|
94
|
+
# Structure, not serialization spelling: Ox renders bare
|
|
95
|
+
# text as <s> nodes and rexml/oga expand empty tags.
|
|
96
|
+
element.inner_xml = "a<c1/>b<c2 v='1'/>c"
|
|
97
|
+
kids = element.children
|
|
98
|
+
expect(
|
|
99
|
+
kids.map { |c| c.element? ? :element : :content },
|
|
100
|
+
).to eq(%i[content element content element content])
|
|
101
|
+
expect(kids[0].text).to eq("a")
|
|
102
|
+
expect(kids[2].text).to eq("b")
|
|
103
|
+
expect(kids[4].text).to eq("c")
|
|
104
|
+
expect(kids[3]["v"]).to eq("1")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "sets inner XML containing element-like text and entities" do
|
|
108
|
+
element.inner_xml = "<c></element> & A</c>"
|
|
109
|
+
expect(element.children.first.text).to eq("</element> & A")
|
|
110
|
+
end
|
|
92
111
|
end
|
|
93
112
|
|
|
94
113
|
describe "node manipulation" do
|
data/spec/moxml/node_spec.rb
CHANGED
|
@@ -168,9 +168,10 @@ RSpec.describe Moxml::Node do
|
|
|
168
168
|
end
|
|
169
169
|
end
|
|
170
170
|
|
|
171
|
-
it "
|
|
171
|
+
it "locates non-element nodes through typed node tests" do
|
|
172
172
|
text = doc.root.children.first.children.first
|
|
173
|
-
expect
|
|
173
|
+
expect(text.path).to end_with("/text()")
|
|
174
|
+
expect(doc.at_xpath(text.path)).to eq(text)
|
|
174
175
|
end
|
|
175
176
|
end
|
|
176
177
|
|
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
require "moxml/adapter/shared_examples/adapter_contract"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
# The describe resolves the adapter constant at load time — under
|
|
7
|
+
# MRI nothing loads the Opal compat shim, so the whole describe must
|
|
8
|
+
# sit behind the engine check (the `if:` metadata alone still
|
|
9
|
+
# evaluates the constant reference).
|
|
10
|
+
if RUBY_ENGINE == "opal"
|
|
11
|
+
RSpec.describe Moxml::Adapter::Oga do
|
|
12
|
+
around do |example|
|
|
13
|
+
Moxml.with_config(:oga, true, "UTF-8") do
|
|
14
|
+
example.run
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
|
-
end
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
it_behaves_like "xml adapter"
|
|
19
|
+
end
|
|
14
20
|
end
|
|
@@ -164,7 +164,11 @@ RSpec.describe "SAX/DOM entity parity" do
|
|
|
164
164
|
# double-escaping on round-trip.
|
|
165
165
|
doc = ctx.create_document
|
|
166
166
|
el = doc.create_element("doc")
|
|
167
|
-
|
|
167
|
+
# Programmatically-built: a literal Text node, no markup parse.
|
|
168
|
+
# (add_child with a String operand is MARKUP since the nokogiri
|
|
169
|
+
# compat round — a bare "&" is ill-formed there and each
|
|
170
|
+
# engine recovers differently.)
|
|
171
|
+
el.add_child(doc.create_text("a & b"))
|
|
168
172
|
doc.add_child(el)
|
|
169
173
|
|
|
170
174
|
serialized = doc.to_xml.sub(/\A<\?[^>]*\?>\s*/, "").strip
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: moxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.115
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
Moxml is a unified XML manipulation library that provides a common API
|