Class: Canon::Xml::C14n
- Inherits:
-
Object
- Object
- Canon::Xml::C14n
- Defined in:
- lib/canon/xml/c14n.rb
Overview
XML Canonicalization 1.1 implementation Per W3C Recommendation: https://www.w3.org/TR/xml-c14n11/
Constant Summary collapse
- RELATIVE_NS_DECL =
Canon::Error parity with the Ruby lane, whose parse rejects relative namespace URIs. A declaration-shaped scan; a relative-URI-shaped string inside CDATA is the only false positive this can produce.
/xmlns(?::[\w.-]+)?="([^"]*)"/- URI_SCHEME =
%r{\A[a-zA-Z][a-zA-Z0-9+.-]*:}
Class Method Summary collapse
-
.canonicalize(xml, with_comments: false) ⇒ String
Canonicalize an XML document.
-
.canonicalize_subset(xml, xpath, with_comments: false) ⇒ String
Canonicalize a document subset selected by XPath expression.
- .native_c14n_available? ⇒ Boolean
-
.native_canonicalize(xml, with_comments) ⇒ Object
leptris' C-side C14N 1.1 — 23x faster than the Ruby processor through canon's own API (1MB document) and the DEFAULT lane since libleptris 1.9.178 (gem 1.9.178.0): the leptris#1117 families are closed by the native parse-policy knob (
noblanks: true— same compact bytes as the Ruby lane's parse) and by the canonicalizer itself (whitespace-only PI data dropped, document-level "\n" separators inserted). - .reset_native_probe! ⇒ Object
- .validate_relative_namespaces!(xml) ⇒ Object
Class Method Details
.canonicalize(xml, with_comments: false) ⇒ String
Canonicalize an XML document
12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/canon/xml/c14n.rb', line 12 def self.canonicalize(xml, with_comments: false) if (native = native_canonicalize(xml, with_comments)) return native end # Build XPath data model root_node = DataModel.from_xml(xml) # Process to canonical form processor = Processor.new(with_comments: with_comments) processor.process(root_node) end |
.canonicalize_subset(xml, xpath, with_comments: false) ⇒ String
Canonicalize a document subset selected by XPath expression.
Implements W3C C14N 1.1 subset canonicalization:
- Evaluates XPath against the document tree
- Marks matched nodes as the node-set
- Renders canonical form for only the selected nodes, with namespace and attribute inheritance from excluded ancestors
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/canon/xml/c14n.rb', line 108 def self.canonicalize_subset(xml, xpath, with_comments: false) root_node = DataModel.from_xml(xml) # Mark all nodes as NOT in the node-set initially mark_all_nodes(root_node, false) # Evaluate XPath and mark matched nodes matched = XPathEngine.evaluate(root_node, xpath) # If XPath matches root or is empty, fall back to full canonicalization if matched.empty? mark_all_nodes(root_node, true) else # Mark matched nodes and their ancestors/descendants mark_subset(root_node, matched) end # Process to canonical form processor = Processor.new(with_comments: with_comments) processor.process(root_node) end |
.native_c14n_available? ⇒ Boolean
85 86 87 88 89 90 |
# File 'lib/canon/xml/c14n.rb', line 85 def self.native_c14n_available? return @native_c14n_available unless @native_c14n_available.nil? @native_c14n_available = defined?(::Leptris::XML::FFI::C14N_1_1) && ::Leptris::XML::FFI.constants.include?(:C14N_MODE_CANONICAL) end |
.native_canonicalize(xml, with_comments) ⇒ Object
leptris' C-side C14N 1.1 — 23x faster than the Ruby processor
through canon's own API (1MB document) and the DEFAULT lane
since libleptris 1.9.178 (gem 1.9.178.0): the leptris#1117
families are closed by the native parse-policy knob
(noblanks: true — same compact bytes as the Ruby lane's
parse) and by the canonicalizer itself (whitespace-only PI
data dropped, document-level "\n" separators inserted). The
one residual byte difference vs the Ruby lane is intentional
and a correctness improvement: document-level PIs now
serialize in document order (REC-xml-c14n 2.1) instead of
the Ruby lane's non-conformant root-first reorder. Relative
namespace URIs raise exactly as the Ruby lane does.
Comments mode keeps the Ruby path regardless (the native
seam exposes no with-comments form). CANON_C14N_BACKEND=ruby
forces the stdlib lane.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/canon/xml/c14n.rb', line 40 def self.native_canonicalize(xml, with_comments) return nil if with_comments return nil if RUBY_ENGINE == "opal" return nil if ENV["CANON_C14N_BACKEND"].to_s.casecmp("ruby").zero? return nil unless Canon::XmlBackend.moxml? && Canon::XmlParsing.moxml_adapter_name == :leptris return nil unless native_c14n_available? validate_relative_namespaces!(xml) doc = Canon::XmlParsing.moxml_context.parse(xml, readonly: true, strict: false, noblanks: true) begin doc.native.c14n(::Leptris::XML::FFI::C14N_1_1, nil, mode: ::Leptris::XML::FFI::C14N_MODE_CANONICAL) ensure doc.free end rescue StandardError # Malformed inputs are the Ruby path's domain (recovery parse # + parse_errors surfacing), not the native lane's. nil end |
.reset_native_probe! ⇒ Object
92 93 94 |
# File 'lib/canon/xml/c14n.rb', line 92 def self.reset_native_probe! @native_c14n_available = nil end |
.validate_relative_namespaces!(xml) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/canon/xml/c14n.rb', line 73 def self.validate_relative_namespaces!(xml) return unless xml.is_a?(String) xml.scan(RELATIVE_NS_DECL) do |(uri)| next if uri.nil? || uri.empty? unless uri.match?(URI_SCHEME) raise Canon::Error, "Relative namespace URI not allowed: #{uri}" end end end |