moxml 0.5.176 → 0.5.178

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: b00ba396700e1ee797651d8db90cc283733f062021e05d8c807c751a07442414
4
- data.tar.gz: f0f35b75c740842a6bb4a21c0fde3b944e5994c2935a1de0e57b6671d02eb2fe
3
+ metadata.gz: ab4976a5d2b2a909d73204d71c0c6d67285ab3e1fd9c53db7bffc00e39425cca
4
+ data.tar.gz: a7d948364c456db7f8201b62a807758121d28d9635326eb91d68c43f499ff81f
5
5
  SHA512:
6
- metadata.gz: 8302fa56d49d9e6965559bf2be0db79afc61b5d6696cbe34aa5f6ff07381af94261b2988f858d76d5625cb7796b84c70999a6e899e7d5b5c61cdd58134dba462
7
- data.tar.gz: 349a9bf1e50540698ee21572f29c93db127d206bca9ab622fed3c989ed601af0109181b9b107ac06c27f17432cf98402ab2118d3ba6f8b0976b8ee7542fd74d8
6
+ metadata.gz: 2b1ff2e8c6dcb6d25e6b07270f3234a8ce7daf7475544b63f070405645c423f241e0212b4260ad688c1cb7433d8bc2ea7d1e3f4a0a14e9dcf4a67e832d53ee01
7
+ data.tar.gz: bf6c389706fb4d28e07af24a4d81bdacf0e4823d9bac95ea2f0ce7f55b08277cdcf8aa800424125a937fb3bf2be603b336b165f01aa9a9048277178357c4471d
@@ -341,6 +341,17 @@ module Moxml
341
341
  # (Leptris::VERSION), +engine_version+ is libleptris's own
342
342
  # (the leptris_version() C face, face-probed — nil on a
343
343
  # binding without it).
344
+ #
345
+ # Support faces are DESCRIPTOR-AUTHORITATIVE (leptris-ruby#572
346
+ # bridge, engine leptris#1905): a face answers true only when
347
+ # the ENGINE's capability bitset serves it AND the binding
348
+ # exposes it — a face the engine serves but the binding has
349
+ # not bridged is indistinguishable from absent without the
350
+ # bitset. On a binding without the bridge the probe answer
351
+ # stands alone (never narrowed by missing data). The bit
352
+ # values decode the engine's LEPTRIS_CAP_* layout (bits
353
+ # append per the engine's contract; the binding re-encodes
354
+ # nothing, the model names them).
344
355
  def capabilities
345
356
  @capabilities ||= {
346
357
  binding_version: ::Leptris::VERSION,
@@ -353,17 +364,44 @@ module Moxml
353
364
  add_element_full: ADD_ELEMENT_FULL,
354
365
  native_strings_utf8: !NATIVE_STRINGS_UTF8.nil?,
355
366
  native_mutations_coherent: !NATIVE_MUTATIONS_COHERENT.nil?,
356
- native_entity_refs: !NATIVE_ENTITY_REFS.nil?,
367
+ native_entity_refs:
368
+ !NATIVE_ENTITY_REFS.nil? && engine_serves?(:entity_ref_nodes),
357
369
  native_bulk_fixed: !NATIVE_BULK_FIXED.nil?,
358
370
  native_plan_structs: NATIVE_PLAN_STRUCTS,
359
371
  native_plan_typed: NATIVE_PLAN_TYPED,
360
- digest: DIGEST_SUPPORTED,
361
- xsd: XSD_SUPPORTED,
362
- relaxng: RELAXNG_SUPPORTED,
363
- xslt: XSLT_SUPPORTED,
372
+ digest: DIGEST_SUPPORTED && engine_serves?(:digest),
373
+ xsd: XSD_SUPPORTED && engine_serves?(:xsd),
374
+ relaxng: RELAXNG_SUPPORTED && engine_serves?(:relaxng),
375
+ xslt: XSLT_SUPPORTED && engine_serves?(:xslt),
364
376
  }.freeze
365
377
  end
366
378
 
