moxml 0.5.59 → 0.5.61
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/lib/moxml/adapter/base.rb +9 -0
- data/lib/moxml/adapter/leptris.rb +116 -37
- data/lib/moxml/plan.rb +133 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml.rb +1 -0
- data/spec/moxml/plan_spec.rb +102 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 432ffb6c040c2b5689552a050b01835b792cde27ddc816981fd186fd83b9df7d
|
|
4
|
+
data.tar.gz: 917f7712e1799209f6d1c3f832fb5a3de492da5e570959a9d8bf2a8b4902d537
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6f0a69c52df888bf9afbc2706e755d9844d8d65247720d887cc6fb34c8d855964f278d798b947bfb028ce58f32253ff6ed92101fcc2cf45fe28695170c23405
|
|
7
|
+
data.tar.gz: 90b841bd8eb58119a38dce5333a53f9b37abc70755e69a887c52ab4565d1101727a2dcc40903a4671b92a10d4ba1bd67ab712ac97d37bdd04861359dd4b09bba
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -358,6 +358,15 @@ namespace_validation_mode: :strict)
|
|
|
358
358
|
nil
|
|
359
359
|
end
|
|
360
360
|
|
|
361
|
+
# Plan row stream (Moxml::Plan): yields |name, attrs_pairs
|
|
362
|
+
# (flat [k, v, ...]), first-text, depth| per element in
|
|
363
|
+
# document order (pre-order). Returns true when the adapter
|
|
364
|
+
# provided the stream; nil/false lets the plan run its
|
|
365
|
+
# generic wrapper walk.
|
|
366
|
+
def plan_rows(_native)
|
|
367
|
+
nil
|
|
368
|
+
end
|
|
369
|
+
|
|
361
370
|
# Deterministic native-memory release for adapters backed by
|
|
362
371
|
# C trees (issue #134). GC-managed engines no-op; released
|
|
363
372
|
# documents raise the engine's use-after-free error on
|
|
@@ -213,49 +213,84 @@ module Moxml
|
|
|
213
213
|
processing_instruction: PROCESSING_INSTRUCTION,
|
|
214
214
|
}.freeze
|
|
215
215
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
216
|
+
# Hot-read face selection: bindings >= 1.9.194.1 carry
|
|
217
|
+
# unshadowed aliases (attr_read/text_read) that plain
|
|
218
|
+
# dispatch reaches at method-cache cost — beats
|
|
219
|
+
# UnboundMethod#bind_call (~86ns) on every bare read.
|
|
220
|
+
ELEMENT_CONTRACT_READ =
|
|
221
|
+
::Moxml::ElementBehavior.instance_method(:[])
|
|
222
|
+
|
|
223
|
+
NATIVE_HOT_ALIASES =
|
|
224
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.194.1")
|
|
225
|
+
|
|
226
|
+
# Doc-level entity-marker memo shared by both hot-read
|
|
227
|
+
# faces (one WeakMap, generation-stamped Integer values).
|
|
228
|
+
ENTITY_DOC_MEMO = ::ObjectSpace::WeakMap.new
|
|
229
|
+
|
|
230
|
+
if NATIVE_HOT_ALIASES
|
|
231
|
+
module Reads
|
|
232
|
+
# Marker presence is a DOCUMENT fact, but the wrapper
|
|
233
|
+
# memo re-derives it per node (the per-read machinery
|
|
234
|
+
# was ~a quarter of the consumer walk). Doc-level
|
|
235
|
+
# WeakMap, generation-stamped into one Integer
|
|
236
|
+
# (immediates make safe weak values): one C document
|
|
237
|
+
# read plus one map hit per bare read. Entries die
|
|
238
|
+
# with their documents; a stale generation re-derives.
|
|
239
|
+
def doc_entity_bearing?
|
|
240
|
+
doc = NN_DOCUMENT.bind_call(self)
|
|
241
|
+
memo = ENTITY_DOC_MEMO[doc]
|
|
242
|
+
gen = ::Moxml::Adapter::Leptris.serialize_generation
|
|
243
|
+
return memo.allbits?(1) if memo && (memo >> 1) == gen
|
|
244
|
+
|
|
245
|
+
bearing = ::Moxml::Adapter::Leptris.entity_bearing?(self)
|
|
246
|
+
ENTITY_DOC_MEMO[doc] = (gen << 1) | (bearing ? 1 : 0)
|
|
247
|
+
bearing
|
|
248
|
+
end
|
|
244
249
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
def [](key)
|
|
251
|
+
if key.is_a?(String) && !key.include?(":")
|
|
252
|
+
value = attr_read(key)
|
|
253
|
+
return value unless value.is_a?(String) && doc_entity_bearing?
|
|
249
254
|
|
|
250
|
-
|
|
255
|
+
return ::Moxml::Adapter::Leptris.restore_entities(value)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
ELEMENT_CONTRACT_READ.bind_call(self, key)
|
|
251
259
|
end
|
|
252
260
|
|
|
253
|
-
|
|
261
|
+
def text
|
|
262
|
+
value = text_read
|
|
263
|
+
value.is_a?(String) && doc_entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
|
|
264
|
+
end
|
|
254
265
|
end
|
|
266
|
+
else
|
|
267
|
+
module Reads
|
|
268
|
+
def doc_entity_bearing?
|
|
269
|
+
doc = NN_DOCUMENT.bind_call(self)
|
|
270
|
+
memo = ENTITY_DOC_MEMO[doc]
|
|
271
|
+
gen = ::Moxml::Adapter::Leptris.serialize_generation
|
|
272
|
+
return memo.allbits?(1) if memo && (memo >> 1) == gen
|
|
273
|
+
|
|
274
|
+
bearing = ::Moxml::Adapter::Leptris.entity_bearing?(self)
|
|
275
|
+
ENTITY_DOC_MEMO[doc] = (gen << 1) | (bearing ? 1 : 0)
|
|
276
|
+
bearing
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def [](key)
|
|
280
|
+
if key.is_a?(String) && !key.include?(":")
|
|
281
|
+
value = NN_ATTRIBUTE.bind_call(self, key)
|
|
282
|
+
return value unless value.is_a?(String) && doc_entity_bearing?
|
|
283
|
+
|
|
284
|
+
return ::Moxml::Adapter::Leptris.restore_entities(value)
|
|
285
|
+
end
|
|
255
286
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
287
|
+
ELEMENT_CONTRACT_READ.bind_call(self, key)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def text
|
|
291
|
+
value = NN_CONTENT.bind_call(self)
|
|
292
|
+
value.is_a?(String) && doc_entity_bearing? ? ::Moxml::Adapter::Leptris.restore_entities(value) : value
|
|
293
|
+
end
|
|
259
294
|
end
|
|
260
295
|
end
|
|
261
296
|
ELEMENT.include(Reads)
|
|
@@ -493,6 +528,50 @@ module Moxml
|
|
|
493
528
|
extend Markers
|
|
494
529
|
extend Materialize
|
|
495
530
|
|
|
531
|
+
# Plan row stream (Moxml::Plan): the engine's one-pass C
|
|
532
|
+
# snapshot is pre-order with the first text child on the row —
|
|
533
|
+
# exactly the plan's shape, with no wrapper minting at all.
|
|
534
|
+
# Marker-bearing documents stay on the generic path (the bulk
|
|
535
|
+
# stream has no marker split).
|
|
536
|
+
NATIVE_PLAN_ROWS =
|
|
537
|
+
NATIVE_READ_LAYER &&
|
|
538
|
+
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.4")
|
|
539
|
+
|
|
540
|
+
def self.plan_rows(native)
|
|
541
|
+
return nil unless NATIVE_READ_LAYER
|
|
542
|
+
|
|
543
|
+
doc = if native.is_a?(::Leptris::XML::Document)
|
|
544
|
+
native
|
|
545
|
+
else
|
|
546
|
+
doc_for(native)
|
|
547
|
+
end
|
|
548
|
+
return nil if doc.nil? || attachments.get(doc, :entity_markers)
|
|
549
|
+
|
|
550
|
+
root_binding = if native.is_a?(::Leptris::XML::Document)
|
|
551
|
+
doc.root
|
|
552
|
+
else
|
|
553
|
+
to_binding(native)
|
|
554
|
+
end
|
|
555
|
+
return nil unless root_binding
|
|
556
|
+
|
|
557
|
+
rows = if NATIVE_PLAN_ROWS
|
|
558
|
+
::Leptris::XML::Native.snapshot_rows(doc,
|
|
559
|
+
root_binding.c_address)
|
|
560
|
+
else
|
|
561
|
+
doc.snapshot(root_binding)
|
|
562
|
+
end
|
|
563
|
+
rows.each do |row|
|
|
564
|
+
if row.is_a?(::Array)
|
|
565
|
+
yield(row[0], row[1], row[2], row[3])
|
|
566
|
+
else
|
|
567
|
+
next unless row[:kind] == "element"
|
|
568
|
+
|
|
569
|
+
yield(row[:name], row[:attrs].flatten, row[:text], row[:depth])
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
true
|
|
573
|
+
end
|
|
574
|
+
|
|
496
575
|
class << self
|
|
497
576
|
def attachments
|
|
498
577
|
@attachments ||= Moxml::NativeAttachment.new
|
data/lib/moxml/plan.rb
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
# Plan-compiled materialization: declare the document shape once,
|
|
5
|
+
# execute it against the adapter's bulk row stream — no wrapper
|
|
6
|
+
# tree, no NodeSet, no per-node contract dispatch. The compiled
|
|
7
|
+
# grammar instead of the interpreted walk.
|
|
8
|
+
#
|
|
9
|
+
# plan = Moxml::Plan.new do
|
|
10
|
+
# on("record") { |attrs, _text, fields| Record.new(attrs["id"], attrs["kind"], fields) }
|
|
11
|
+
# on("field") { |attrs, text, _kids| Field.new(attrs["name"], attrs["unit"], text) }
|
|
12
|
+
# end
|
|
13
|
+
# records = plan.parse(xml, ctx)
|
|
14
|
+
#
|
|
15
|
+
# Handlers run bottom-up as each element completes; +children+ is
|
|
16
|
+
# the array of handler values for matched child elements whose
|
|
17
|
+
# parent ALSO matched (values of unmatched parents are dropped —
|
|
18
|
+
# matching a parent is the consumer's decision to keep a subtree).
|
|
19
|
+
# +attrs+ is a Hash of the element's attributes (local name =>
|
|
20
|
+
# value); +text+ is the first text child's content, or nil.
|
|
21
|
+
#
|
|
22
|
+
# Name matching is global (flat table, not path-relative): element
|
|
23
|
+
# names unique per role — the dominant consumer shape — work as-is;
|
|
24
|
+
# distinct roles sharing a name need distinct documents or a
|
|
25
|
+
# rename upstream.
|
|
26
|
+
#
|
|
27
|
+
# Adapters with a bulk stream answer plan_rows (leptris: the
|
|
28
|
+
# engine's one-pass C snapshot — pre-order, first-text on the
|
|
29
|
+
# row); the rest run the generic pre-order wrapper walk below,
|
|
30
|
+
# emitting the identical stream — spec-pinned equal.
|
|
31
|
+
class Plan
|
|
32
|
+
def initialize(&block)
|
|
33
|
+
@handlers = {}
|
|
34
|
+
instance_eval(&block) if block
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Registers a handler for elements named +name+ (local name).
|
|
38
|
+
# Returns self so registrations chain.
|
|
39
|
+
def on(name, &handler)
|
|
40
|
+
raise ArgumentError, "on(#{name.inspect}) requires a block" unless handler
|
|
41
|
+
|
|
42
|
+
@handlers[name] = handler
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Parses +xml+ and materializes the plan against it.
|
|
47
|
+
def parse(xml, context)
|
|
48
|
+
materialize(context.parse(xml))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Materializes the plan against a document (or element) wrapper.
|
|
52
|
+
# Returns the values of top-level matched elements.
|
|
53
|
+
def materialize(node)
|
|
54
|
+
results = []
|
|
55
|
+
frames = [] # per open depth: children values (nil = unmatched)
|
|
56
|
+
meta = [] # per open depth: [name, attrs, text] (nil = unmatched)
|
|
57
|
+
|
|
58
|
+
close_top = lambda do
|
|
59
|
+
m = meta.pop
|
|
60
|
+
children = frames.pop
|
|
61
|
+
next unless m
|
|
62
|
+
|
|
63
|
+
value = @handlers[m[0]].call(m[1], m[2], children)
|
|
64
|
+
if frames.empty? || frames.last.nil?
|
|
65
|
+
results << value
|
|
66
|
+
else
|
|
67
|
+
frames.last << value
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
row = lambda do |name, attrs_pairs, text, depth|
|
|
72
|
+
close_top.call while frames.size > depth
|
|
73
|
+
handler = @handlers[name]
|
|
74
|
+
if handler
|
|
75
|
+
attrs = {}
|
|
76
|
+
i = 0
|
|
77
|
+
while i < attrs_pairs.length
|
|
78
|
+
attrs[attrs_pairs[i]] = attrs_pairs[i + 1]
|
|
79
|
+
i += 2
|
|
80
|
+
end
|
|
81
|
+
frames << []
|
|
82
|
+
meta << [name, attrs, text]
|
|
83
|
+
else
|
|
84
|
+
frames << nil
|
|
85
|
+
meta << nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
adapter = node.context.config.adapter
|
|
90
|
+
ran = adapter.plan_rows(node.native) do |name, pairs, text, depth|
|
|
91
|
+
row.call(name, pairs, text, depth)
|
|
92
|
+
end
|
|
93
|
+
unless ran
|
|
94
|
+
walk_wrappers(node) do |name, pairs, text, depth|
|
|
95
|
+
row.call(name, pairs, text, depth)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
close_top.call while meta.any?
|
|
100
|
+
results
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Generic pre-order walk over the wrapper tree — the fallback
|
|
106
|
+
# stream for adapters without a bulk path. Same protocol as
|
|
107
|
+
# plan_rows: |name, attrs_pairs (flat [k, v, ...]), first-text,
|
|
108
|
+
# depth|.
|
|
109
|
+
def walk_wrappers(node, depth = 0)
|
|
110
|
+
case node
|
|
111
|
+
when Document
|
|
112
|
+
(root = node.root) && walk_wrappers(root, depth) { |*a| yield(*a) }
|
|
113
|
+
when Element
|
|
114
|
+
first_text = nil
|
|
115
|
+
node.children.each do |child|
|
|
116
|
+
first_text ||= child.content if child.is_a?(Text)
|
|
117
|
+
end
|
|
118
|
+
yield(node.name, attribute_pairs(node), first_text, depth)
|
|
119
|
+
node.children.each do |child|
|
|
120
|
+
walk_wrappers(child, depth + 1) { |*a| yield(*a) } if child.is_a?(Element)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def attribute_pairs(element)
|
|
126
|
+
pairs = []
|
|
127
|
+
element.attributes.each do |attr|
|
|
128
|
+
pairs << attr.name << attr.value
|
|
129
|
+
end
|
|
130
|
+
pairs
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml.rb
CHANGED
|
@@ -93,6 +93,7 @@ module Moxml
|
|
|
93
93
|
autoload :XmlUtils, "moxml/xml_utils"
|
|
94
94
|
autoload :XmlEmitter, "moxml/xml_emitter"
|
|
95
95
|
autoload :Materializer, "moxml/materializer"
|
|
96
|
+
autoload :Plan, "moxml/plan"
|
|
96
97
|
autoload :Adapter, "moxml/adapter"
|
|
97
98
|
autoload :XPath, "moxml/xpath"
|
|
98
99
|
autoload :SAX, "moxml/sax"
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# Moxml::Plan: shape-compiled materialization over the adapter's
|
|
6
|
+
# bulk row stream (leptris C snapshot) and the generic wrapper walk.
|
|
7
|
+
# Both paths must produce identical values.
|
|
8
|
+
RSpec.describe Moxml::Plan do
|
|
9
|
+
PlanRow = Struct.new(:id, :kind, :fields) # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
|
10
|
+
PlanCell = Struct.new(:name, :unit, :value) # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
|
|
11
|
+
|
|
12
|
+
let(:xml) do
|
|
13
|
+
<<~XML
|
|
14
|
+
<?xml version="1.0"?>
|
|
15
|
+
<catalog>
|
|
16
|
+
<record id="r0" kind="k0"><field name="f0" unit="u0">v 0.0</field><field name="f1" unit="u1">v 0.1</field></record>
|
|
17
|
+
<record id="r1" kind="k1"><field name="f0" unit="u0">v 1.0</field></record>
|
|
18
|
+
</catalog>
|
|
19
|
+
XML
|
|
20
|
+
end
|
|
21
|
+
let(:plan) do
|
|
22
|
+
described_class.new do
|
|
23
|
+
on("record") do |attrs, _text, fields|
|
|
24
|
+
PlanRow.new(attrs["id"], attrs["kind"], fields)
|
|
25
|
+
end
|
|
26
|
+
on("field") do |attrs, text, _kids|
|
|
27
|
+
PlanCell.new(attrs["name"], attrs["unit"], text)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
shared_examples "plan materialization" do |adapter_name|
|
|
33
|
+
let(:ctx) { Moxml.new(adapter_name) }
|
|
34
|
+
|
|
35
|
+
it "builds the typed shape" do
|
|
36
|
+
rows = plan.parse(xml, ctx)
|
|
37
|
+
|
|
38
|
+
expect(rows.size).to eq(2)
|
|
39
|
+
expect(rows[0].id).to eq("r0")
|
|
40
|
+
expect(rows[0].kind).to eq("k0")
|
|
41
|
+
expect(rows[0].fields.size).to eq(2)
|
|
42
|
+
expect(rows[0].fields[0]).to eq(PlanCell.new("f0", "u0", "v 0.0"))
|
|
43
|
+
expect(rows[0].fields[1]).to eq(PlanCell.new("f1", "u1", "v 0.1"))
|
|
44
|
+
expect(rows[1].fields[0].value).to eq("v 1.0")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "drops values of unmatched parents" do
|
|
48
|
+
orphans = described_class.new do
|
|
49
|
+
on("field") { |attrs, text, _| PlanCell.new(attrs["name"], attrs["unit"], text) }
|
|
50
|
+
end.parse(xml, ctx)
|
|
51
|
+
|
|
52
|
+
# fields match everywhere; no record handler keeps them
|
|
53
|
+
expect(orphans.size).to eq(3)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns top-level values for a matched root" do
|
|
57
|
+
roots = described_class.new do
|
|
58
|
+
on("catalog") { |_a, _t, kids| [:catalog, kids] }
|
|
59
|
+
on("record") { |attrs, _t, fields| [attrs["id"], fields] }
|
|
60
|
+
on("field") { |attrs, text, _| [attrs["name"], text] }
|
|
61
|
+
end.parse(xml, ctx)
|
|
62
|
+
|
|
63
|
+
expect(roots.size).to eq(1)
|
|
64
|
+
expect(roots[0][0]).to eq(:catalog)
|
|
65
|
+
expect(roots[0][1].size).to eq(2)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "handles depth-3 nesting" do
|
|
69
|
+
deep = described_class.new do
|
|
70
|
+
on("catalog") { |_a, _t, records| records }
|
|
71
|
+
on("record") { |attrs, _t, fields| [attrs["id"], fields] }
|
|
72
|
+
on("field") { |attrs, text, _| [attrs["name"], text] }
|
|
73
|
+
end.parse(xml, ctx)
|
|
74
|
+
|
|
75
|
+
expect(deep.size).to eq(1)
|
|
76
|
+
expect(deep[0]).to eq(
|
|
77
|
+
[["r0", [["f0", "v 0.0"], ["f1", "v 0.1"]]],
|
|
78
|
+
["r1", [["f0", "v 1.0"]]]],
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "is reusable across documents" do
|
|
83
|
+
first = plan.parse(xml, ctx)
|
|
84
|
+
second = plan.parse(xml, ctx)
|
|
85
|
+
|
|
86
|
+
expect(first).to eq(second)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "bulk path (leptris)" do
|
|
91
|
+
it_behaves_like "plan materialization", :leptris
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "generic path" do
|
|
95
|
+
it_behaves_like "plan materialization", :nokogiri
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "requires a handler block" do
|
|
99
|
+
expect { described_class.new.on("x") }
|
|
100
|
+
.to raise_error(ArgumentError, /requires a block/)
|
|
101
|
+
end
|
|
102
|
+
end
|
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.61
|
|
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-09-
|
|
11
|
+
date: 2026-09-18 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
|
|
@@ -214,6 +214,7 @@ files:
|
|
|
214
214
|
- lib/moxml/native_attachment/opal.rb
|
|
215
215
|
- lib/moxml/node.rb
|
|
216
216
|
- lib/moxml/node_set.rb
|
|
217
|
+
- lib/moxml/plan.rb
|
|
217
218
|
- lib/moxml/processing_instruction.rb
|
|
218
219
|
- lib/moxml/sax.rb
|
|
219
220
|
- lib/moxml/sax/block_handler.rb
|
|
@@ -487,6 +488,7 @@ files:
|
|
|
487
488
|
- spec/moxml/opal_oga_smoke_spec.rb
|
|
488
489
|
- spec/moxml/opal_rexml_adapter_spec.rb
|
|
489
490
|
- spec/moxml/opal_smoke_spec.rb
|
|
491
|
+
- spec/moxml/plan_spec.rb
|
|
490
492
|
- spec/moxml/processing_instruction_spec.rb
|
|
491
493
|
- spec/moxml/readonly_parse_spec.rb
|
|
492
494
|
- spec/moxml/sax/namespace_splitter_spec.rb
|