moxml 0.5.117 → 0.5.119

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: e55a5915424bfa9e3977ed2d9d5207502c42c48eb8b1f48de5406017d8e7cfa5
4
- data.tar.gz: '01315569d92e95d97e8d466a20da3e26be9db15d876c1062e45d739179399273'
3
+ metadata.gz: 7a30730e871854c7b88fc3b6a3498b4f4d5504ac47caa2aabcc530f2bf0eec96
4
+ data.tar.gz: 8d4f301d7d100cab15cc55b4f0e3db36bc5e50916775adfd34f64b816546d466
5
5
  SHA512:
6
- metadata.gz: 5138b1d6a1a60d5b0a6f1a6517599a6b6d05b34cf14266309a0cc9cc3082e332cb62c8cd76477363853beb7b1ae8d7ef6b3833bf17e07ee950ce7f71a68e4bdb
7
- data.tar.gz: 9b6ba4c742f717ad843eb1bdd08d58957eea374b33988b11dac6d9ece9e8a199739198057c09621c97af94462b329049916f3f0283612941adca7ac199bb73ba
6
+ metadata.gz: 4b61a839f7399e0b9e9236b1d921cfc161714b114d73da1b737a80c98795d8c88b96f8e85e71c3a71f58a090578b2ba6ffa4ec57cb9a6309cb53296bb000b277
7
+ data.tar.gz: b42fa624031b28da813039d441024be7766f8322fc54f247075b0c205cb79ec57052ababc789ab90f1c3b91b04a0ced4de85760af3178c1675aedd390fdd8046
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gemspec
9
9
  gem "benchmark"
10
10
  gem "benchmark-ips"
11
11
  gem "get_process_mem"
12
- gem "leptris", "~> 1.9.280"
12
+ gem "leptris", "~> 1.9.321"
13
13
  gem "libxml-ruby", ">= 5.0"
14
14
  gem "nokogiri", "~> 1.18"
15
15
  gem "openssl", "~> 3.0"
data/README.adoc CHANGED
@@ -119,7 +119,7 @@ carries the plan-ABI surface, the 1.9.290 XSLT-params and
119
119
  bulk-append faces, and the 1.9.292 engine deep-copy root install).
120
120
  |===
121
121
 
122
- NOTE: moxml's dev Gemfile pins `leptris ~> 1.9.174`, `nokogiri ~> 1.18`,
122
+ NOTE: moxml's dev Gemfile pins `leptris ~> 1.9.321` (the tested pair; the gemspec itself has zero runtime dependencies). Consumers of the leptris adapter should run leptris 1.9.320+ — earlier engines carry silent wrong-answer plan-path defects (moxml#344); moxml gates degrade to the always-correct slower paths below it.
123
123
  `ox ~> 2.14`, `libxml-ruby >= 5.0`, and the vendored oga fork — the CI
124
124
  matrix tests exactly those floors.
125
125
 
