moxml 0.5.138 → 0.5.140

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20467d4026cf631dd85798aaaa47a53526e1a6c8e26b1583ac896e56bb0fa4b4
4
- data.tar.gz: 3d3829939332dfd24a1d552308d10fd7c0dbbe6eed998a5292ec5a5d82d53def
3
+ metadata.gz: 40599a2ac65b555f44dd70acb32404c381886b86d1385e9d4cb9c516c613e3ae
4
+ data.tar.gz: 5ed6d5baf3ba27e62191b468da167f8ad1f844a961d6879fb6343308ca646b62
5
5
  SHA512:
6
- metadata.gz: 353a615d10c7755a3d03a5dd13988eb57b1f30b2bc68f1506e68bc48d053deca42fd968cbba67f5df10cd14dff7da757bfd15f89a734cd1e607e34d3b319eb0a
7
- data.tar.gz: bfa917028338657c3c09d91f32a964a653f4e50bc8d5abd1d0c7cf404d284d2efb216b0e62df1f17f1d731ef636c6d60bbe27a5f587ccca2c45bb65aa93c8e39
6
+ metadata.gz: 17709b3e62f6b7c19724b8c493dd1d46f44dc7f2c980d7c39a263be46d6118cad5be71f8493b2f23ed8a4ed214ab8e8c21be5790dfddf6e127302e280593f8d1
7
+ data.tar.gz: 8ff39e0c9ac39d25f9cfd931055461cc26da5852f12881a63d4bccebde698d4dacb29995c9f40b3242ca943a55a10b2a69a5ea524f88b00f0bf3f87bb9b5674d
@@ -303,6 +303,16 @@ module Moxml
303
303
  # validator raise; leptris gates on >= 1.9.321.0.
304
304
  XSD_SUPPORTED = false
305
305
 
306
+ # Issue #384: schema handles are adapter-native objects — a
307
+ # handle compiled through one adapter must not dispatch
308
+ # through another. Adapters with an XSD engine answer their
309
+ # handle type; the default (no engine) matches nothing, so a
310
+ # mismatched wrap raises a clear error at construction
311
+ # instead of a NoMethodError deep in the first validate.
312
+ def xsd_handle_match?(_handle)
313
+ false
314
+ end
315
+
306
316
  def xsd_compile(_schema_source)
307
317
  raise Moxml::NotImplementedError,
308
318
  "XSD validation requires an adapter with an XSD engine"
@@ -92,8 +92,20 @@ module Moxml
92
92
  # NativeNode#[]), so the adapter's native fast paths speak to
93
93
  # the native layer through these bound entries — bind_call is
94
94
  # the same C dispatch without the shadow lookup.
95
+ # Always defined: nothing can be an instance of an anonymous
96
+ # class, so on the FFI-only lane is_a?(NN) is uniformly false
97
+ # and the ungated native-node conditionals keep their
98
+ # semantics without the compiled ext.
99
+ NN = NATIVE_READ_LAYER ? ::Leptris::XML::NativeNode : Class.new
100
+
101
+ # The binding's Document.parse gained the strict: kwarg later
102
+ # than the native layer's default-strict behavior — probe the
103
+ # face instead of version-gating (the RECOVER_MODE pattern).
104
+ STRICT_PARSE_KWARG =
105
+ ::Leptris::XML::Document.method(:parse)
106
+ .parameters.any? { |_, key| key == :strict }
107
+
95
108
  if NATIVE_READ_LAYER
96
- NN = ::Leptris::XML::NativeNode
97
109
  NN_ATTRIBUTE = NN.instance_method(:attribute)
98
110
  NN_NAME = NN.instance_method(:name)
99
111
  NN_PARENT = NN.instance_method(:parent)
@@ -290,6 +302,11 @@ module Moxml
290
302
  ::Leptris::XML::XSD.compile(schema_source)
291
303
  end
292
304
 