379
+ # The engine's capability descriptor (raw bitset), or nil on
380
+ # a binding without the bridge. The bit values are the
381
+ # engine's LEPTRIS_CAP_* layout at descriptor version 1 —
382
+ # bits append, so the decode stays valid on later sets.
383
+ ENGINE_CAPS_BITS = {
384
+ digest: 1 << 9,
385
+ xsd: 1 << 10,
386
+ relaxng: 1 << 12,
387
+ xslt: 1 << 14,
388
+ entity_ref_nodes: 1 << 24,
389
+ }.freeze
390
+
391
+ def engine_serves?(face)
392
+ descriptor = engine_descriptor
393
+ return true unless descriptor
394
+
395
+ descriptor[:caps].anybits?(ENGINE_CAPS_BITS.fetch(face))
396
+ end
397
+
398
+ def engine_descriptor
399
+ @engine_descriptor ||=
400
+ if defined?(::Leptris::XML.capabilities)
401
+ ::Leptris::XML.capabilities
402
+ end
403
+ end
404
+
367
405
  # WeakMap has no #clear — 0.5.100's valve crashed every parse
368
406
  # crossing the cap on 3.4+ (NoMethodError). Replace the
369
407
  # registry wholesale, type-preserving: the WeakMap flavor
data/lib/moxml/context.rb CHANGED
@@ -214,9 +214,10 @@ module Moxml
214
214
  # Compile an XSD schema from a file path — <xs:include href>
215
215
  # resolves relative to the file's directory (the engine
216
216
  # resolves it; reading the file into #xsd would lose that).
217
- # The #452 rule index reads this document only; included
218
- # schema parts stay engine-validated (the index can only
219
- # under-report).
217
+ # The #452 rule index reads this document only; a document
218
+ # that composes others (include/import/redefine/override)
219
+ # turns the strict re-checks off (#510) — included parts stay
220
+ # engine-validated and the index can only under-report.
220
221
  def xsd_file(path)
221
222
  handle = config.adapter.xsd_compile_file(path.to_s)
222
223
  source = File.read(path.to_s)
@@ -229,7 +230,10 @@ module Moxml
229
230
  # types) are visible to the head's content models — the
230
231
  # single-document faces cannot see them (#483 item 4). The
231
232
  # Schema's #version / SchemaRules index read the HEAD document
232
- # (the first path); engine-set semantics cover the rest.
233
+ # (the first path); engine-set semantics cover the rest. A set
234
+ # is composition-shaped by contract, so the strict re-checks
235
+ # stay off (#510): the index under-reports instead of
236
+ # false-reporting components that live in a member.
233
237
  # Adapters without a set-capable engine raise
234
238
  # Moxml::NotImplementedError.
235
239
  def xsd_set(*paths)
@@ -238,7 +242,7 @@ module Moxml
238
242
 
239
243
  handle = config.adapter.xsd_compile_set(paths.map(&:to_s))
240
244
  source = File.read(paths.first.to_s)
241
- Moxml::XSD::Schema.new(handle, self, source)
245
+ Moxml::XSD::Schema.new(handle, self, source, self_contained: false)
242
246
  end
243
247
 
244
248
  # Compile a RELAX NG schema from source or from a file path (the
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.176"
4
+ VERSION = "0.5.178"
5
5
  end
@@ -197,7 +197,7 @@ module Moxml
197
197
  :attribute_default, :elements,
198
198
  :attributes, :contents, :version,
199
199
  :element_patterns, :attribute_patterns,
200
- :substitution_members)
200
+ :substitution_members, :self_contained)
201
201
 
202
202
  # Resolves a QName token against an element's in-scope
203
203
  # bindings. node.namespaces is adapter-divergent (some
@@ -338,7 +338,7 @@ module Moxml
338
338
  # Index one schema document. Returns nil when the source
339
339
  # cannot be indexed — the engine's answer then stands alone
340
340
  # (today's behavior).
341
- def index(schema_source, context)
341
+ def index(schema_source, context, self_contained: nil)
342
342
  root = context.parse(schema_source).root
343
343
  return nil unless root&.name == "schema"
344
344
 
@@ -346,7 +346,8 @@ module Moxml
346
346
  root["targetNamespace"].to_s,
347
347
  root["elementFormDefault"] == "qualified",
