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 +4 -4
- data/Gemfile +1 -1
- data/README.adoc +1 -1
- data/docs/c14n/README.md +39 -0
- data/docs/c14n/algorithms.md +65 -0
- data/docs/c14n/api.md +62 -0
- data/docs/c14n/examples.md +47 -0
- data/docs/signature/c14n.md +8 -6
- data/lib/moxml/adapter/base.rb +14 -0
- data/lib/moxml/adapter/leptris.rb +49 -4
- data/lib/moxml/context.rb +12 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd.rb +66 -0
- data/lib/moxml.rb +1 -0
- data/spec/moxml/plan_conformance_spec.rb +24 -0
- data/spec/moxml/xsd_spec.rb +66 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a30730e871854c7b88fc3b6a3498b4f4d5504ac47caa2aabcc530f2bf0eec96
|
|
4
|
+
data.tar.gz: 8d4f301d7d100cab15cc55b4f0e3db36bc5e50916775adfd34f64b816546d466
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b61a839f7399e0b9e9236b1d921cfc161714b114d73da1b737a80c98795d8c88b96f8e85e71c3a71f58a090578b2ba6ffa4ec57cb9a6309cb53296bb000b277
|
|
7
|
+
data.tar.gz: b42fa624031b28da813039d441024be7766f8322fc54f247075b0c205cb79ec57052ababc789ab90f1c3b91b04a0ced4de85760af3178c1675aedd390fdd8046
|
data/Gemfile
CHANGED
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.
|
|
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
|
|
data/docs/c14n/README.md
ADDED
|
@@ -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: `&` → `&`, `<` → `<`, `>` → `>`
|
|
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
|
+
```
|
data/docs/signature/c14n.md
CHANGED
|
@@ -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
|
-
**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
data/lib/moxml/adapter/base.rb
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
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.
|
|
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-
|
|
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
|