305
+ def xsd_handle_match?(handle)
306
+ defined?(::Leptris::XML::XSD::Schema) &&
307
+ handle.is_a?(::Leptris::XML::XSD::Schema)
308
+ end
309
+
293
310
  def xsd_compile_file(path)
294
311
  unless XSD_SUPPORTED && ::Leptris::XML::XSD.respond_to?(:compile_file)
295
312
  raise Moxml::NotImplementedError,
@@ -397,6 +414,27 @@ module Moxml
397
414
  end
398
415
  end
399
416
 
417
+ class << self
418
+ # These three live OUTSIDE the NATIVE_READ_LAYER gate:
419
+ # ungated methods (register_mint_klasses!, the
420
+ # create/register seams) and the specs' singleton const_get
421
+ # read them, and on the FFI-only lane the gate's Identity
422
+ # classes do not exist. NATIVE_MUTATIONS_COHERENT is false
423
+ # without the native layer, so all three evaluate false
424
+ # there.
425
+ NATIVE_IDENTITY =
426
+ NATIVE_MUTATIONS_COHERENT &&
427
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.4")
428
+
429
+ NATIVE_C_MINT =
430
+ NATIVE_IDENTITY &&
431
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.255.2")
432
+
433
+ NATIVE_KLASS_CHILDREN =
434
+ NATIVE_IDENTITY &&
435
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.2")
436
+ end
437
+
400
438
  if NATIVE_READ_LAYER
401
439
  # root NativeNode -> binding document (recorded at #root
402
440
  # mint; the native layer exposes no document accessor).
@@ -501,10 +539,6 @@ module Moxml
501
539
  TEXT.include(Reads)
502
540
  end
503
541
 
504
- NATIVE_IDENTITY =
505
- NATIVE_MUTATIONS_COHERENT &&
506
- Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.4")
507
-
508
542
  # leptris-ruby#246 klass-propagating reads: the read
509
543
  # family (children/element_children/next_sibling/parent)
510
544
  # mints the mapped subclass per kind, so children arrive
@@ -521,14 +555,6 @@ module Moxml
521
555
  # classes — walked and built nodes arrive already carrying the
522
556
  # contract, and the Ruby shell mint drops out entirely
523
557
  # (moxml#288: the entire remaining walk/build gap).
524
- NATIVE_C_MINT =
525
- NATIVE_IDENTITY &&
526
- Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.255.2")
527
-
528
- NATIVE_KLASS_CHILDREN =
529
- NATIVE_IDENTITY &&
530
- Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.2")
531
-
532
558
  if NATIVE_KLASS_CHILDREN