348
348
  root["attributeFormDefault"] == "qualified",
349
- {}, {}, {}, schema_version(root), {}, {}, {}
349
+ {}, {}, {}, schema_version(root), {}, {}, {},
350
+ self_contained.nil? ? composition_free?(root) : self_contained
350
351
  )
351
352
  child_elements(root).each do |decl|
352
353
  case decl.name
@@ -360,6 +361,24 @@ module Moxml
360
361
  nil
361
362
  end
362
363
 
364
+ COMPOSITION_ELEMENTS = %w[include import redefine override].freeze
365
+
366
+ # A schema document that composes other documents
367
+ # (include/import/redefine/override) declares only part of
368
+ # the compiled declaration surface in its own source — the
369
+ # engine resolves the members, and a strict re-check run
370
+ # against this index alone would answer "not declared" for
371
+ # components that live in a member (#510): a false report,
372
+ # which the one-directional contract forbids. Composition
373
+ # therefore turns the strict re-checks off — the index
374
+ # under-reports instead. Schema sets (xsd_set) are always
375
+ # composition-shaped: any member may carry declarations.
376
+ def composition_free?(root)
377
+ child_elements(root).none? do |decl|
378
+ COMPOSITION_ELEMENTS.include?(decl.name)
379
+ end
380
+ end
381
+
363
382
  # The effective schema version declared at the root
364
383
  # (#460 item 2): vc:minVersion="1.1" on <xs:schema> scopes
365
384
  # the document to 1.1 semantics; anything else reports
@@ -606,12 +625,18 @@ module Moxml
606
625
  model = index.contents[root_qname]
607
626
  findings = []
608
627
  findings << nil_finding(root, declaration) if declaration
609
- findings.concat(attribute_findings(root, model, index))
610
- if model
628
+ # The strict wildcard/attribute re-checks answer "not
629
+ # declared" against this index's declaration sets — they
630
+ # vouch only when the index saw the whole declaration
631
+ # surface (no composition, #510).
632
+ if model && index.self_contained
611
633
  child_elements(root).each do |child|
612
634
  findings.concat(child_findings(child, model, index))
613
635
  end
614
636
  end
637
+ if index.self_contained
638
+ findings.concat(attribute_findings(root, model, index))
639
+ end
615
640
  findings.compact
616
641
  end
617
642
 
data/lib/moxml/xsd.rb CHANGED
@@ -96,11 +96,15 @@ module Moxml
96
96
  "schema released (deterministic drop); compile a new one"
97
97
  end
98
98
 
99
- def initialize(handle, context, schema_source = nil)
99
+ def initialize(handle, context, schema_source = nil,
100
+ self_contained: nil)
100
101
  @handle = handle
101
102
  @context = context
102
103
  @rules_index =
103
- schema_source && SchemaRules.index(schema_source, context)
104
+ if schema_source
105
+ SchemaRules.index(schema_source, context,
106
+ self_contained: self_contained)
107
+ end
104
108
  assert_adapter_matches_handle!
105
109
  end
106
110
 
@@ -52,6 +52,31 @@ RSpec.describe "engine capability report" do
52
52
  expect(caps[:xslt]).to be(true)
53
53
  end
54
54
 
55
+ # leptris-ruby#572: the binding bridges the engine's capability
56
+ # descriptor, and the support faces answer its verdict ANDed with
57
+ # the binding probe — a face the engine serves but the binding has
58
+ # not bridged is not "true" (the half-face trap the version-gate
59
+ # class could not see).
60
+ it "answers the support faces from the engine descriptor bitset" do
61
+ skip "requires the binding's descriptor bridge (leptris 1.9.396+)" unless
62
+ defined?(Leptris::XML.capabilities)
63
+
64
+ caps = Moxml::Adapter::Leptris.capabilities
65
+ descriptor = Leptris::XML.capabilities
66
+ expect(descriptor[:descriptor_version]).to be(1)
67
+ {
68
+ digest: 1 << 9,
69
+ xsd: 1 << 10,
70
+ relaxng: 1 << 12,
71
+ xslt: 1 << 14,
72
+ }.each do |face, bit|
73
+ served = descriptor[:caps].anybits?(bit)
74
+ expect(caps[face]).to eql(served),
75
+ "#{face} must answer the engine bitset's verdict " \
76
+ "(ANDed with the binding probe)"
77
+ end
78
+ end
79
+
55
80
  it "is memoized — repeated queries answer the same frozen object" do