@@ -0,0 +1,39 @@
1
+ # Canonicalization (C14N)
2
+
3
+ Canonicalization is the load-bearing primitive for XML signature and a
4
+ top-level moxml feature, sibling to `Moxml::XPath`, `Moxml::Builder`,
5
+ and `Moxml::SAX`. Two documents that differ only in surface
6
+ representation (whitespace, attribute order, namespace prefix choice)
7
+ produce identical canonical bytes.
8
+
9
+ ## Pages
10
+
11
+ - [Algorithms](algorithms.md) — Inclusive C14N 1.0/1.1 (canon-port) and
12
+ Exclusive C14N 1.0 (moxml-native), the data model, output invariants,
13
+ and adapter delegation.
14
+ - [API](api.md) — the full public surface: canonicalize, exclusive,
15
+ subset, digests, and the comparison helper.
16
+ - [Examples](examples.md) — patterns: whole-document, subset,
17
+ cross-adapter verification, digests.
18
+
19
+ ## Quick start
20
+
21
+ ```ruby
22
+ # Canonical octets (inclusive C14N 1.0)
23
+ Moxml::C14n.canonicalize(node_or_xml)
24
+
25
+ # Exclusive C14N 1.0 (signatures over moved subdocuments)
26
+ Moxml::C14n.canonicalize_exclusive(node_or_xml,
27
+ inclusive_namespaces: ["foo"])
28
+
29
+ # Node-set subset canonicalization (same-document references)
30
+ Moxml::C14n.canonicalize_subset(node_or_xml, "//signed")
31
+
32
+ # Canonical comparison (testing, regression, signature debugging)
33
+ Moxml::C14n.same_canonical_form?(left, right)
34
+ ```
35
+
36
+ `node_or_xml` accepts a `Moxml::Node`, `Moxml::Document`, or XML
37
+ `String`. Engines without a native canonicalizer take the pure-Ruby
38
+ path — results are byte-identical across adapters, which is what keeps
39
+ cross-adapter signature verification sound.
@@ -0,0 +1,65 @@
1
+ # C14N algorithms
2
+
3
+ ## Inclusive C14N 1.0 / 1.1
4
+
5
+ - W3C: <https://www.w3.org/TR/xml-c14n/>, <https://www.w3.org/TR/xml-c14n11/>
6
+ - Implementation: **ported from `lutaml/canon`** (~1,200 lines)
7
+ - Files: `lib/moxml/c14n/inclusive_10.rb`, `inclusive_11.rb`,
8
+ `data_model.rb`, `processor.rb`, `namespace_handler.rb`,
9
+ `attribute_handler.rb`, `xml_base_handler.rb`, `character_encoder.rb`,
10
+ `node.rb`, `nodes/*.rb`
11
+ - "Attracts" ancestor context: at the apex, every in-scope namespace is
12
+ rendered, including those inherited from outside the canonicalization
13
+ subset.
14
+
15
+ ## Exclusive C14N 1.0
16
+
17
+ - W3C: <https://www.w3.org/TR/xml-exc-c14n/>
18
+ - Implementation: **moxml-native** (canon does not implement exclusive)
19
+ - Files: `lib/moxml/c14n/exclusive.rb`, `writer.rb`, `namespace_context.rb`
20
+ - "Repels" ancestor context: only namespaces visibly used by the apex
21
+ element's qualified name or attributes are rendered. Keeps signatures
22
+ valid when subdocuments move between XML contexts (e.g., into a SOAP
23
+ envelope).
24
+
25
+ ## Adapter delegation
26
+
27
+ Adapters whose engines ship a native canonicalizer delegate through a
28
+ lazy byte-safety probe — the pure-Ruby path remains the fallback and
29
+ the arbiter: all routes produce byte-identical output (pinned by
30
+ `spec/moxml/c14n/adapter_delegation_spec.rb`).
31
+
32
+ - **leptris**: engine-delegated inclusive C14N — an order of magnitude
33
+ faster than the Ruby path on large documents.
34
+ - **nokogiri**: libxml2 native canonicalization.
35
+
36
+ ## Data model
37
+
38
+ The canon-derived inclusive engines walk an intermediate data model
39
+ (`Moxml::C14n::Nodes::*`) rather than the live `Moxml::Node` tree,
40
+ because canonicalization needs:
41
+
42
+ - **Node-set membership flags** for subset canonicalization (spec §3).
43
+ - **Sorted namespace and attribute axes** per spec §2.3 / §2.4.
44
+ - **xml:base fixup** per RFC 3986 with C14N 1.1 modifications.
45
+ - **xml:\* inheritable attribute resolution** (xml:lang, xml:space)
46
+ from omitted ancestors.
47
+
48
+ The data model is built from `Moxml::Node` via `Moxml::C14n::DataModel`.
49
+
50
+ ## Output invariants
51
+
52
+ - UTF-8 encoded, no BOM
53
+ - NFC characters preserved
54
+ - Document-order traversal
55
+ - Entity references: `&` → `&amp;`, `<` → `&lt;`, `>` → `&gt;`
56
+ - Attribute values: also escape `"`, tab, LF, CR
57
+ - Empty elements expanded: `<foo/>` → `<foo></foo>`
58
+ - Line endings normalized to LF (the parser already does this)
59
+
60
+ ## Cross-verification
61
+
62
+ The libxmlsec1-produced fixtures in `spec/fixtures/xmldsig/` verify
63
+ byte-exact against our C14N output — the implementation matches
64
+ libxml2's C-based canonicalization for the cases the Ruby reference
65
+ exercises.
data/docs/c14n/api.md ADDED
@@ -0,0 +1,62 @@
1
+ # C14N API
2
+
3
+ `node_or_xml` accepts a `Moxml::Node`, `Moxml::Document`, or XML
4
+ `String` on every entry point.
5
+
6
+ ## Convenience methods
7
+
8
+ ```ruby
9
+ # Inclusive C14N 1.0
10
+ Moxml::C14n.canonicalize(node_or_xml, with_comments: false)
11
+
12
+ # Exclusive C14N 1.0
13
+ Moxml::C14n.canonicalize_exclusive(
14
+ node_or_xml,
15
+ with_comments: false,
16
+ inclusive_namespaces: [], # InclusiveNamespacesPrefixList parameter
17
+ )
18
+
19
+ # Inclusive C14N 1.1
20
+ Moxml::C14n.canonicalize_inclusive11(node_or_xml, with_comments: false)
21
+
22
+ # Node-set subset canonicalization: only nodes matched by the XPath
23
+ # render (position-path matching — see docs on enveloped signatures)
24
+ Moxml::C14n.canonicalize_subset(node_or_xml, "//signed",
25
+ with_comments: false)
26
+
27
+ # Canonical comparison: do two inputs share one canonical form?
28
+ # (inclusive C14N 1.0, comments dropped, on both sides)
29
+ Moxml::C14n.same_canonical_form?(left, right) # nodes or raw strings
30
+ ```
31
+
32
+ All canonicalize methods return canonical UTF-8 octet strings.
33
+
34
+ ## Direct engine access
35
+
36
+ Used by the signature algorithms:
37
+
38
+ ```ruby
39
+ Moxml::C14n::Inclusive10.new.canonicalize(node, with_comments:,
40
+ inclusive_namespaces:)
41
+ Moxml::C14n::Inclusive11.new.canonicalize(node, with_comments:,
42
+ inclusive_namespaces:)
43
+ Moxml::C14n::Exclusive.new.canonicalize(node, with_comments:,
44
+ inclusive_namespaces:)
45
+ ```
46
+
47
+ ## Digests
48
+
49
+ `Node#digest` (engine-delegated where available) computes the canonical
50
+ digest of a subtree:
51
+
52
+ ```ruby
53
+ node.digest # SHA-256 over inclusive C14N 1.0
54
+ node.digest(drop_ws_text: true)
55
+ ```
56
+
57
+ ## Backward-compat escape helpers
58
+
59
+ ```ruby
60
+ Moxml::C14n.escape_text("a & b < c > d")
61
+ Moxml::C14n.escape_attribute(%(a"b\tc\nd))
62
+ ```
@@ -0,0 +1,47 @@
1
+ # C14N examples
2
+
3
+ ## Whole-document canonicalization
4
+
5
+ ```ruby
6
+ doc = Moxml.new.parse(xml)
7
+ Moxml::C14n.canonicalize(doc)
8
+ # => "<?xml version=\"1.0\"?>\n<a b=\"1\">text</a>\n"
9
+ ```
10
+
11
+ ## Subset canonicalization (enveloped signature shape)
12
+
13
+ Only nodes matched by the XPath expression render — the same-document
14
+ reference pattern:
15
+
16
+ ```ruby
17
+ Moxml::C14n.canonicalize_subset(doc, "//ds:SignedInfo",
18
+ { "ds" => "http://www.w3.org/2000/09/xmldsig#" })
19
+ ```
20
+
21
+ ## Canonical comparison
22
+
23
+ Testing two documents for semantic equality — whitespace, attribute
24
+ order, and prefix choice are eliminated before comparison:
25
+
26
+ ```ruby
27
+ Moxml::C14n.same_canonical_form?(v1_document, v2_document) # => true
28
+ Moxml::C14n.same_canonical_form?(fixture_xml, output_string) # => true
29
+ ```
30
+
31
+ ## Cross-adapter verification
32
+
33
+ A signature produced with one adapter must verify with another — the
34
+ canonical bytes are byte-identical across adapters:
35
+
36
+ ```ruby
37
+ %i[leptris nokogiri rexml ox libxml].map do |adapter|
38
+ Moxml.new(adapter).parse(xml).then { |d| Moxml::C14n.canonicalize(d) }
39
+ end.uniq.size # => 1
40
+ ```
41
+
42
+ ## Digest of a subtree
43
+
44
+ ```ruby
45
+ signed_element.digest
46
+ # engine-delegated SHA-256 over the subtree's canonical form
47
+ ```
@@ -1,4 +1,4 @@
1
- # Canonicalization (C14N)
1
+ # Canonicalization (C14N) in XML signature
2
2
 