533
559
  kind_classes = [
534
560
  Identity::ELEMENT,
@@ -1065,6 +1091,10 @@ module Moxml
1065
1091
  ::Leptris::XML::Document.parse(
1066
1092
  processed,
1067
1093
  readonly: options[:readonly] == true,
1094
+ # The FFI fallback lane parses leniently by default —
1095
+ # the strict intent must ride explicitly or strict
1096
+ # callers silently get a best-effort tree.
1097
+ **(options[:strict] && STRICT_PARSE_KWARG ? { strict: true } : {}),
1068
1098
  options: parse_flags(options, recover: recover,
1069
1099
  context: _context),
1070
1100
  )
@@ -1343,13 +1373,22 @@ module Moxml
1343
1373
  # comment, cdata, pi. Parsed documents register right after
1344
1374
  # the binding mint. Unregistered bindings (older floors, the
1345
1375
  # C-mint off) are unchanged — the shell path stays.
1346
- MINT_KIND_CLASSES = [
1347
- Identity::ELEMENT,
1348
- Identity::TEXT,
1349
- Identity::COMMENT,
1350
- Identity::CDATA,
1351
- Identity::PROCESSING_INSTRUCTION,
1352
- ].freeze
1376
+ # Identity lives under the NATIVE_READ_LAYER gate — on the
1377
+ # FFI-only lane (windows, or a binding whose native bundle
1378
+ # fails to load) the constant must not be referenced at load
1379
+ # time. register_mint_klasses! never reads it there
1380
+ # (NATIVE_C_MINT is false).
1381
+ MINT_KIND_CLASSES = if NATIVE_READ_LAYER
1382
+ [
1383
+ Identity::ELEMENT,
1384
+ Identity::TEXT,
1385
+ Identity::COMMENT,
1386
+ Identity::CDATA,
1387
+ Identity::PROCESSING_INSTRUCTION,
1388
+ ].freeze
1389
+ else
1390
+ [].freeze
1391
+ end
1353
1392
 
1354
1393
  def register_mint_klasses!(binding_doc)
1355
1394
  return binding_doc unless NATIVE_C_MINT && binding_doc
@@ -2349,7 +2388,7 @@ module Moxml
2349
2388
  # upstream (its absorbed_into? checks are not transitive) —
2350
2389
  # those keep the pin + adoption copy. Entity references
2351
2390
  # keep the marker flow below.
2352
- if NATIVE_DOC_ABSORB && !child.is_a?(NN) &&
2391
+ if NATIVE_READ_LAYER && NATIVE_DOC_ABSORB && !child.is_a?(NN) &&
2353
2392
  child.is_a?(::Leptris::XML::Node) &&
2354
2393
  !child.is_a?(CustomizedLeptris::EntityReference)
2355
2394
  child_doc = child.document
@@ -2447,8 +2486,11 @@ module Moxml
2447
2486
  end
2448
2487
  # 1.9.311: an absorbed element source moves through the raw
2449
2488
  # engine faces — the binding's sibling insert drops it.
2450
- if new_node.is_a?(::Leptris::XML::Element) &&
2451
- !new_node.is_a?(::Leptris::XML::NativeNode)
2489
+ # NATIVE_READ_LAYER: the absorb needs the raw engine add to
2490
+ # land the source; on the FFI lane the binding's own insert
2491
+ # handles it and the absorbed-source splice loses the node.
2492
+ if NATIVE_READ_LAYER && new_node.is_a?(::Leptris::XML::Element) &&
2493
+ !new_node.is_a?(NN)
2452
2494
  new_doc = new_node.document
2453
2495
  node_doc = node.document
2454
2496
  if new_doc && node_doc && !new_doc.equal?(node_doc)
@@ -2477,8 +2519,11 @@ module Moxml
2477
2519
  pin_foreign_owner_document(node, new_node)
2478
2520
  # 1.9.311: an absorbed element source moves through the raw
2479
2521
  # engine faces — the binding's sibling insert drops it.
2480
- if new_node.is_a?(::Leptris::XML::Element) &&
2481
- !new_node.is_a?(::Leptris::XML::NativeNode)
2522
+ # NATIVE_READ_LAYER: the absorb needs the raw engine add to
2523
+ # land the source; on the FFI lane the binding's own insert
2524
+ # handles it and the absorbed-source splice loses the node.
2525
+ if NATIVE_READ_LAYER && new_node.is_a?(::Leptris::XML::Element) &&
2526
+ !new_node.is_a?(NN)
2482
2527
  new_doc = new_node.document
2483
2528
  node_doc = node.document
2484
2529
  if new_doc && node_doc && !new_doc.equal?(node_doc)
@@ -215,6 +215,10 @@ module Moxml
215
215
  ::Nokogiri::XML::Schema.new(schema_source.to_s)
216
216
  end
217
217
 
218
+ def xsd_handle_match?(handle)
219
+ handle.is_a?(::Nokogiri::XML::Schema)
220
+ end
221
+
218
222
  def xsd_compile_file(path)
219
223
  path = path.to_s
220
224
  # libxml2 resolves schemaLocation hrefs against the
data/lib/moxml/config.rb CHANGED
@@ -47,7 +47,23 @@ module Moxml
47
47
  def runtime_default_adapter
48
48
  return opal_runtime_adapter if RUBY_ENGINE == "opal"
49
49
 
50
- return PREFERRED_ADAPTER if leptris_preferred_available?
50
+ preferred = leptris_preferred_available?
51
+ return PREFERRED_ADAPTER if preferred == true
52
+
53
+ # Issue #384: a PRESENT-but-unusable leptris (broken native
54
+ # bundle, adapter load failure) must not degrade silently —
55
+ # validation semantics differ per adapter, so the
56
+ # substitution is announced. A machine without the gem at
57
+ # all is a normal nokogiri/rexml deployment: no noise.
58
+ if preferred == :broken
59
+ Kernel.warn(
60
+ "moxml: the leptris adapter is installed but unusable " \
61
+ "(see the preceding error); falling back to " \
62
+ "#{detect_loaded_adapter || FALLBACK_ADAPTER} — " \
63
+ "adapter-specific behavior (XSD validation, " \
64
+ "serialization) will differ",
65
+ )
66
+ end
51
67
 
52
68
  detect_loaded_adapter || FALLBACK_ADAPTER
53
69
  end
@@ -74,14 +90,19 @@ module Moxml
74
90
  def leptris_preferred_available?
75
91
  return @leptris_preferred_available if defined?(@leptris_preferred_available)
76
92
 
77
- @leptris_preferred_available = begin
78
- require "leptris"
79
- Moxml::Adapter.load(:leptris)
80
- Gem::Version.new(::Leptris::VERSION) >=
81
- Gem::Version.new(Moxml::Adapter::Leptris::MINIMUM_BINDING_VERSION)
82
- rescue LoadError, NameError
83
- false
84
- end
93
+ @leptris_preferred_available =
94
+ begin
95
+ require "leptris"
96
+ Moxml::Adapter.load(:leptris)
97
+ Gem::Version.new(::Leptris::VERSION) >=
98
+ Gem::Version.new(Moxml::Adapter::Leptris::MINIMUM_BINDING_VERSION)
99
+ rescue LoadError
100
+ # Not installed: a normal deployment shape, silent.
101
+ false
102
+ rescue NameError, Moxml::AdapterError
103
+ # Installed but unusable (adapter load failure): loud.
104
+ :broken
105
+ end
85
106
  end
86
107
 
87
108
  def detect_loaded_adapter
data/lib/moxml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.138"
4
+ VERSION = "0.5.140"
5
5
  end
data/lib/moxml/xsd.rb CHANGED
@@ -92,6 +92,29 @@ module Moxml
92
92
  def initialize(handle, context)
93
93
  @handle = handle
94
94
  @context = context
95
+ assert_adapter_matches_handle!
96
+ end
97
+
98
+ # Issue #384: the handle is an adapter-native object; routing
99
+ # it through a different adapter's xsd_validate surfaced as a
100
+ # distant NoMethodError. Fail at the wrap with the mismatch
101
+ # named.
102
+ def assert_adapter_matches_handle!
103
+ adapter = @context.config.adapter
104
+ return if adapter.xsd_handle_match?(@handle)
105
+
106
+ raise Moxml::AdapterError.new(
107
+ "schema handle mismatch: #{handle_class_name(@handle)} " \
108
+ "cannot be validated through the " \
109
+ "#{adapter.respond_to?(:adapter_name) ? adapter.adapter_name : adapter} " \
110
+ "adapter — compile the schema through the same adapter " \
111
+ "that will validate it",
112
+ operation: "xsd_compile",
113
+ )
114
+ end
115
+
116
+ def handle_class_name(handle)
117
+ handle.class.name.split("::").last(2).join("::")
95
118
  end
96
119
 
97
120
  # Validate a node or raw XML string. Returns [] when valid.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Issue #384: when the preferred default adapter is installed but
6
+ # unusable, the silent substitution to the fallback adapter changed
7
+ # validation semantics invisibly. The degradation now announces
8
+ # itself; an install without the gem at all stays silent (a normal
9
+ # deployment shape).
10
+ RSpec.describe "Config default-adapter degradation" do
11
+ def with_broken_leptris
12
+ old_default_adapter =
13
+ Moxml::Config.instance_variable_get(:@default_adapter)
14
+ Moxml::Config.remove_instance_variable(:@default_adapter) if old_default_adapter
15
+ # The state a broken install produces (adapter load failure with
16
+ # the gem present): present-but-unusable, not absent.
17
+ Moxml::Config.instance_variable_set(:@leptris_preferred_available, :broken)
18
+ yield
19
+ ensure
20
+ Moxml::Config.remove_instance_variable(:@leptris_preferred_available)
21
+ Moxml::Config.instance_variable_set(:@default_adapter, :nokogiri)
22
+ end
23
+
24
+ it "warns loudly and names the fallback" do
25
+ with_broken_leptris do
26
+ expect do
27
+ Moxml::Config.default_adapter
28
+ end.to output(/leptris adapter is installed but unusable/).to_stderr
29
+ end
30
+ end
31
+
32
+ it "still answers a usable adapter" do
33
+ with_broken_leptris do
34
+ expect(Moxml::Config.default_adapter).to eq(:nokogiri)
35
+ end
36
+ end
37
+
38
+ it "keeps a working leptris install silent" do
39
+ expect do
40
+ Moxml::Config.default_adapter
41
+ end.not_to output(/unusable/).to_stderr
42
+ end
43
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Issue #384: a schema handle compiled through one adapter must not
6
+ # dispatch through another — the misroute surfaced as a NoMethodError
7
+ # far from its cause when a silently-degraded context resolved to
8
+ # nokogiri while the handle came from the leptris API.
9
+ RSpec.describe "Moxml::XSD::Schema handle/adapter matching" do
10
+ let(:leptris_ctx) { Moxml.new(:leptris) }
11
+ let(:nokogiri_ctx) { Moxml.new(:nokogiri) }
12
+
13
+ it "accepts a handle compiled through the same adapter" do
14
+ schema = leptris_ctx.xsd("<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'/>")
15
+ expect(schema).to be_a(Moxml::XSD::Schema)
16
+ end
17
+
18
+ it "raises a named mismatch at the wrap, not a distant NoMethodError" do
19
+ handle = leptris_ctx.config.adapter.xsd_compile(
20
+ "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'/>",
21
+ )
22
+
23
+ expect do
24
+ Moxml::XSD::Schema.new(handle, nokogiri_ctx)
25
+ end.to raise_error(Moxml::AdapterError, /schema handle mismatch/)
26
+ end
27
+
28
+ it "keeps same-adapter validation working end to end" do
29
+ schema = nokogiri_ctx.xsd(
30
+ "<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" \
31
+ "<xsd:element name='r'/></xsd:schema>",
32
+ )
33
+ expect(schema.valid?(nokogiri_ctx.parse("<r/>"))).to be(true)
34
+ end
35
+ 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.138
4
+ version: 0.5.140
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-15 00:00:00.000000000 Z
11
+ date: 2026-10-16 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
@@ -491,6 +491,7 @@ files:
491
491
  - spec/moxml/cdata_spec.rb
492
492
  - spec/moxml/children_source_retention_spec.rb
493
493
  - spec/moxml/comment_spec.rb
494
+ - spec/moxml/config_degradation_spec.rb
494
495
  - spec/moxml/config_spec.rb
495
496
  - spec/moxml/context_spec.rb
496
497
  - spec/moxml/css_spec.rb
@@ -585,6 +586,7 @@ files:
585
586
  - spec/moxml/xpath_capabilities_spec.rb
586
587
  - spec/moxml/xpath_spec.rb
587
588
  - spec/moxml/xsd_error_contract_spec.rb
589
+ - spec/moxml/xsd_handle_mismatch_spec.rb
588
590
  - spec/moxml/xsd_release_spec.rb
589
591
  - spec/moxml/xsd_spec.rb
590
592
  - spec/moxml/xslt/stylesheet_spec.rb