56
81
  expect(Moxml::Adapter::Leptris.capabilities)
57
82
  .to equal(Moxml::Adapter::Leptris.capabilities)
@@ -82,6 +82,84 @@ RSpec.describe "Schema-set compilation" do
82
82
  end
83
83
  end
84
84
 
85
+ # #510: the strict re-check answers "not declared" against the
86
+ # head document's own declaration index; composition (a set, or a
87
+ # single document carrying include/import/redefine/override)
88
+ # means declarations may live in a member the index never saw —
89
+ # the re-check must stay silent (under-report, never
90
+ # false-report) and let the engine's set-wide answer stand.
91
+ describe "strict re-check across composed schemas (#510)" do
92
+ def write_wildcard_set(dir)
93
+ File.write(File.join(dir, "headw.xsd"), <<~XSD)
94
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
95
+ xmlns="urn:W" targetNamespace="urn:W">
96
+ <xs:import namespace="urn:D" schemaLocation="decl.xsd"/>
97
+ <xs:element name="foo">
98
+ <xs:complexType>
99
+ <xs:sequence>
100
+ <xs:any namespace="##other" processContents="strict"/>
101
+ </xs:sequence>
102
+ </xs:complexType>
103
+ </xs:element>
104
+ </xs:schema>
105
+ XSD
106
+ File.write(File.join(dir, "decl.xsd"), <<~XSD)
107
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
108
+ xmlns="urn:D" targetNamespace="urn:D">
109
+ <xs:element name="bar" type="xs:string"/>
110
+ </xs:schema>
111
+ XSD
112
+ [File.join(dir, "headw.xsd"), File.join(dir, "decl.xsd")]
113
+ end
114
+
115
+ def wildcard_instance
116
+ '<foo xmlns="urn:W" xmlns:d="urn:D"><d:bar>hi</d:bar></foo>'
117
+ end
118
+
119
+ it "does not false-report a member-declared admission on a set" do
120
+ skip "requires the leptris compile_set face (binding 1.9.386.0+)" unless
121
+ set_capable?
122
+
123
+ Dir.mktmpdir do |dir|
124
+ paths = write_wildcard_set(dir)
125
+ schema = ctx.xsd_set(*paths)
126
+ expect(schema.validate(wildcard_instance)).to be_empty
127
+ end
128
+ end
129
+
130
+ it "does not false-report on a single composing document" do
131
+ Dir.mktmpdir do |dir|
132
+ write_wildcard_set(dir)
133
+ schema = ctx.xsd_file(File.join(dir, "headw.xsd"))
134
+ expect(schema.validate(wildcard_instance)).to be_empty
135
+ end
136
+ end
137
+
138
+ it "keeps the re-check on a self-contained schema" do
139
+ Dir.mktmpdir do |dir|
140
+ path = File.join(dir, "solo.xsd")
141
+ File.write(path, <<~XSD)
142
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
143
+ xmlns="urn:S" targetNamespace="urn:S">
144
+ <xs:element name="solo">
145
+ <xs:complexType>
146
+ <xs:sequence>
147
+ <xs:any namespace="##other" processContents="strict"/>
148
+ </xs:sequence>
149
+ </xs:complexType>
150
+ </xs:element>
151
+ </xs:schema>
152
+ XSD
153
+ schema = ctx.xsd_file(path)
154
+ errors = schema.validate(
155
+ '<solo xmlns="urn:S"><u:x xmlns:u="urn:U"/></solo>',
156
+ )
157
+ expect(errors.first.message)
158
+ .to include("matches a strict wildcard but is not declared")
159
+ end
160
+ end
161
+ end
162
+
85
163
  it "raises on an empty set" do
86
164
  expect { ctx.xsd_set }.to raise_error(ArgumentError)
87
165
  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.176
4
+ version: 0.5.178
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-30 00:00:00.000000000 Z
11
+ date: 2026-10-31 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