3
3
  Canonicalization is the load-bearing primitive for XML signature.
4
4
  Two documents that differ only in surface representation (whitespace,
@@ -6,7 +6,9 @@ attribute order, namespace prefix choice) must produce identical
6
6
  canonical bytes — otherwise signatures won't verify.
7
7
 
8
8
  `Moxml::C14n` is a top-level moxml feature, sibling to `Moxml::XPath`,
9
- `Moxml::Builder`, and `Moxml::SAX`.
9
+ `Moxml::Builder`, and `Moxml::SAX`. The full C14N documentation tree
10
+ lives at [docs/c14n/](../c14n/README.md) — this page covers the
11
+ signature-specific view.
10
12
 
11
13
  ## Algorithms
12
14
 
@@ -97,10 +99,10 @@ and it's much faster than pure Ruby. But:
97
99
  3. **Canon already had the mature pure-Ruby implementation.** Porting it
98
100
  was less work than building a hybrid adapter-delegated system.
99
101
 
100
- **Future work:** add an optional adapter-level C14N delegation. The
101
- Nokogiri adapter could expose `canonicalize(node, ...)` and the
102
- `Moxml::C14n` top-level method would delegate when available. This is
103
- documented in TODO.complete/19.
102
+ **Shipped:** optional adapter-level C14N delegation — the Nokogiri
103
+ (libxml2) and leptris (engine) adapters expose native canonicalization
104
+ behind a lazy byte-safety probe, byte-identical with the pure-Ruby
105
+ path (see [docs/c14n/algorithms.md](../c14n/algorithms.md#adapter-delegation)).
104
106
 
105
107
  ## Cross-verification
106
108
 
@@ -258,6 +258,20 @@ module Moxml
258
258
  raise Moxml::NotImplementedError, XSLT_UNSUPPORTED_MESSAGE
259
259
  end
260
260
 
261
+ # XSD validation (moxml XSD contract): engines without a
262
+ # validator raise; leptris gates on >= 1.9.321.0.
263
+ XSD_SUPPORTED = false
264
+
265
+ def xsd_compile(_schema_source)
266
+ raise Moxml::NotImplementedError,
267
+ "XSD validation requires an adapter with an XSD engine"
268
+ end
269
+
270
+ def xsd_validate(_xsd_handle, _native_document)
271
+ raise Moxml::NotImplementedError,
272
+ "XSD validation requires an adapter with an XSD engine"
273
+ end
274
+
261
275
  # Apply a compiled stylesheet; returns the engine's result
262
276
  # document, or nil when the output method cannot produce one
263
277
  # (text). @params is the flat quoted name/value list.
@@ -165,6 +165,18 @@ module Moxml
165
165
  # binding 1.9.313): the DSL accumulates markup in a plain
166
166
  # String and ONE native crossing (parse + attach) flushes the
167
167
  # whole subtree. See Adapter::Base#builder_face.
168
+ # XSD validation (leptris-ruby tier-1, #1075): the binding's
169
+ # Leptris::XML::XSD faces — >= 1.9.321.0 carries instance
170
+ # validation with accumulated errors (slices 3-4). Face probe
171
+ # pairs the gate (misrelease races — NATIVE_PLAN_STRUCTS
172
+ # lesson).
173
+ NATIVE_XSD =
174
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.321.0") &&
175
+ defined?(::Leptris::XML::XSD) &&
176
+ ::Leptris::XML::FFI.respond_to?(:leptris_xsd_validate)
177
+
178
+ XSD_SUPPORTED = NATIVE_XSD
179
+
168
180
  NATIVE_BUILDER =
169
181
  defined?(::Leptris::XML::Builder) &&
170
182
  defined?(::Leptris::XML::Document) &&
@@ -218,6 +230,27 @@ module Moxml
218
230
  # (and on installs without the native layer — the constant
219
231
  # check short-circuits), a Node.wrap over the shared C
220
232
  # pointer for NativeNodes.
233
+ def xsd_compile(schema_source)
234
+ unless XSD_SUPPORTED
235
+ raise Moxml::NotImplementedError,
236
+ "XSD validation requires the leptris 1.9.321+ engine"
237
+ end
238
+
239
+ ::Leptris::XML::XSD.compile(schema_source)
240
+ end
241
+
242
+ def xsd_validate(xsd_handle, native_document)
243
+ # +xsd_handle+ is the binding's Leptris::XML::XSD::Schema;
244
+ # validate_errors takes the binding document and returns
245
+ # the engine's accumulated messages ([] = valid).
246
+ doc = if native_document.is_a?(::Leptris::XML::Document)
247
+ native_document
248
+ else
249
+ to_binding(native_document)
250
+ end
251
+ xsd_handle.validate_errors(doc)
252
+ end
253
+
221
254
  def builder_face(native_document)
222
255
  return nil unless NATIVE_BUILDER && NATIVE_READ_LAYER
223
256
 
@@ -725,7 +758,16 @@ module Moxml
725
758
  # exactly the plan's shape, with no wrapper minting at all.
726
759
  # Marker-bearing documents stay on the generic path (the bulk
727
760
  # stream has no marker split).
728
- NATIVE_PLAN_ROWS = true
761
+ # moxml#344: engines 1.9.314-1.9.319 mis-match on the plan
762
+ # path — child-row exact-URI ns_uri pointed into build
763
+ # buffers (rows vanish after GC, leptris#1585) and plain
764
+ # attribute rows did not leniently match namespace-qualified
765
+ # wire attributes (nil captures, leptris#1586). Both are
766
+ # SILENT wrong answers, so the gate names the fixed engine:
767
+ # below 1.9.320.0 the plan faces degrade to the
768
+ # always-correct generic wrapper walk.
769
+ NATIVE_PLAN_ROWS =
770
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.320.0")
729
771
 
730
772
  # leptris-ruby#272 (binding 1.9.201.1): Native.plan_structs —
731
773
  # the C struct executor behind Moxml::StructPlan. The floor
@@ -735,9 +777,12 @@ module Moxml
735
777
  # below the fix. The probe guards the version gate: a
736
778
  # numerically newer lockstep can publish without the face
737
779
  # (1.9.199.0 shipped without it), and the gate alone would
738
- # NoMethodError.
780
+ # NoMethodError. Floor also carries moxml#344: 1.9.314-1.9.319
781
+ # mis-match on the plan path (leptris#1585/#1586, silent nil
782
+ # captures) — the struct executor degrades to the Ruby Plan
783
+ # fallback below the fix, same as the varargs-mint floor.
739
784
  NATIVE_PLAN_STRUCTS =
740
- Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.201.1") &&
785
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.320.0") &&
741
786
  (!defined?(::Leptris::XML::Native) ||
742
787
  ::Leptris::XML::Native.respond_to?(:plan_structs))
743
788
 
@@ -781,7 +826,7 @@ module Moxml
781
826
  end
782
827
 
783
828
  def self.plan_rows(native)
784
- return nil unless NATIVE_READ_LAYER
829
+ return nil unless NATIVE_READ_LAYER && NATIVE_PLAN_ROWS
785
830
 
786
831
  doc = if native.is_a?(::Leptris::XML::Document)
787
832
  native
data/lib/moxml/context.rb CHANGED
@@ -144,6 +144,18 @@ module Moxml
144
144
  # @return [Moxml::XSLT::Stylesheet]
145
145
  # @raise [Moxml::NotImplementedError] on adapters without an engine
146
146
  # @raise [Moxml::XsltError] on a malformed stylesheet
147
+ # Compile an XSD schema for validation (moxml XSD contract).
148
+ # Returns a Moxml::XSD::Schema; #validate/#valid? take a node or
149
+ # raw XML string. Adapters without an engine raise
150
+ # Moxml::NotImplementedError; Moxml::XSD.supported?(self)
151
+ # answers without raising.
152
+ #
153
+ # @raise [Moxml::NotImplementedError] on adapters without a validator
154
+ def xsd(schema_source)
155
+ handle = config.adapter.xsd_compile(schema_source.to_s)
156
+ Moxml::XSD::Schema.new(handle, self)
157
+ end
158
+
147
159
  def xslt(stylesheet)
148
160
  source = case stylesheet
149
161
  when Moxml::Document then stylesheet.to_xml
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.117"
4
+ VERSION = "0.5.119"
5
5
  end
data/lib/moxml/xsd.rb ADDED
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moxml
4
+ # XSD validation. Implementation-agnostic over engines that ship
5
+ # an XSD validator (currently leptris, tier-1: named+typed
6
+ # top-level elements, one-level content-model descent, lexical
7
+ # builtin/simpleType checks — see docs/c14n for the sibling
8
+ # pattern).
9
+ #
10
+ # ctx = Moxml.new(:leptris)
11
+ # schema = ctx.xsd(<<~XSD)
12
+ # <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
13
+ # <xs:element name="note" type="t_note"/>
14
+ # <xs:complexType name="t_note">
15
+ # <xs:sequence>
16
+ # <xs:element name="to" type="xs:string"/>
17
+ # </xs:sequence>
18
+ # </xs:complexType>
19
+ # </xs:schema>
20
+ # XSD
21
+ # schema.valid?(doc) # => true/false
22
+ # schema.validate(doc) # => [] or ["1:0: ...", ...]
23
+ #
24
+ # Adapters without an engine raise Moxml::NotImplementedError at
25
+ # compile; Moxml::XSD.supported?(context) answers without
26
+ # raising. The tier-1 scope is the engine's: documents outside it
27
+ # validate laxly (see the binding's xsd_validate_spec).
28
+ module XSD
29
+ SUPPORTED_ADAPTERS = %i[leptris].freeze
30
+
31
+ class << self
32
+ # Whether the context's adapter ships an XSD validator.
33
+ # Answers without raising; compile raises
34
+ # Moxml::NotImplementedError regardless (the capability probe
35
+ # is advisory).
36
+ def supported?(context)
37
+ adapter = context.config.adapter
38
+ adapter.const_defined?(:XSD_SUPPORTED) &&
39
+ adapter.const_get(:XSD_SUPPORTED)
40
+ end
41
+ end
42
+
43
+ # A compiled schema (adapter-owned handle). Validation results
44
+ # are the engine's accumulated messages; [] means valid.
45
+ class Schema
46
+ def initialize(handle, context)
47
+ @handle = handle
48
+ @context = context
49
+ end
50
+
51
+ # Validate a node or raw XML string. Returns [] when valid.
52
+ def validate(node_or_xml)
53
+ doc = case node_or_xml
54
+ when Moxml::Document then node_or_xml
55
+ when Moxml::Node then node_or_xml.document
56
+ else @context.parse(node_or_xml.to_s)
57
+ end
58
+ @context.config.adapter.xsd_validate(@handle, doc.native)
59
+ end
60
+
61
+ def valid?(node_or_xml)
62
+ validate(node_or_xml).empty?
63
+ end
64
+ end
65
+ end
66
+ end
data/lib/moxml.rb CHANGED
@@ -101,6 +101,7 @@ module Moxml
101
101
  autoload :Signature, "moxml/signature"
102
102
  autoload :C14n, "moxml/c14n"
103
103
  autoload :XSLT, "moxml/xslt"
104
+ autoload :XSD, "moxml/xsd"
104
105
 
105
106
  # Error hierarchy — each subclass autoloads from the same file
106
107
  autoload :Error, "moxml/error"
@@ -77,4 +77,28 @@ RSpec.describe "Moxml::Plan adapter conformance" do
77
77
  )
78
78
  end
79
79
  end
80
+
81
+ describe "plan-gate floors (moxml#344)" do
82
+ let(:gate_ctx) { Moxml.new(:leptris) }
83
+
84
+ it "arms both plan faces on engines carrying the #1585/#1586 fixes" do
85
+ skip "testing engine predates the floor" unless
86
+ Gem::Version.new(Leptris::VERSION) >= Gem::Version.new("1.9.320.0")
87
+
88
+ expect(Moxml::Adapter::Leptris::NATIVE_PLAN_ROWS).to be(true)
89
+ expect(Moxml::Adapter::Leptris::NATIVE_PLAN_STRUCTS).to be(true)
90
+ end
91
+
92
+ it "captures namespace-qualified wire attributes (leptris#1586)" do
93
+ skip "plan faces degraded below 1.9.320 (moxml#344)" unless
94
+ Moxml::Adapter::Leptris::NATIVE_PLAN_ROWS
95
+
96
+ plan = Moxml::Plan.new do
97
+ on("e") { |attrs| @captured = attrs }
98
+ end
99
+ plan.parse('<r xmlns:w="urn:w"><e w:name="Aptos"/></r>', gate_ctx)
100
+ captured = plan.instance_variable_get(:@captured)
101
+ expect(captured).to include("w:name" => "Aptos")
102
+ end
103
+ end
80
104
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Moxml::XSD contract (leptris tier-1 faces): compile + validate
6
+ # with accumulated errors; unsupported adapters raise
7
+ # NotImplementedError at compile.
8
+ RSpec.describe Moxml::XSD do
9
+ let(:ctx) { Moxml.new(:leptris) }
10
+ let(:schema_text) do
11
+ <<~XSD
12
+ <?xml version="1.0"?>
13
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
14
+ <xs:element name="note" type="t_note"/>
15
+ <xs:complexType name="t_note">
16
+ <xs:sequence>
17
+ <xs:element name="to" type="xs:string"/>
18
+ </xs:sequence>
19
+ </xs:complexType>
20
+ </xs:schema>
21
+ XSD
22
+ end
23
+
24
+ it "reports supported? on the leptris adapter" do
25
+ skip "leptris adapter unavailable" unless
26
+ Moxml::Adapter::Leptris::XSD_SUPPORTED
27
+
28
+ expect(described_class.supported?(ctx)).to be(true)
29
+ end
30
+
31
+ context "when the adapter ships a validator" do
32
+ before do
33
+ skip "leptris 1.9.321+ engine required" unless
34
+ Moxml::Adapter::Leptris::XSD_SUPPORTED
35
+ end
36
+
37
+ it "compiles and validates a good document" do
38
+ schema = ctx.xsd(schema_text)
39
+ doc = ctx.parse("<note><to>You</to></note>")
40
+ expect(schema.valid?(doc)).to be(true)
41
+ expect(schema.validate(doc)).to eq([])
42
+ end
43
+
44
+ it "returns accumulated messages for an invalid document" do
45
+ schema = ctx.xsd(schema_text)
46
+ errors = schema.validate("<note><wrong/></note>")
47
+ expect(errors).not_to be_empty
48
+ end
49
+
50
+ it "validates raw XML strings and nodes" do
51
+ schema = ctx.xsd(schema_text)
52
+ doc = ctx.parse("<note><to>x</to></note>")
53
+ expect(schema.valid?(doc.root)).to be(true)
54
+ expect(schema.valid?("<note><to>x</to></note>")).to be(true)
55
+ expect(schema.valid?("<note><wrong/></note>")).to be(false)
56
+ end
57
+
58
+ it "derives XSD_SUPPORTED from the engine face gate" do
59
+ # The capability flag must not outrun the engine faces: a
60
+ # misreleased lockstep (NATIVE_PLAN_STRUCTS lesson) would arm
61
+ # compile against missing functions.
62
+ expect(Moxml::Adapter::Leptris::XSD_SUPPORTED)
63
+ .to eq(Moxml::Adapter::Leptris::NATIVE_XSD)
64
+ end
65
+ end
66
+ 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.117
4
+ version: 0.5.119
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-07 00:00:00.000000000 Z
11
+ date: 2026-10-08 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
@@ -89,6 +89,10 @@ files:
89
89
  - docs/_tutorials/index.adoc
90
90
  - docs/_tutorials/namespace-handling.adoc
91
91
  - docs/_tutorials/xpath-queries.adoc
92
+ - docs/c14n/README.md
93
+ - docs/c14n/algorithms.md
94
+ - docs/c14n/api.md
95
+ - docs/c14n/examples.md
92
96
  - docs/index.adoc
93
97
  - docs/signature/algorithms.md
94
98
  - docs/signature/architecture.md
@@ -293,6 +297,7 @@ files:
293
297
  - lib/moxml/xpath/parser.rb
294
298
  - lib/moxml/xpath/ruby/generator.rb
295
299
  - lib/moxml/xpath/ruby/node.rb
300
+ - lib/moxml/xsd.rb
296
301
  - lib/moxml/xslt.rb
297
302
  - lib/moxml/xslt/stylesheet.rb
298
303
  - moxml.gemspec
@@ -565,6 +570,7 @@ files:
565
570
  - spec/moxml/xpath/union_spec.rb
566
571
  - spec/moxml/xpath_capabilities_spec.rb
567
572
  - spec/moxml/xpath_spec.rb
573
+ - spec/moxml/xsd_spec.rb
568
574
  - spec/moxml/xslt/stylesheet_spec.rb
569
575
  - spec/performance/README.md
570
576
  - spec/performance/benchmark_spec.rb