moxml 0.5.18 → 0.5.20
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/.github/workflows/rake.yml +5 -0
- data/Rakefile +3 -2
- data/docs/_pages/adapter-protocol.adoc +2 -2
- data/docs/_pages/adapters/leptris.adoc +9 -6
- data/lib/moxml/adapter/leptris/document_parts.rb +66 -0
- data/lib/moxml/adapter/leptris.rb +57 -3
- data/lib/moxml/document.rb +6 -1
- data/lib/moxml/node_set.rb +9 -2
- data/lib/moxml/version.rb +1 -1
- data/spec/integration/all_adapters_spec.rb +9 -1
- data/spec/integration/shared_examples/node_wrappers/declaration_behavior.rb +6 -0
- data/spec/moxml/adapter/leptris_spec.rb +44 -15
- data/spec/moxml/adapter/oga_spec.rb +3 -2
- data/spec/moxml/node_set_spec.rb +12 -0
- data/spec/performance/benchmark_spec.rb +9 -5
- 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: 59c8df5982f2b80280386a94385ceed9a65b1b80524772d914e19052a564aedd
|
|
4
|
+
data.tar.gz: 3e3432a24b65e60bddf345955d4454a5a459f4898693fa9037b71e8a052a6115
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3c041e77c6230d320e5a2779f08ffe29505780d4eac520ce1c12c83d5532102153787b592d999067daef2aabfedf08c5a1830d374baa224a9d795e20816faf69
|
|
7
|
+
data.tar.gz: 54367812683befdea88202d68a7c15c14b91184e3d350cef9510d94a8e6c769d307f311282041d2816626de181697a8f78b429cb7e71ae2b2d11638d34d0a1f9
|
data/.github/workflows/rake.yml
CHANGED
data/Rakefile
CHANGED
|
@@ -222,8 +222,9 @@ namespace :spec do
|
|
|
222
222
|
t.pattern = "spec/examples/**/*_spec.rb"
|
|
223
223
|
end
|
|
224
224
|
|
|
225
|
-
desc "Run performance benchmarks"
|
|
225
|
+
desc "Run performance benchmarks (sets RUN_PERFORMANCE=1)"
|
|
226
226
|
RSpec::Core::RakeTask.new(:performance) do |t|
|
|
227
|
+
ENV["RUN_PERFORMANCE"] = "1"
|
|
227
228
|
t.pattern = "spec/performance/**/*_spec.rb"
|
|
228
229
|
t.rspec_opts = "--tag performance"
|
|
229
230
|
end
|
|
@@ -288,4 +289,4 @@ namespace :opal do
|
|
|
288
289
|
end
|
|
289
290
|
end
|
|
290
291
|
|
|
291
|
-
task default:
|
|
292
|
+
task default: :spec
|
|
@@ -59,8 +59,8 @@ Serialization::
|
|
|
59
59
|
`serialize(node, options)`. Options: `indent`, `encoding`,
|
|
60
60
|
`declaration`, `no_declaration`, `expand_empty`, `line_ending`,
|
|
61
61
|
`indent_text` (the indent-unit string — Nokogiri and
|
|
62
|
-
leptris >= 1.9.45;
|
|
63
|
-
leptris
|
|
62
|
+
leptris >= 1.9.45; its element unit path
|
|
63
|
+
drops comments — leptris-ruby#115).
|
|
64
64
|
Entity-marker restoration is owned by the wrapper layer
|
|
65
65
|
(`Node#to_xml`), guarded by `entity_bearing?`.
|
|
66
66
|
|
|
@@ -31,9 +31,11 @@ ctx.config.adapter_name # => :leptris / :nokogiri / :rexml (Opal)
|
|
|
31
31
|
== Parse options
|
|
32
32
|
|
|
33
33
|
`noblanks: true`::
|
|
34
|
-
Drop whitespace-only text nodes
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
Drop whitespace-only text nodes — libxml2 `XML_PARSE_NOBLANKS`
|
|
35
|
+
semantics (issues #153/#156): boundary spaces of text-bearing
|
|
36
|
+
nodes survive. Implemented moxml-side (the engine flag trims
|
|
37
|
+
boundaries too — leptris/leptris/#677); requires a mutable
|
|
38
|
+
document, so `readonly: true` + `noblanks: true` raises.
|
|
37
39
|
|
|
38
40
|
`readonly: true`::
|
|
39
41
|
The binding memoizes every read and refuses mutations — the
|
|
@@ -68,9 +70,10 @@ implementation over one `leptris_node_traverse` call.
|
|
|
68
70
|
== Known limitations (tracked upstream)
|
|
69
71
|
|
|
70
72
|
* Tab indentation honors the indent-unit string on leptris >= 1.9.45
|
|
71
|
-
(`indent_text:`), byte-identical to Nokogiri
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
(`indent_text:`), byte-identical to Nokogiri including
|
|
74
|
+
text-bearing children since the engine fix (leptris/leptris#658,
|
|
75
|
+
1.9.46); comment children still diverge on the element unit path
|
|
76
|
+
(leptris-ruby#115).
|
|
74
77
|
* A PI preceding the DOCTYPE reorders on serialization — libleptris
|
|
75
78
|
normalizes DOCTYPE-first and the document node does not record its
|
|
76
79
|
relative position.
|
|
@@ -76,6 +76,9 @@ module Moxml
|
|
|
76
76
|
# only walks the root subtree, so declaration, DOCTYPE, PIs and
|
|
77
77
|
# document-level text are assembled around it explicitly.
|
|
78
78
|
def serialize_document(doc, options)
|
|
79
|
+
fast = fast_document_output(doc, options)
|
|
80
|
+
return fast if fast
|
|
81
|
+
|
|
79
82
|
# Nokogiri's document shape: every top-level part is
|
|
80
83
|
# newline-terminated, at any indent — declaration, DOCTYPE,
|
|
81
84
|
# document PIs, the root element, trailing newline after it.
|
|
@@ -108,6 +111,69 @@ module Moxml
|
|
|
108
111
|
parts.join
|
|
109
112
|
end
|
|
110
113
|
|
|
114
|
+
# Issue #158: the engine's whole-document serializer is one C
|
|
115
|
+
# call; the composed path serializes the root through the
|
|
116
|
+
# element face, which copies the subtree into a fresh document
|
|
117
|
+
# on every call (~4.5x slower end to end). The engine's output
|
|
118
|
+
# is byte-identical to the composed one EXCEPT epilog parts
|
|
119
|
+
# glue directly to the root — so the fast path declines
|
|
120
|
+
# whenever anything follows the root, any attachment overrides
|
|
121
|
+
# a part, or the engine's declaration line differs from the
|
|
122
|
+
# facade's canonical form (checked post-hoc: a source
|
|
123
|
+
# declaration carrying standalone or another version would
|
|
124
|
+
# diverge).
|
|
125
|
+
def fast_document_output(doc, options)
|
|
126
|
+
return nil unless LIBXML2_LAYOUT_PARITY
|
|
127
|
+
return nil if attachments.get(doc, :declaration) ||
|
|
128
|
+
attachments.get(doc, :doctype) ||
|
|
129
|
+
attachments.get(doc, :document_text) ||
|
|
130
|
+
attachments.get(doc, :entity_markers)
|
|
131
|
+
|
|
132
|
+
include_decl = !options[:no_declaration] && options.fetch(:declaration) do
|
|
133
|
+
document_has_declaration?(doc)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
root_seen = false
|
|
137
|
+
doc.children.each do |child|
|
|
138
|
+
if child.is_a?(::Leptris::XML::Element)
|
|
139
|
+
return nil if root_seen
|
|
140
|
+
|
|
141
|
+
root_seen = true
|
|
142
|
+
elsif root_seen
|
|
143
|
+
# Epilog parts glue directly to the root in the engine's
|
|
144
|
+
# document output — compose those.
|
|
145
|
+
return nil
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The engine's subset serializer mangles every declaration
|
|
150
|
+
# after the first (loses its "<") — moxml's own formatter is
|
|
151
|
+
# correct, so multi-declaration subsets compose.
|
|
152
|
+
dt = doc.doctype
|
|
153
|
+
if dt&.class&.method_defined?(:internal_subset) &&
|
|
154
|
+
(subset = dt.internal_subset) && subset.scan("<!").size > 1
|
|
155
|
+
return nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
kwargs = {
|
|
159
|
+
indent: options.fetch(:indent, 0),
|
|
160
|
+
no_decl: !include_decl,
|
|
161
|
+
encoding: options[:encoding],
|
|
162
|
+
}
|
|
163
|
+
if INDENT_UNIT_SUPPORTED && options[:indent_text].is_a?(String)
|
|
164
|
+
kwargs[:indent_text] = options[:indent_text]
|
|
165
|
+
end
|
|
166
|
+
output = doc.to_xml(**kwargs)
|
|
167
|
+
return nil if output.nil? || output.empty?
|
|
168
|
+
|
|
169
|
+
if include_decl && !output.start_with?(default_declaration_xml(doc, options))
|
|
170
|
+
return nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
output << "\n" unless output.end_with?("\n")
|
|
174
|
+
output
|
|
175
|
+
end
|
|
176
|
+
|
|
111
177
|
def native_doctype_xml(doc)
|
|
112
178
|
dt = doc.doctype
|
|
113
179
|
return nil unless dt
|
|
@@ -106,6 +106,18 @@ module Moxml
|
|
|
106
106
|
ctx = _context || Context.new(:leptris)
|
|
107
107
|
doc = Document.new(native_doc, ctx)
|
|
108
108
|
|
|
109
|
+
if options[:noblanks]
|
|
110
|
+
# The engine's DROP_WS_TEXT trims boundary whitespace of
|
|
111
|
+
# NON-blank text nodes at parse (leptris/leptris#677), so
|
|
112
|
+
# the flag is never forwarded — blanks drop here instead.
|
|
113
|
+
if options[:readonly] == true
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"noblanks: true requires a mutable document (readonly: true freezes the tree at parse)"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
strip_blank_text_nodes!(native_doc)
|
|
119
|
+
end
|
|
120
|
+
|
|
109
121
|
record_source_declaration(native_doc, processed)
|
|
110
122
|
attachments.set(native_doc, :entity_markers, entity_markers)
|
|
111
123
|
attachments.set(native_doc, :parse_errors, recover_errors) if recover_errors
|
|
@@ -116,15 +128,57 @@ module Moxml
|
|
|
116
128
|
# nil when no parse flag is requested — the binding treats a
|
|
117
129
|
# nil options hash as plain defaults (matching libxml2/
|
|
118
130
|
# Nokogiri semantics: blanks kept, no ATTLIST defaults).
|
|
119
|
-
# noblanks
|
|
120
|
-
#
|
|
131
|
+
# noblanks is deliberately absent: the engine's
|
|
132
|
+
# LEPTRIS_PARSE_DROP_WS_TEXT trims boundary whitespace of
|
|
133
|
+
# non-blank text nodes too (leptris/leptris#677), so moxml
|
|
134
|
+
# implements it itself after parse — see
|
|
135
|
+
# strip_blank_text_nodes!.
|
|
121
136
|
def parse_flags(options)
|
|
122
137
|
flags = 0
|
|
123
138
|
flags |= ::Leptris::XML::ParseOptions::DTDATTR if options[:dtdattr] == true
|
|
124
|
-
flags |= ::Leptris::XML::ParseOptions::NOBLANKS if options[:noblanks] == true
|
|
125
139
|
flags.zero? ? nil : ::Leptris::XML::ParseOptions.new(flags)
|
|
126
140
|
end
|
|
127
141
|
|
|
142
|
+
# XML whitespace exactly — \v and \f are not XML space.
|
|
143
|
+
BLANK_TEXT_RE = /\A[ \t\r\n]*\z/
|
|
144
|
+
|
|
145
|
+
# libxml2's XML_PARSE_NOBLANKS semantics (issues #153/#156):
|
|
146
|
+
# drop WHOLLY-whitespace text nodes; the boundary spaces of
|
|
147
|
+
# text-bearing nodes stay (mixed content cannot be
|
|
148
|
+
# re-indented). Raw-pointer walk over the root subtree — the
|
|
149
|
+
# same batch child-pointer pattern the materializer uses.
|
|
150
|
+
def strip_blank_text_nodes!(doc)
|
|
151
|
+
binding_ffi = ::Leptris::XML::FFI
|
|
152
|
+
root = binding_ffi.leptris_document_root(doc.c_ptr)
|
|
153
|
+
return unless root && !root.null?
|
|
154
|
+
|
|
155
|
+
strip_blanks_under(root, binding_ffi, ::FFI::MemoryPointer.new(:pointer, 64), 64)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def strip_blanks_under(ptr, binding_ffi, buf, capacity)
|
|
159
|
+
count = binding_ffi.leptris_node_children(ptr, buf, capacity)
|
|
160
|
+
while count == capacity
|
|
161
|
+
capacity *= 4
|
|
162
|
+
buf = ::FFI::MemoryPointer.new(:pointer, capacity)
|
|
163
|
+
count = binding_ffi.leptris_node_children(ptr, buf, capacity)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Snapshot before unlinking or recursing — both reuse the
|
|
167
|
+
# buffer for the next level.
|
|
168
|
+
children = buf.read_array_of_pointer(count)
|
|
169
|
+
children.each do |child|
|
|
170
|
+
case binding_ffi.leptris_node_get_type(child)
|
|
171
|
+
when ::Leptris::XML::FFI::NODE_ELEMENT
|
|
172
|
+
strip_blanks_under(child, binding_ffi, buf, capacity)
|
|
173
|
+
when ::Leptris::XML::FFI::NODE_TEXT
|
|
174
|
+
content = binding_ffi.leptris_text_node_get_content(child)
|
|
175
|
+
next unless content.match?(BLANK_TEXT_RE)
|
|
176
|
+
|
|
177
|
+
binding_ffi.check_status(binding_ffi.leptris_node_unlink(child))
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
128
182
|
def parse_errors(native_doc)
|
|
129
183
|
attachments.get(native_doc, :parse_errors) || NO_PARSE_ERRORS
|
|
130
184
|
end
|
data/lib/moxml/document.rb
CHANGED
|
@@ -104,7 +104,12 @@ module Moxml
|
|
|
104
104
|
node = prepare_node(node)
|
|
105
105
|
|
|
106
106
|
if node.is_a?(Declaration)
|
|
107
|
-
#
|
|
107
|
+
# A proper XML document carries at most one declaration
|
|
108
|
+
# (issue #23).
|
|
109
|
+
if @has_xml_declaration || adapter.has_declaration?(@native, self)
|
|
110
|
+
raise Moxml::ValidationError, "Document already has an XML declaration"
|
|
111
|
+
end
|
|
112
|
+
|
|
108
113
|
@has_xml_declaration = true
|
|
109
114
|
adapter.add_child(@native, node.native)
|
|
110
115
|
elsif root && !node.is_a?(ProcessingInstruction) && !node.is_a?(Comment) && !node.is_a?(Doctype)
|
data/lib/moxml/node_set.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Moxml
|
|
|
4
4
|
class NodeSet
|
|
5
5
|
include Enumerable
|
|
6
6
|
|
|
7
|
-
attr_reader :
|
|
7
|
+
attr_reader :context
|
|
8
8
|
|
|
9
9
|
def initialize(nodes, context, parent_node = nil)
|
|
10
10
|
@nodes = Array(nodes)
|
|
@@ -13,6 +13,13 @@ module Moxml
|
|
|
13
13
|
@parent_node = parent_node
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# The raw native nodes — adapter-internal work only (issue #26).
|
|
17
|
+
# The public surface is wrapper-only: every Enumerable access
|
|
18
|
+
# goes through #each/#[]/#to_a, which wrap.
|
|
19
|
+
def native_nodes
|
|
20
|
+
@nodes
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
def each
|
|
17
24
|
return to_enum(:each) unless block_given?
|
|
18
25
|
|
|
@@ -64,7 +71,7 @@ module Moxml
|
|
|
64
71
|
end
|
|
65
72
|
|
|
66
73
|
def +(other)
|
|
67
|
-
self.class.new(@nodes + other.
|
|
74
|
+
self.class.new(@nodes + other.native_nodes, @context, @parent_node)
|
|
68
75
|
end
|
|
69
76
|
|
|
70
77
|
def <<(node)
|
data/lib/moxml/version.rb
CHANGED
|
@@ -59,7 +59,15 @@ RSpec.describe "Cross-adapter integration" do
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
all_shared_examples.each do |shared_example_name|
|
|
62
|
-
|
|
62
|
+
# The performance battery runs only under RUN_PERFORMANCE /
|
|
63
|
+
# `rake spec:performance` (issue #45) - tagged at inclusion:
|
|
64
|
+
# definition-site metadata on the shared group does not
|
|
65
|
+
# propagate through this indirection.
|
|
66
|
+
if shared_example_name == "Performance Examples"
|
|
67
|
+
it_behaves_like shared_example_name, performance: true
|
|
68
|
+
else
|
|
69
|
+
it_behaves_like shared_example_name
|
|
70
|
+
end
|
|
63
71
|
end
|
|
64
72
|
end
|
|
65
73
|
end
|
|
@@ -101,5 +101,11 @@ RSpec.shared_examples "Moxml::Declaration" do
|
|
|
101
101
|
declaration.remove
|
|
102
102
|
expect(doc.to_xml).not_to include("<?xml")
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
it "rejects a second declaration (issue #23)" do
|
|
106
|
+
doc.add_child(doc.create_declaration("1.0"))
|
|
107
|
+
expect { doc.add_child(doc.create_declaration("1.0")) }
|
|
108
|
+
.to raise_error(Moxml::ValidationError, /already has an XML declaration/)
|
|
109
|
+
end
|
|
104
110
|
end
|
|
105
111
|
end
|
|
@@ -185,31 +185,60 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
185
185
|
expect(doc.to_xml).to include("<?renamed changed?>")
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
it "matches raw Nokogiri with tab units
|
|
188
|
+
it "matches raw Nokogiri with tab units" do
|
|
189
189
|
skip "requires the indent unit (leptris 1.9.45+)" unless described_class::INDENT_UNIT_SUPPORTED
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
191
|
+
# Text-bearing children included since the engine fix for
|
|
192
|
+
# leptris/leptris#658 (leptris 1.9.46). Comment children are
|
|
193
|
+
# excluded: the binding's element+unit path drops them
|
|
194
|
+
# (leptris-ruby#115).
|
|
195
|
+
sources = [
|
|
196
|
+
%(<r><a><b/></a><c><d/><e/></c></r>),
|
|
197
|
+
%(<root><child>content</child><empty/></root>),
|
|
198
|
+
%(<r>text <b>bold</b> tail</r>),
|
|
199
|
+
]
|
|
200
|
+
sources.each do |source|
|
|
201
|
+
target = Nokogiri::XML(source).to_xml(indent: 1, indent_text: "\t", encoding: "UTF-8")
|
|
202
|
+
output = ctx.parse(source).to_xml(
|
|
203
|
+
indent: 1, declaration: true, expand_empty: false,
|
|
204
|
+
encoding: "UTF-8", indent_text: "\t"
|
|
205
|
+
)
|
|
206
|
+
expect(output).to eq(target), "tab parity failed for #{source}"
|
|
207
|
+
end
|
|
198
208
|
end
|
|
199
209
|
|
|
200
|
-
it "drops space-only text nodes with noblanks and matches Nokogiri (
|
|
201
|
-
|
|
210
|
+
it "drops space-only text nodes with noblanks and matches Nokogiri (issues #153/#156)" do
|
|
211
|
+
sources = [
|
|
212
|
+
%(<a><t>1</t> <n/></a>), # blank filler between tags
|
|
213
|
+
%(<p> leading</p>), # boundary space must survive
|
|
214
|
+
%(<p><b>b</b> after</p>), # element-to-text space must survive
|
|
215
|
+
%(<p>trailing </p>), # trailing space must survive
|
|
216
|
+
%(<r>\n <a>x <b>y</b> z</a>\n <c/>\n</r>),
|
|
217
|
+
]
|
|
202
218
|
recipe = {
|
|
203
219
|
indent: 2, declaration: true, expand_empty: false, encoding: "UTF-8"
|
|
204
220
|
}
|
|
205
|
-
target = Nokogiri::XML(source, &:noblanks).to_xml(indent: 2, encoding: "UTF-8")
|
|
206
221
|
|
|
207
|
-
|
|
222
|
+
sources.each do |source|
|
|
223
|
+
target = Nokogiri::XML(source, &:noblanks).to_xml(indent: 2, encoding: "UTF-8")
|
|
224
|
+
|
|
225
|
+
doc = ctx.parse(source, noblanks: true)
|
|
226
|
+
expect(doc.to_xml(recipe)).to eq(target), "noblanks parity failed for #{source}"
|
|
227
|
+
|
|
228
|
+
via_nokogiri = Moxml.new(:nokogiri).parse(source, noblanks: true).to_xml(recipe)
|
|
229
|
+
expect(via_nokogiri).to eq(target)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "leaves no text nodes on a noblanks tree of blank filler" do
|
|
234
|
+
doc = ctx.parse(%(<a><t>1</t> <n/></a>), noblanks: true)
|
|
208
235
|
expect(doc.root.children.to_a.select(&:text?)).to be_empty
|
|
209
|
-
|
|
236
|
+
end
|
|
210
237
|
|
|
211
|
-
|
|
212
|
-
|
|
238
|
+
it "refuses noblanks on readonly parses" do
|
|
239
|
+
# The strip mutates the tree; readonly freezes it at parse.
|
|
240
|
+
expect { ctx.parse("<a/>", noblanks: true, readonly: true) }
|
|
241
|
+
.to raise_error(ArgumentError, /noblanks.*mutable/)
|
|
213
242
|
end
|
|
214
243
|
|
|
215
244
|
it "reports the tracked native for a re-added document PI" do
|
|
@@ -13,12 +13,13 @@ RSpec.describe Moxml::Adapter::Oga do
|
|
|
13
13
|
it_behaves_like "xml adapter"
|
|
14
14
|
|
|
15
15
|
describe "serialization" do
|
|
16
|
-
it "
|
|
16
|
+
it "rejects a repeated XML declaration instead of duplicating it (issue #23)" do
|
|
17
17
|
context = Moxml::Context.new(:oga)
|
|
18
18
|
doc = context.create_document
|
|
19
19
|
|
|
20
20
|
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
|
21
|
-
doc.add_child(doc.create_declaration("1.0", "UTF-8"))
|
|
21
|
+
expect { doc.add_child(doc.create_declaration("1.0", "UTF-8")) }
|
|
22
|
+
.to raise_error(Moxml::ValidationError)
|
|
22
23
|
doc.add_child(doc.create_element("root"))
|
|
23
24
|
|
|
24
25
|
serialized = doc.to_xml
|
data/spec/moxml/node_set_spec.rb
CHANGED
|
@@ -36,4 +36,16 @@ RSpec.describe Moxml::NodeSet do
|
|
|
36
36
|
expect(nodes).to be_empty
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
describe "public surface (issue #26)" do
|
|
41
|
+
it "exposes raw natives only through native_nodes" do
|
|
42
|
+
children = doc.root.children
|
|
43
|
+
expect(children.native_nodes).not_to include(a_kind_of(Moxml::Node))
|
|
44
|
+
expect(children).not_to respond_to(:nodes)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "wraps through the Enumerable surface" do
|
|
48
|
+
expect(doc.root.children.map(&:class).uniq).to eq([Moxml::Element])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
39
51
|
end
|
|
@@ -4,9 +4,13 @@ require "benchmark"
|
|
|
4
4
|
require "benchmark/ips"
|
|
5
5
|
|
|
6
6
|
RSpec.shared_examples "Performance Examples" do
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
# Issue #45: benchmarks never run inside the default suites - only
|
|
8
|
+
# under RUN_PERFORMANCE=1 (the CI perf step and
|
|
9
|
+
# rake spec:performance). RSpec tag exclusion proved unreliable
|
|
10
|
+
# through the shared-example indirection, so this is a hard env gate.
|
|
11
|
+
if ENV["SKIP_BENCHMARKS"] || !ENV["RUN_PERFORMANCE"]
|
|
12
|
+
it "skips benchmarks unless RUN_PERFORMANCE is set" do
|
|
13
|
+
skip "Benchmarks run only with RUN_PERFORMANCE=1 (rake spec:performance)"
|
|
10
14
|
end
|
|
11
15
|
else
|
|
12
16
|
let(:context) { Moxml.new }
|
|
@@ -41,7 +45,7 @@ RSpec.shared_examples "Performance Examples" do
|
|
|
41
45
|
}
|
|
42
46
|
end
|
|
43
47
|
|
|
44
|
-
it "meets Parser performance threshold" do
|
|
48
|
+
it "meets Parser performance threshold", :performance do
|
|
45
49
|
result = nil
|
|
46
50
|
report = Benchmark.ips do |x|
|
|
47
51
|
x.config(time: 5, warmup: 2)
|
|
@@ -54,7 +58,7 @@ RSpec.shared_examples "Performance Examples" do
|
|
|
54
58
|
expect(ips).to be >= threshold, message
|
|
55
59
|
end
|
|
56
60
|
|
|
57
|
-
it "meets Serializer performance threshold" do
|
|
61
|
+
it "meets Serializer performance threshold", :performance do
|
|
58
62
|
report = Benchmark.ips do |x|
|
|
59
63
|
x.config(time: 5, warmup: 2)
|
|
60
64
|
x.report("Serializer") { _ = doc.to_xml }
|
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.20
|
|
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-
|
|
11
|
+
date: 2026-09-01 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
|