moxml 0.5.150 → 0.5.151
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/docs/_pages/ecosystem.adoc +2 -2
- data/lib/moxml/adapter/base.rb +7 -0
- data/lib/moxml/adapter/leptris.rb +55 -0
- data/lib/moxml/attribute_resolver.rb +4 -0
- data/lib/moxml/context.rb +25 -2
- data/lib/moxml/document.rb +11 -1
- data/lib/moxml/element.rb +13 -1
- data/lib/moxml/node.rb +29 -6
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +90 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d27f78bba118e07aa9790336f2d7cbc0545e48217fcaf7ec93b222a170cc398
|
|
4
|
+
data.tar.gz: 6095220fc10e1f483d1ee3aed9b83c70ea1a61f0f304e00f4863a7fd52aca90c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fec02ca86ee8bbe73cd220363316147d35f94c010399c3b8163bc616460f0b03382c249befa262304e5182532c1e2abca6852707345b64edfab1c8c1d1661d9
|
|
7
|
+
data.tar.gz: 47f9e080e840c83a0b006d906bbf5e526d12324812c8d1a63c49cb12f2c3f0610078f98120a326080d6ba189bac7fe0131d82e628e58ad28878e1537bd1df96c
|
data/docs/_pages/ecosystem.adoc
CHANGED
|
@@ -21,7 +21,7 @@ validation target, never a stand-in.
|
|
|
21
21
|
|
|
22
22
|
| `lutaml/lutaml-model` | — | 6213+ examples green on the released pair
|
|
23
23
|
|
|
24
|
-
| `lutaml/canon` | — | 2573 examples, 0 failures
|
|
24
|
+
| `lutaml/canon` | — | 2573 examples, 0 failures (a transient Windows failure of one JSON-engine parity example — yeptris 0.6.34.2's undefined `MARSHAL_STRICT_DUP_KEYS` under json 3 — cleared with yeptris 0.6.34.3)
|
|
25
25
|
|
|
26
26
|
| `metanorma/metanorma-standoc` | ghostscript | 627 examples green in CI on main (the boilerplate-fixture failure seen during the 04/05 validation was resolved by the 06 adapter fixes; a few local-only failures still need generated assets)
|
|
27
27
|
|
|
@@ -42,7 +42,7 @@ a declared, decided lane.
|
|
|
42
42
|
|===
|
|
43
43
|
| Consumer | Use | Disposition
|
|
44
44
|
|
|
45
|
-
| canon | HTML5 parse-lint (`parse_html(collect_errors:)`) | **
|
|
45
|
+
| canon | HTML5 parse-lint (`parse_html(collect_errors:)`) | **Retired** (canon#281, merged): `Moxml::Context#parse_html` forwards `collect_errors` and `Document#parse_diagnostics` answers the §13.2.5 token stream with code/line/column; canon rides the facade.
|
|
46
46
|
|
|
47
47
|
| canon | c14n native delegation (`doc.native.c14n`) | **Decided lane**: canon owns the c14n reference implementation and byte-probes the engine against its Ruby path; it is the backend behind moxml's own native delegation.
|
|
48
48
|
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -580,6 +580,13 @@ namespace_validation_mode: :strict)
|
|
|
580
580
|
false
|
|
581
581
|
end
|
|
582
582
|
|
|
583
|
+
# Whether the mint pre-writes the consumer's contract ivars
|
|
584
|
+
# in C (create_primed_element), letting Document#create_element
|
|
585
|
+
# skip its Ruby prime pass. Base: never.
|
|
586
|
+
def mints_primed?
|
|
587
|
+
false
|
|
588
|
+
end
|
|
589
|
+
|
|
583
590
|
# Whether add_child can keep tracking the same native —
|
|
584
591
|
# adapters that may recreate the node on attach (libxml's
|
|
585
592
|
# doc.root=) override to false so the wrapper refresh path
|
|
@@ -1193,14 +1193,26 @@ module Moxml
|
|
|
1193
1193
|
# entry are correct; upstream bug filed). Track the state per
|
|
1194
1194
|
# binding document and route XPath to the reference Ruby
|
|
1195
1195
|
# engine until a child insertion rebuilds the engine index.
|
|
1196
|
+
#
|
|
1197
|
+
# FIXED upstream in libleptris 1.9.378 (leptris/leptris#1802):
|
|
1198
|
+
# every splice path invalidates the index, so the guard is
|
|
1199
|
+
# dead weight on modern engines — and it sat on the builder
|
|
1200
|
+
# hot path (one Monitor-synchronized attachment set plus a
|
|
1201
|
+
# document hop per add_child). Retirement is probed, not
|
|
1202
|
+
# version-gated: any build that passes the #519 repro opts
|
|
1203
|
+
# itself out, no moxml release needed.
|
|
1196
1204
|
XPATH_INDEX_STALE = :xpath_index_stale
|
|
1197
1205
|
|
|
1198
1206
|
def mark_xpath_index_stale(node)
|
|
1207
|
+
return if native_xpath_index_fixed?
|
|
1208
|
+
|
|
1199
1209
|
doc = xpath_index_document(node)
|
|
1200
1210
|
attachments.set(doc, XPATH_INDEX_STALE, true) if doc
|
|
1201
1211
|
end
|
|
1202
1212
|
|
|
1203
1213
|
def clear_xpath_index_stale(node)
|
|
1214
|
+
return if native_xpath_index_fixed?
|
|
1215
|
+
|
|
1204
1216
|
doc = xpath_index_document(node)
|
|
1205
1217
|
attachments.set(doc, XPATH_INDEX_STALE, false) if doc
|
|
1206
1218
|
end
|
|
@@ -1762,6 +1774,23 @@ module Moxml
|
|
|
1762
1774
|
(owner_doc || create_document).create_element(name.to_s)
|
|
1763
1775
|
end
|
|
1764
1776
|
|
|
1777
|
+
# MINT_PRIMED: the compiled ext's create_element takes
|
|
1778
|
+
# optional (context, adapter) and pre-writes the contract
|
|
1779
|
+
# ivars in C — arity -1 vs the old 2. Self-gating: no
|
|
1780
|
+
# version floor, the probe reads the loaded ext.
|
|
1781
|
+
MINT_PRIMED = defined?(::Leptris::XML::NativeNode) &&
|
|
1782
|
+
::Leptris::XML::NativeNode
|
|
1783
|
+
.method(:create_element).arity == -1
|
|
1784
|
+
|
|
1785
|
+
def mints_primed?
|
|
1786
|
+
MINT_PRIMED
|
|
1787
|
+
end
|
|
1788
|
+
|
|
1789
|
+
def create_primed_element(name, owner_doc:, context:, adapter:)
|
|
1790
|
+
validate_element_name(name)
|
|
1791
|
+
owner_doc.native_create_element(name.to_s, context, adapter)
|
|
1792
|
+
end
|
|
1793
|
+
|
|
1765
1794
|
def create_native_text(content, owner_doc = nil)
|
|
1766
1795
|
if NATIVE_MUTATIONS_COHERENT && owner_doc
|
|
1767
1796
|
return owner_doc.native_create_text(content)
|
|
@@ -2408,6 +2437,7 @@ module Moxml
|
|
|
2408
2437
|
# callers fall back to create_element + set_attribute +
|
|
2409
2438
|
# add_child when absent.
|
|
2410
2439
|
def create_element_with_attrs(parent, name, attrs)
|
|
2440
|
+
validate_element_name(name)
|
|
2411
2441
|
binding_parent = to_binding(parent)
|
|
2412
2442
|
doc = binding_parent.document or return nil
|
|
2413
2443
|
return nil unless ::Leptris::XML::Native.respond_to?(:create_element_with_attrs)
|
|
@@ -3129,7 +3159,32 @@ module Moxml
|
|
|
3129
3159
|
end.join
|
|
3130
3160
|
end
|
|
3131
3161
|
|
|
3162
|
+
# SET_TEXT_NATIVE: bindings with the unshadowed C alias
|
|
3163
|
+
# (the add_child_native pattern — set_text_native aliases
|
|
3164
|
+
# content= on the TypedData self) take the one-frame write:
|
|
3165
|
+
# no doc_for climb, no address read, no module dispatch.
|
|
3166
|
+
# Self-gating by respond_to? — older exts fall through to
|
|
3167
|
+
# the set_binding_text face.
|
|
3168
|
+
SET_TEXT_NATIVE = defined?(::Leptris::XML::NativeNode) &&
|
|
3169
|
+
::Leptris::XML::NativeNode.method_defined?(:set_text_native)
|
|
3170
|
+
|
|
3132
3171
|
def set_text_content(node, content)
|
|
3172
|
+
if SET_TEXT_NATIVE && node.is_a?(::Leptris::XML::NativeNode)
|
|
3173
|
+
begin
|
|
3174
|
+
node.set_text_native(content.to_s)
|
|
3175
|
+
return
|
|
3176
|
+
rescue ::Leptris::XML::Error => e
|
|
3177
|
+
# Same #401 contract as the fallback below: a detached
|
|
3178
|
+
# (parentless) node re-raises as the named Moxml::Error.
|
|
3179
|
+
raise if node.parent
|
|
3180
|
+
|
|
3181
|
+
raise Moxml::Error,
|
|
3182
|
+
"cannot set text content on a detached " \
|
|
3183
|
+
"(parentless) node — attach it to a document " \
|
|
3184
|
+
"first (issue #401): #{e.message}"
|
|
3185
|
+
end
|
|
3186
|
+
end
|
|
3187
|
+
|
|
3133
3188
|
# Native-layer fast path (leptris-ruby#460): the C setter
|
|
3134
3189
|
# takes document+address directly — no binding twin mint
|
|
3135
3190
|
# per write. The Identity wrapper's own content= would
|
|
@@ -109,6 +109,10 @@ module Moxml
|
|
|
109
109
|
# over-invalidate (global bump instead of local) — a no-op
|
|
110
110
|
# for correctness.
|
|
111
111
|
if name.start_with?("xmlns")
|
|
112
|
+
# Introducing (or rewriting) a namespace declaration: mark the
|
|
113
|
+
# context BEFORE the invalidation, so this write's generation
|
|
114
|
+
# bump fans out to every wrapper's memoized scope reads.
|
|
115
|
+
element.context.mark_namespaces_active!
|
|
112
116
|
adapter.set_attribute(element.native, name, value)
|
|
113
117
|
element.invalidate_attribute_cache!
|
|
114
118
|
return value
|
data/lib/moxml/context.rb
CHANGED
|
@@ -90,7 +90,10 @@ module Moxml
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def bump_children_generation
|
|
93
|
-
|
|
93
|
+
# One dispatch, not two: children_generation re-dispatched just
|
|
94
|
+
# to default nil. Shared counter — node wrappers' children memos
|
|
95
|
+
# key on it.
|
|
96
|
+
@children_generation = @children_generation.to_i + 1
|
|
94
97
|
end
|
|
95
98
|
|
|
96
99
|
def namespace_scope_generation
|
|
@@ -101,6 +104,22 @@ module Moxml
|
|
|
101
104
|
@namespace_scope_generation += 1
|
|
102
105
|
end
|
|
103
106
|
|
|
107
|
+
# Namespace scopes only need the per-mutation generation bump
|
|
108
|
+
# while namespaces can exist. A builder-only document (no parse,
|
|
109
|
+
# no xmlns write) never has one, and the bump sat on the attach
|
|
110
|
+
# hot path (deletion experiment: build row 2.128x -> 1.923x).
|
|
111
|
+
# Every namespace introduction route marks the context active
|
|
112
|
+
# first — parse faces and the xmlns attribute-write face — so
|
|
113
|
+
# the sticky flag never misses an introduction.
|
|
114
|
+
def namespace_scopes_active?
|
|
115
|
+
@namespace_scopes_active = false if @namespace_scopes_active.nil?
|
|
116
|
+
@namespace_scopes_active
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def mark_namespaces_active!
|
|
120
|
+
@namespace_scopes_active = true
|
|
121
|
+
end
|
|
122
|
+
|
|
104
123
|
def entity_registry
|
|
105
124
|
@entity_registry ||= build_entity_registry
|
|
106
125
|
end
|
|
@@ -128,6 +147,7 @@ module Moxml
|
|
|
128
147
|
# Parse with adapter, passing self (context) so adapter can use our config
|
|
129
148
|
parsed_options = default_options.merge(options)
|
|
130
149
|
doc = config.adapter.parse(xml_string, parsed_options, self)
|
|
150
|
+
mark_namespaces_active!
|
|
131
151
|
|
|
132
152
|
# Set declaration flag on Document wrapper (proper OOP)
|
|
133
153
|
doc.has_xml_declaration = has_declaration if doc.is_a?(Document)
|
|
@@ -233,7 +253,9 @@ module Moxml
|
|
|
233
253
|
# are the result either way.
|
|
234
254
|
def parse_fragment(xml)
|
|
235
255
|
adapter = config.adapter
|
|
236
|
-
adapter.parse_fragment(xml, self)
|
|
256
|
+
nodes = adapter.parse_fragment(xml, self)
|
|
257
|
+
mark_namespaces_active!
|
|
258
|
+
nodes.map do |node|
|
|
237
259
|
# Adapters that tie a parent_node lifetime chain (#245)
|
|
238
260
|
# return wrapped nodes — pass them through untouched;
|
|
239
261
|
# re-wrapping would race the weak wrapper map.
|
|
@@ -242,6 +264,7 @@ module Moxml
|
|
|
242
264
|
end
|
|
243
265
|
|
|
244
266
|
def iterparse(xml, mode: :top_level, &block)
|
|
267
|
+
mark_namespaces_active!
|
|
245
268
|
config.adapter.iterparse(xml, mode, self, &block)
|
|
246
269
|
end
|
|
247
270
|
|
data/lib/moxml/document.rb
CHANGED
|
@@ -163,7 +163,17 @@ module Moxml
|
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
def create_element(name)
|
|
166
|
-
|
|
166
|
+
# The preprimed mint (leptris binding with the C-primed
|
|
167
|
+
# create_element): the mint already wrote @context/@adapter in
|
|
168
|
+
# C, so the returned node IS the finished wrapper and the Ruby
|
|
169
|
+
# prime pass disappears from the build path.
|
|
170
|
+
native = if adapter.mints_primed?
|
|
171
|
+
adapter.create_primed_element(name, owner_doc: @native,
|
|
172
|
+
context: context,
|
|
173
|
+
adapter: adapter)
|
|
174
|
+
else
|
|
175
|
+
adapter.create_element(name, owner_doc: @native)
|
|
176
|
+
end
|
|
167
177
|
# leptris-ruby#508 mints registered klasses at create: the node
|
|
168
178
|
# IS a contract-carrying identity (@native is self) — prime it
|
|
169
179
|
# in place instead of shelling a second wrapper (a raw mint
|
data/lib/moxml/element.rb
CHANGED
|
@@ -276,6 +276,7 @@ module Moxml
|
|
|
276
276
|
def add_namespace(prefix, uri)
|
|
277
277
|
adapter.create_namespace(@native, prefix, uri,
|
|
278
278
|
namespace_validation_mode: context.config.namespace_validation_mode)
|
|
279
|
+
context.mark_namespaces_active!
|
|
279
280
|
invalidate_namespace_cache!
|
|
280
281
|
self
|
|
281
282
|
rescue ValidationError => e
|
|
@@ -530,8 +531,19 @@ module Moxml
|
|
|
530
531
|
|
|
531
532
|
# Clear the namespace caches and bump the context's scope
|
|
532
533
|
# generation so every wrapper — including ones not reachable from
|
|
533
|
-
# any children cache — recomputes on next read.
|
|
534
|
+
# any children cache — recomputes on next read. While namespaces
|
|
535
|
+
# cannot exist (context.namespace_scopes_active? is false) the
|
|
536
|
+
# memos can only hold namespace-free reads, so skipping the whole
|
|
537
|
+
# body is exact — and the nil-assignment form would MATERIALIZE
|
|
538
|
+
# three unset ivars per attached node, which is the cost this
|
|
539
|
+
# gate exists to remove. Every introduction route — parse faces
|
|
540
|
+
# and the xmlns write face — marks the context BEFORE its own
|
|
541
|
+
# invalidation, so the introducing mutation still clears and
|
|
542
|
+
# bumps; in-scope reads are generation-keyed, so other wrappers
|
|
543
|
+
# recompute on the bump.
|
|
534
544
|
def invalidate_namespace_cache!
|
|
545
|
+
return unless context.namespace_scopes_active?
|
|
546
|
+
|
|
535
547
|
@namespaces = nil
|
|
536
548
|
@namespace_definitions = nil
|
|
537
549
|
@in_scope_namespaces = nil
|
data/lib/moxml/node.rb
CHANGED
|
@@ -30,9 +30,11 @@ module Moxml
|
|
|
30
30
|
def prime_contract(native, context, adapter = nil, node_type = nil)
|
|
31
31
|
@context = context
|
|
32
32
|
@native = native
|
|
33
|
-
@parent_node
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# @parent_node and @node_type_cached stay UNSET when empty —
|
|
34
|
+
# every reader is nil-tolerant, and a nil write materializes the
|
|
35
|
+
# ivar (object-shape transition) on every created node.
|
|
36
|
+
@adapter = adapter unless adapter.nil?
|
|
37
|
+
@node_type_cached = node_type unless node_type.nil?
|
|
36
38
|
self
|
|
37
39
|
end
|
|
38
40
|
|
|
@@ -214,10 +216,16 @@ module Moxml
|
|
|
214
216
|
def attach_child(node)
|
|
215
217
|
context.bump_children_generation
|
|
216
218
|
node = prepare_node(node)
|
|
217
|
-
|
|
219
|
+
oc = node.context
|
|
220
|
+
if !oc.nil? && !oc.equal?(context) &&
|
|
221
|
+
oc.namespace_scopes_active?
|
|
222
|
+
context.mark_namespaces_active!
|
|
223
|
+
end
|
|
224
|
+
a = adapter
|
|
225
|
+
a.add_child(@native, node.native)
|
|
218
226
|
# Refresh native in case adapter changed identity (e.g., LibXML
|
|
219
227
|
# doc.root=); stable-identity adapters skip the round trip.
|
|
220
|
-
unless
|
|
228
|
+
unless a.native_identity_stable?
|
|
221
229
|
refreshed = adapter.actual_native(node.native, @native)
|
|
222
230
|
node.refresh_native!(refreshed) if refreshed && refreshed != node.native
|
|
223
231
|
end
|
|
@@ -231,6 +239,7 @@ module Moxml
|
|
|
231
239
|
def attach_previous_sibling(node)
|
|
232
240
|
context.bump_children_generation
|
|
233
241
|
node = prepare_node(node)
|
|
242
|
+
absorb_namespace_activity(node)
|
|
234
243
|
inserted = adapter.add_previous_sibling(@native, node.native)
|
|
235
244
|
refresh_inserted_native(node, inserted)
|
|
236
245
|
# Invalidate the parent's memoized children list. The wrapper-side
|
|
@@ -245,6 +254,7 @@ module Moxml
|
|
|
245
254
|
def attach_next_sibling_to(anchor, node)
|
|
246
255
|
context.bump_children_generation
|
|
247
256
|
node = prepare_node(node)
|
|
257
|
+
absorb_namespace_activity(node)
|
|
248
258
|
inserted = adapter.add_next_sibling(anchor.native, node.native)
|
|
249
259
|
refresh_inserted_native(node, inserted)
|
|
250
260
|
anchor.parent&.invalidate_children_cache!
|
|
@@ -252,6 +262,16 @@ module Moxml
|
|
|
252
262
|
self
|
|
253
263
|
end
|
|
254
264
|
|
|
265
|
+
# A node adopted from ANOTHER context can carry namespaces this
|
|
266
|
+
# context's own faces (parse, xmlns writes) never saw; import the
|
|
267
|
+
# source context's activity flag so the gated scope-generation
|
|
268
|
+
# bump stays armed here too.
|
|
269
|
+
def absorb_namespace_activity(node)
|
|
270
|
+
oc = node.context
|
|
271
|
+
return if oc.nil? || oc.equal?(context)
|
|
272
|
+
context.mark_namespaces_active! if oc.namespace_scopes_active?
|
|
273
|
+
end
|
|
274
|
+
|
|
255
275
|
def refresh_inserted_native(node, inserted)
|
|
256
276
|
return unless adapter.add_sibling_returns_inserted?
|
|
257
277
|
return if inserted.nil? || inserted.equal?(node.native)
|
|
@@ -773,7 +793,10 @@ module Moxml
|
|
|
773
793
|
protected
|
|
774
794
|
|
|
775
795
|
def invalidate_children_cache!
|
|
776
|
-
|
|
796
|
+
# Only nil an EXISTING memo: nil-assignment materializes an
|
|
797
|
+
# unset ivar (object-shape transition), and the builder hot
|
|
798
|
+
# path invalidates parents whose children memo was never read.
|
|
799
|
+
@children = nil unless @children.nil?
|
|
777
800
|
end
|
|
778
801
|
|
|
779
802
|
# Invalidate parent's cached children when this node
|
data/lib/moxml/version.rb
CHANGED
|
@@ -865,5 +865,95 @@ RSpec.describe Moxml::Adapter::Leptris do
|
|
|
865
865
|
expect(native).not_to be_nil
|
|
866
866
|
expect(native.to_a.map { |n| n.name.to_s }).to eq(%w[a c b])
|
|
867
867
|
end
|
|
868
|
+
|
|
869
|
+
it "never arms the guard on engines that invalidate on splice" do
|
|
870
|
+
skip "requires an engine whose node-set index survives sibling insertion (leptris-ruby#519)" unless described_class.native_xpath_index_fixed?
|
|
871
|
+
|
|
872
|
+
doc = ctx.parse("<root><anchor/></root>")
|
|
873
|
+
doc.xpath("//ext") # prime the engine index
|
|
874
|
+
doc.root.add_child(doc.create_element("ext"))
|
|
875
|
+
expect(doc.xpath("//ext").size).to eq(1)
|
|
876
|
+
|
|
877
|
+
expect(
|
|
878
|
+
described_class.attachments.get(
|
|
879
|
+
described_class.xpath_index_document(doc.root.native),
|
|
880
|
+
described_class.singleton_class.const_get(:XPATH_INDEX_STALE),
|
|
881
|
+
),
|
|
882
|
+
).to be_nil
|
|
883
|
+
end
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
describe "namespace scope-generation gate" do
|
|
887
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
888
|
+
|
|
889
|
+
# The per-attach scope-generation bump only matters once
|
|
890
|
+
# namespaces exist; the builder hot path must not pay it.
|
|
891
|
+
it "does not bump the scope generation while namespaces are absent" do
|
|
892
|
+
doc = ctx.create_document
|
|
893
|
+
root = doc.create_element("r")
|
|
894
|
+
doc.root = root
|
|
895
|
+
gen = ctx.namespace_scope_generation
|
|
896
|
+
root.add_child(doc.create_element("a"))
|
|
897
|
+
root.add_child(doc.create_element("b"))
|
|
898
|
+
expect(ctx.namespace_scope_generation).to eq(gen)
|
|
899
|
+
expect(ctx.namespace_scopes_active?).to be(false)
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
it "arms the gate when a write introduces a namespace" do
|
|
903
|
+
doc = ctx.create_document
|
|
904
|
+
root = doc.create_element("r")
|
|
905
|
+
doc.root = root
|
|
906
|
+
root["xmlns:foo"] = "urn:x"
|
|
907
|
+
expect(ctx.namespace_scopes_active?).to be(true)
|
|
908
|
+
expect(root.namespaces.map { |n| [n.prefix, n.uri] })
|
|
909
|
+
.to include(%w[foo urn:x])
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
it "is armed by parsed documents" do
|
|
913
|
+
doc = ctx.parse("<r xmlns:foo='urn:x'><foo:a/></r>")
|
|
914
|
+
expect(ctx.namespace_scopes_active?).to be(true)
|
|
915
|
+
expect(doc.xpath("//foo:a", "foo" => "urn:x").size).to eq(1)
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
it "imports the flag from a foreign context on adoption" do
|
|
919
|
+
donor_ctx = Moxml.new(:leptris)
|
|
920
|
+
donor = donor_ctx.parse("<x xmlns:foo='urn:x'/>")
|
|
921
|
+
doc = ctx.create_document
|
|
922
|
+
root = doc.create_element("r")
|
|
923
|
+
doc.root = root
|
|
924
|
+
expect(ctx.namespace_scopes_active?).to be(false)
|
|
925
|
+
root.add_child(donor.root)
|
|
926
|
+
expect(ctx.namespace_scopes_active?).to be(true)
|
|
927
|
+
expect(root.children.first.namespaces.map(&:uri))
|
|
928
|
+
.to include("urn:x")
|
|
929
|
+
end
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
# The README error-handling contract: an invalid element name must
|
|
933
|
+
# raise Moxml::ValidationError at CREATE time. The adapter's native
|
|
934
|
+
# fast paths (create_primed_element, create_element_with_attrs) mint
|
|
935
|
+
# from a name without passing through Base#create_element, where
|
|
936
|
+
# validate_element_name lives — the failure then surfaced late, at
|
|
937
|
+
# add_child, as Moxml::Error "Document already has a root element".
|
|
938
|
+
describe "element name validation on the create seam" do
|
|
939
|
+
let(:ctx) { Moxml.new(:leptris) }
|
|
940
|
+
|
|
941
|
+
it "raises ValidationError when create_element mints an invalid name" do
|
|
942
|
+
doc = ctx.parse("<root/>")
|
|
943
|
+
expect { doc.create_element("wrong,name") }
|
|
944
|
+
.to raise_error(Moxml::ValidationError, /Invalid XML element name/)
|
|
945
|
+
end
|
|
946
|
+
|
|
947
|
+
it "raises ValidationError when add_element mints an invalid name" do
|
|
948
|
+
doc = ctx.create_document
|
|
949
|
+
expect { doc.add_element("bad,name", id: "1") }
|
|
950
|
+
.to raise_error(Moxml::ValidationError, /Invalid XML element name/)
|
|
951
|
+
end
|
|
952
|
+
|
|
953
|
+
it "still mints valid names with punctuation allowed in XML Names" do
|
|
954
|
+
doc = ctx.parse("<root/>")
|
|
955
|
+
element = doc.create_element("ns:my-element.v2")
|
|
956
|
+
expect(element).to be_a(Moxml::Element)
|
|
957
|
+
end
|
|
868
958
|
end
|
|
869
959
|
end
|