moxml 0.5.7 → 0.5.9
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 +100 -9
- data/lib/moxml/context.rb +7 -0
- data/lib/moxml/document.rb +7 -0
- data/lib/moxml/entity.rb +21 -6
- data/lib/moxml/materializer.rb +90 -0
- data/lib/moxml/node.rb +7 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml.rb +1 -0
- data/spec/moxml/materializer_spec.rb +82 -0
- data/spec/moxml/readonly_parse_spec.rb +27 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 011d9a8566b2697d8c01a17a1a4f702ace315361dae3b246f8ed5ad5fb79b3e8
|
|
4
|
+
data.tar.gz: 286839c9aca15e4027d4b01bcdfb432c2036f10234fb408cf10f88e5dcbeac0d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f53698ba5eee597fb6bda3355c1f7dfe0a33442cd3e67a6aedc73a155a23e80f049f2fc63aed6871b6cb2e85723719d7e7f318cc140ef178e5dde4bcd493a095
|
|
7
|
+
data.tar.gz: 44d1afe3880ebc55dd214a0a7f2b48c69151504b6c5a16646afd88cf4a885184132db9a590552c2531bfda8ea84a92e8fac50a7cfae75379e215582acf0f5bac
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -189,6 +189,15 @@ namespace_validation_mode: :strict)
|
|
|
189
189
|
true
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
# Whether the engine offers a bulk materialization path for
|
|
193
|
+
# Materializer (issue #132). When true, materialize_records
|
|
194
|
+
# yields flattened records for the subtree; returning nil
|
|
195
|
+
# (e.g. for document shapes the bulk path cannot express)
|
|
196
|
+
# falls back to the generic wrapper walk.
|
|
197
|
+
def bulk_materialize?
|
|
198
|
+
false
|
|
199
|
+
end
|
|
200
|
+
|
|
192
201
|
# Check if the native document has an XML declaration
|
|
193
202
|
# @param native_doc the native document object
|
|
194
203
|
# @param wrapper [Moxml::Document] the wrapper with has_xml_declaration flag
|
|
@@ -30,10 +30,15 @@ module Moxml
|
|
|
30
30
|
|
|
31
31
|
def parse(xml, options = {}, _context = nil)
|
|
32
32
|
xml_string = xml.is_a?(IO) || xml.is_a?(StringIO) ? xml.read : xml.to_s
|
|
33
|
-
|
|
33
|
+
# The marker flag rides preprocess's own `&` scan — no
|
|
34
|
+
# second full-buffer probe (issue #132 parse-side note).
|
|
35
|
+
processed, entity_markers = Entity.preprocess_with_marker_flag(xml_string)
|
|
34
36
|
|
|
37
|
+
# readonly: true (issue #133): the binding memoizes reads and
|
|
38
|
+
# refuses mutations — the parse-and-read lifecycle for
|
|
39
|
+
# multi-pass consumers (comparison, diff, signature).
|
|
35
40
|
native_doc = begin
|
|
36
|
-
::Leptris::XML::Document.parse(processed)
|
|
41
|
+
::Leptris::XML::Document.parse(processed, readonly: options[:readonly] == true)
|
|
37
42
|
rescue ::Leptris::XML::ParseError => e
|
|
38
43
|
# libleptris has no recovery mode that survives unclosed
|
|
39
44
|
# tags; non-strict callers get an empty document, matching
|
|
@@ -46,7 +51,7 @@ module Moxml
|
|
|
46
51
|
doc = Document.new(native_doc, ctx)
|
|
47
52
|
|
|
48
53
|
record_source_declaration(native_doc, processed)
|
|
49
|
-
attachments.set(native_doc, :entity_markers,
|
|
54
|
+
attachments.set(native_doc, :entity_markers, entity_markers)
|
|
50
55
|
|
|
51
56
|
doc
|
|
52
57
|
end
|
|
@@ -224,18 +229,104 @@ module Moxml
|
|
|
224
229
|
end
|
|
225
230
|
end
|
|
226
231
|
|
|
232
|
+
# Bulk materialization (issue #132): leptris_node_traverse
|
|
233
|
+
# walks the subtree with one FFI call; the only per-node cost
|
|
234
|
+
# is the C->Ruby callback and the property reads below. No
|
|
235
|
+
# Moxml::Node or Attribute wrapper is allocated.
|
|
236
|
+
def bulk_materialize?
|
|
237
|
+
true
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def materialize_records(native, &block)
|
|
241
|
+
doc = native.is_a?(::Leptris::XML::Document) ? native : native.document
|
|
242
|
+
# Marker-bearing text needs the split pipeline (children-level
|
|
243
|
+
# ER expansion); the bulk path has no marker handling.
|
|
244
|
+
return nil if doc.nil? || attachments.get(doc, :entity_markers)
|
|
245
|
+
|
|
246
|
+
root = native.is_a?(::Leptris::XML::Document) ? native.root : native
|
|
247
|
+
return nil if root.nil?
|
|
248
|
+
|
|
249
|
+
depth_memo = {}.compare_by_identity
|
|
250
|
+
root.traverse do |node|
|
|
251
|
+
record = case node
|
|
252
|
+
when ::Leptris::XML::Element
|
|
253
|
+
element_material_record(node, depth_memo)
|
|
254
|
+
when ::Leptris::XML::CDATA
|
|
255
|
+
# CDATA < Text in the binding: this arm must come
|
|
256
|
+
# first or CDATA content reports as text.
|
|
257
|
+
text_material_record(:cdata, node.content, node, depth_memo)
|
|
258
|
+
when ::Leptris::XML::Text
|
|
259
|
+
text_material_record(:text, node.content, node, depth_memo)
|
|
260
|
+
when ::Leptris::XML::Comment
|
|
261
|
+
text_material_record(:comment, node.content, node, depth_memo)
|
|
262
|
+
when ::Leptris::XML::ProcessingInstruction
|
|
263
|
+
text_material_record(:processing_instruction, node.content, node, depth_memo)
|
|
264
|
+
.merge(qname: node.target)
|
|
265
|
+
end
|
|
266
|
+
yield(record) if record
|
|
267
|
+
end
|
|
268
|
+
true
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def element_material_record(node, depth_memo)
|
|
272
|
+
attributes = node.each_attribute.map do |attr|
|
|
273
|
+
# Local name + separate prefix, matching the generic
|
|
274
|
+
# path's resolver semantics (moxml's canonical shape).
|
|
275
|
+
name = attr.name
|
|
276
|
+
prefix = attr.prefix
|
|
277
|
+
name = name.split(":", 2)[1] || name if prefix
|
|
278
|
+
[name, attr.value, attr.namespace_uri, prefix]
|
|
279
|
+
end
|
|
280
|
+
ns = node.namespace
|
|
281
|
+
{
|
|
282
|
+
kind: :element,
|
|
283
|
+
qname: node.name,
|
|
284
|
+
prefix: node.prefix,
|
|
285
|
+
namespace_uri: ns&.href,
|
|
286
|
+
attributes: attributes,
|
|
287
|
+
text: nil,
|
|
288
|
+
depth: material_depth(node, depth_memo),
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def text_material_record(kind, text, node, depth_memo)
|
|
293
|
+
{
|
|
294
|
+
kind: kind,
|
|
295
|
+
qname: nil,
|
|
296
|
+
prefix: nil,
|
|
297
|
+
namespace_uri: nil,
|
|
298
|
+
attributes: Materializer::EMPTY_ATTRIBUTES,
|
|
299
|
+
text: text,
|
|
300
|
+
depth: material_depth(node, depth_memo),
|
|
301
|
+
}
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Binding wrappers are address-stable and #parent is memoized,
|
|
305
|
+
# so each edge is fetched once; depths resolve through the
|
|
306
|
+
# identity-keyed memo.
|
|
307
|
+
def material_depth(node, memo)
|
|
308
|
+
memo[node] ||= begin
|
|
309
|
+
parent = node.parent
|
|
310
|
+
parent.nil? || parent.is_a?(::Leptris::XML::Document) ? 0 : material_depth(parent, memo) + 1
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
227
314
|
def node_type(node)
|
|
315
|
+
# Frequency-ordered: elements and text dominate every real
|
|
316
|
+
# document, and Node.wrap dispatches here once per cold
|
|
317
|
+
# wrap. CDATA must precede Text (CDATA < Text in the
|
|
318
|
+
# binding).
|
|
228
319
|
case node
|
|
320
|
+
when ::Leptris::XML::Element then :element
|
|
321
|
+
when ::Leptris::XML::CDATA then :cdata
|
|
322
|
+
when ::Leptris::XML::Text, CustomizedLeptris::TextSegment then :text
|
|
323
|
+
when ::Leptris::XML::Attr then :attribute
|
|
324
|
+
when ::Leptris::XML::Comment then :comment
|
|
325
|
+
when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then :processing_instruction
|
|
229
326
|
when ::Leptris::XML::Document then :document
|
|
230
327
|
when ::Leptris::XML::DocType, CustomizedLeptris::Doctype then :doctype
|
|
231
328
|
when CustomizedLeptris::Declaration then :declaration
|
|
232
329
|
when CustomizedLeptris::EntityReference then :entity_reference
|
|
233
|
-
when ::Leptris::XML::CDATA then :cdata
|
|
234
|
-
when ::Leptris::XML::Comment then :comment
|
|
235
|
-
when ::Leptris::XML::ProcessingInstruction, CustomizedLeptris::DocumentPI then :processing_instruction
|
|
236
|
-
when ::Leptris::XML::Text, CustomizedLeptris::TextSegment then :text
|
|
237
|
-
when ::Leptris::XML::Element then :element
|
|
238
|
-
when ::Leptris::XML::Attr then :attribute
|
|
239
330
|
else :unknown
|
|
240
331
|
end
|
|
241
332
|
end
|
data/lib/moxml/context.rb
CHANGED
|
@@ -74,6 +74,13 @@ module Moxml
|
|
|
74
74
|
doc
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Parse then flatten in one call — see Moxml::Materializer
|
|
78
|
+
# (issue #132). Yields records; returns an Enumerator when no
|
|
79
|
+
# block is given.
|
|
80
|
+
def materialize(xml, options = {}, &block)
|
|
81
|
+
parse(xml, options).materialize(&block)
|
|
82
|
+
end
|
|
83
|
+
|
|
77
84
|
# Parse XML using SAX (event-driven) parsing
|
|
78
85
|
#
|
|
79
86
|
# SAX parsing is memory-efficient and suitable for large XML files.
|
data/lib/moxml/document.rb
CHANGED
|
@@ -24,6 +24,13 @@ module Moxml
|
|
|
24
24
|
root_element ? Moxml::Node.wrap(root_element, context) : nil
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
# Materialize the root subtree — see Moxml::Materializer.
|
|
28
|
+
def materialize(&block)
|
|
29
|
+
return to_enum(:materialize) unless block
|
|
30
|
+
|
|
31
|
+
root&.materialize(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
27
34
|
def create_element(name)
|
|
28
35
|
Element.new(adapter.create_element(name, owner_doc: @native), context)
|
|
29
36
|
end
|
data/lib/moxml/entity.rb
CHANGED
|
@@ -55,9 +55,12 @@ module Moxml
|
|
|
55
55
|
module_function
|
|
56
56
|
|
|
57
57
|
# Replace non-standard entity references with markers before
|
|
58
|
-
# parsing. Always returns a UTF-8 encoded string
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
# parsing. Always returns a UTF-8 encoded string, and reports
|
|
59
|
+
# whether the result can contain markers: the flag rides the
|
|
60
|
+
# same `&` scan, so callers needing it (leptris parse) avoid a
|
|
61
|
+
# second full-buffer multibyte probe.
|
|
62
|
+
def preprocess_with_marker_flag(xml)
|
|
63
|
+
return ["", false] if xml.nil?
|
|
61
64
|
|
|
62
65
|
str = if xml.encoding == Encoding::BINARY
|
|
63
66
|
# Binary strings are assumed to be UTF-8. If the bytes are
|
|
@@ -78,11 +81,23 @@ module Moxml
|
|
|
78
81
|
# Fast path: no `&` means no entity references to mark — skip
|
|
79
82
|
# the regex scan and string allocation entirely. The vast
|
|
80
83
|
# majority of XML payloads contain no entity references.
|
|
81
|
-
return str unless str.include?("&")
|
|
84
|
+
return [str, false] unless str.include?("&")
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
marked = false
|
|
87
|
+
processed = str.gsub(NAME_RE) do |match|
|
|
88
|
+
name = ::Regexp.last_match(1)
|
|
89
|
+
if STANDARD_ENTITIES.include?(name)
|
|
90
|
+
match
|
|
91
|
+
else
|
|
92
|
+
marked = true
|
|
93
|
+
"#{MARKER}#{name};"
|
|
94
|
+
end
|
|
85
95
|
end
|
|
96
|
+
[processed, marked]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def preprocess_entities(xml)
|
|
100
|
+
preprocess_with_marker_flag(xml)[0]
|
|
86
101
|
end
|
|
87
102
|
|
|
88
103
|
# Resolve numeric (&#NN; / &#xNN;) and the five standard named
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moxml
|
|
4
|
+
# Flattened subtree records for conversion-style consumers
|
|
5
|
+
# (issue #132): one record per node, emitted in post-order (children
|
|
6
|
+
# before parents — build with a stack), carrying everything a
|
|
7
|
+
# consumer tree needs without allocating Moxml::Node or
|
|
8
|
+
# Moxml::Attribute wrappers.
|
|
9
|
+
#
|
|
10
|
+
# context.materialize(xml) { |r| ... }
|
|
11
|
+
# document.materialize.each { |r| ... } # Enumerator
|
|
12
|
+
#
|
|
13
|
+
# Record shape:
|
|
14
|
+
# { kind: :element|:text|:cdata|:comment|:processing_instruction,
|
|
15
|
+
# qname: "tag"|"pi-target"|nil, prefix: "p"|nil,
|
|
16
|
+
# namespace_uri: "urn:x"|nil,
|
|
17
|
+
# attributes: [[name, value, namespace_uri, prefix], ...],
|
|
18
|
+
# text: String|nil, depth: Integer }
|
|
19
|
+
#
|
|
20
|
+
# Adapters with a bulk path (leptris: one leptris_node_traverse FFI
|
|
21
|
+
# call for the whole subtree) answer bulk_materialize?; the others
|
|
22
|
+
# walk the wrapper tree generically.
|
|
23
|
+
module Materializer
|
|
24
|
+
# Shared frozen empty attribute list for non-element records;
|
|
25
|
+
# adapters' bulk paths reference it too.
|
|
26
|
+
EMPTY_ATTRIBUTES = [].freeze
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
def materialize(node, &block)
|
|
31
|
+
adapter = node.context.config.adapter
|
|
32
|
+
return enum_for(:materialize, node) unless block
|
|
33
|
+
|
|
34
|
+
if adapter.bulk_materialize? && adapter.materialize_records(node.native, &block)
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
walk(node, 0, &block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Generic post-order walk over the wrapper tree. Works on every
|
|
42
|
+
# adapter; the fast bulk path exists where the engine offers one.
|
|
43
|
+
def walk(node, depth, &block)
|
|
44
|
+
case node
|
|
45
|
+
when Element
|
|
46
|
+
node.children.each { |child| walk(child, depth + 1, &block) }
|
|
47
|
+
yield(element_record(node, depth))
|
|
48
|
+
when Text
|
|
49
|
+
yield(text_record(:text, node.content, depth))
|
|
50
|
+
when Cdata
|
|
51
|
+
yield(text_record(:cdata, node.content, depth))
|
|
52
|
+
when Comment
|
|
53
|
+
yield(text_record(:comment, node.content, depth))
|
|
54
|
+
when ProcessingInstruction
|
|
55
|
+
yield(text_record(:processing_instruction, node.content, depth).merge(qname: node.target))
|
|
56
|
+
when EntityReference
|
|
57
|
+
yield(text_record(:entity_reference, "&#{node.name};", depth).merge(qname: node.name))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def element_record(element, depth)
|
|
62
|
+
attributes = element.attributes.map do |attr|
|
|
63
|
+
ns = attr.namespace
|
|
64
|
+
[attr.name, attr.value, ns&.uri, ns&.prefix]
|
|
65
|
+
end
|
|
66
|
+
ns = element.namespace
|
|
67
|
+
{
|
|
68
|
+
kind: :element,
|
|
69
|
+
qname: element.name,
|
|
70
|
+
prefix: element.namespace_prefix,
|
|
71
|
+
namespace_uri: ns&.uri,
|
|
72
|
+
attributes: attributes,
|
|
73
|
+
text: nil,
|
|
74
|
+
depth: depth,
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def text_record(kind, text, depth)
|
|
79
|
+
{
|
|
80
|
+
kind: kind,
|
|
81
|
+
qname: nil,
|
|
82
|
+
prefix: nil,
|
|
83
|
+
namespace_uri: nil,
|
|
84
|
+
attributes: EMPTY_ATTRIBUTES,
|
|
85
|
+
text: text,
|
|
86
|
+
depth: depth,
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
data/lib/moxml/node.rb
CHANGED
|
@@ -124,6 +124,13 @@ module Moxml
|
|
|
124
124
|
result.is_a?(Array) ? NodeSet.new(result, context) : result
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
# Flattened post-order records for this subtree without
|
|
128
|
+
# allocating wrappers — see Moxml::Materializer (issue #132).
|
|
129
|
+
# Returns an Enumerator when no block is given.
|
|
130
|
+
def materialize(&block)
|
|
131
|
+
Materializer.materialize(self, &block)
|
|
132
|
+
end
|
|
133
|
+
|
|
127
134
|
def at_xpath(expression, namespaces = {})
|
|
128
135
|
Moxml::Node.wrap(adapter.at_xpath(@native, expression, namespaces),
|
|
129
136
|
context)
|
data/lib/moxml/version.rb
CHANGED
data/lib/moxml.rb
CHANGED
|
@@ -87,6 +87,7 @@ module Moxml
|
|
|
87
87
|
autoload :NativeAttachment, "moxml/native_attachment"
|
|
88
88
|
autoload :XmlUtils, "moxml/xml_utils"
|
|
89
89
|
autoload :XmlEmitter, "moxml/xml_emitter"
|
|
90
|
+
autoload :Materializer, "moxml/materializer"
|
|
90
91
|
autoload :Adapter, "moxml/adapter"
|
|
91
92
|
autoload :XPath, "moxml/xpath"
|
|
92
93
|
autoload :SAX, "moxml/sax"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Materializer do
|
|
6
|
+
let(:xml) do
|
|
7
|
+
<<~XML
|
|
8
|
+
<catalog xmlns="urn:c" xmlns:p="urn:p">
|
|
9
|
+
<book id="b1" p:lang="en"><title>T</title><!--n--><?pi x?><![CDATA[raw]]></book>
|
|
10
|
+
</catalog>
|
|
11
|
+
XML
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def records_for(adapter)
|
|
15
|
+
Moxml.new(adapter).materialize(xml).to_a
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "emits one record per node in post-order with depth, names, and attributes" do
|
|
19
|
+
records = records_for(:leptris)
|
|
20
|
+
|
|
21
|
+
expect(records.map { |r| [r[:kind], r[:depth]] }).to eq(
|
|
22
|
+
[
|
|
23
|
+
# post-order: whitespace before <book>, then the book subtree,
|
|
24
|
+
# then the trailing whitespace, then the catalog itself
|
|
25
|
+
[:text, 1],
|
|
26
|
+
[:text, 3], [:element, 2], [:comment, 2],
|
|
27
|
+
[:processing_instruction, 2], [:cdata, 2],
|
|
28
|
+
[:element, 1],
|
|
29
|
+
[:text, 1],
|
|
30
|
+
[:element, 0]
|
|
31
|
+
],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
book = records.find { |r| r[:qname] == "book" }
|
|
35
|
+
expect(book[:attributes]).to include(["id", "b1", nil, nil], ["lang", "en", "urn:p", "p"])
|
|
36
|
+
expect(book[:namespace_uri]).to eq("urn:c")
|
|
37
|
+
expect(book[:text]).to be_nil
|
|
38
|
+
|
|
39
|
+
title_text = records.find { |r| r[:kind] == :text && r[:text] == "T" }
|
|
40
|
+
expect(title_text[:attributes]).to eq([])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "uses the same record stream on the bulk path and the generic walk" do
|
|
44
|
+
skip "leptris not installed" unless Moxml::Adapter.available?(:leptris)
|
|
45
|
+
|
|
46
|
+
expect(records_for(:leptris)).to eq(records_for(:nokogiri))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "falls back to the generic walk for marker-bearing documents" do
|
|
50
|
+
entity_xml = "<r><a>pre post</a></r>"
|
|
51
|
+
records = Moxml.new(:leptris).materialize(entity_xml).to_a
|
|
52
|
+
|
|
53
|
+
expect(records.map { |r| [r[:kind], r[:text]] }).to eq(
|
|
54
|
+
[[:text, "pre"], [:entity_reference, " "], [:text, "post"],
|
|
55
|
+
[:element, nil], [:element, nil]],
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "materializes from any element subtree" do
|
|
60
|
+
doc = Moxml.new(:nokogiri).parse(xml)
|
|
61
|
+
title = doc.at_xpath("//*[local-name()='title']")
|
|
62
|
+
records = title.materialize.to_a
|
|
63
|
+
|
|
64
|
+
expect(records.map { |r| [r[:kind], r[:depth]] }).to eq([[:text, 1], [:element, 0]])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "allocates no wrappers on the bulk path" do
|
|
68
|
+
skip "leptris not installed" unless Moxml::Adapter.available?(:leptris)
|
|
69
|
+
|
|
70
|
+
ctx = Moxml.new(:leptris)
|
|
71
|
+
ctx.materialize(xml).to_a # warm
|
|
72
|
+
GC.start
|
|
73
|
+
before = GC.stat(:total_allocated_objects)
|
|
74
|
+
ctx.materialize(xml).to_a
|
|
75
|
+
allocated = GC.stat(:total_allocated_objects) - before
|
|
76
|
+
node_count = ctx.parse(xml).materialize.count
|
|
77
|
+
|
|
78
|
+
# Records themselves allocate (hash + FFI strings); the wrapper
|
|
79
|
+
# tree does not: far fewer allocations than 2 wrappers per node.
|
|
80
|
+
expect(allocated).to be < node_count * 20
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "readonly parsing" do
|
|
6
|
+
let(:xml) { '<r><a x="1">t</a></r>' }
|
|
7
|
+
|
|
8
|
+
it "parses and reads normally" do
|
|
9
|
+
doc = Moxml.new(:leptris).parse(xml, readonly: true)
|
|
10
|
+
|
|
11
|
+
expect(doc.root.name).to eq("r")
|
|
12
|
+
expect(doc.at_xpath("//a")["x"]).to eq("1")
|
|
13
|
+
expect(doc.at_xpath("//a").text).to eq("t")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "refuses mutations" do
|
|
17
|
+
doc = Moxml.new(:leptris).parse(xml, readonly: true)
|
|
18
|
+
|
|
19
|
+
expect { doc.root["y"] = "2" }.to raise_error(/ReadOnly/i)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "is a no-op concept on other adapters" do
|
|
23
|
+
doc = Moxml.new(:nokogiri).parse(xml, readonly: true)
|
|
24
|
+
|
|
25
|
+
expect(doc.root.name).to eq("r")
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -196,6 +196,7 @@ files:
|
|
|
196
196
|
- lib/moxml/entity_registry.rb
|
|
197
197
|
- lib/moxml/entity_registry_opal_data.rb
|
|
198
198
|
- lib/moxml/error.rb
|
|
199
|
+
- lib/moxml/materializer.rb
|
|
199
200
|
- lib/moxml/namespace.rb
|
|
200
201
|
- lib/moxml/native_attachment.rb
|
|
201
202
|
- lib/moxml/native_attachment/native.rb
|
|
@@ -454,6 +455,7 @@ files:
|
|
|
454
455
|
- spec/moxml/entity_spec.rb
|
|
455
456
|
- spec/moxml/error_spec.rb
|
|
456
457
|
- spec/moxml/lazy_parse_spec.rb
|
|
458
|
+
- spec/moxml/materializer_spec.rb
|
|
457
459
|
- spec/moxml/moxml_spec.rb
|
|
458
460
|
- spec/moxml/namespace_spec.rb
|
|
459
461
|
- spec/moxml/namespace_uri_validation_spec.rb
|
|
@@ -472,6 +474,7 @@ files:
|
|
|
472
474
|
- spec/moxml/opal_rexml_adapter_spec.rb
|
|
473
475
|
- spec/moxml/opal_smoke_spec.rb
|
|
474
476
|
- spec/moxml/processing_instruction_spec.rb
|
|
477
|
+
- spec/moxml/readonly_parse_spec.rb
|
|
475
478
|
- spec/moxml/sax/namespace_splitter_spec.rb
|
|
476
479
|
- spec/moxml/sax_entity_parity_spec.rb
|
|
477
480
|
- spec/moxml/sax_spec.rb
|