moxml 0.5.170 → 0.5.172
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/README.adoc +6 -7
- data/Rakefile +23 -0
- data/conformance/moxml-conformance.json +413 -0
- data/lib/moxml/adapter/customized_leptris/declaration.rb +9 -0
- data/lib/moxml/adapter/customized_leptris/doctype.rb +8 -0
- data/lib/moxml/adapter/customized_leptris/entity_reference.rb +8 -0
- data/lib/moxml/adapter/customized_leptris/text_segment.rb +8 -0
- data/lib/moxml/adapter/customized_leptris.rb +5 -0
- data/lib/moxml/adapter/customized_libxml/cdata.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/comment.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/declaration.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/element.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/entity_reference.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/node.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -0
- data/lib/moxml/adapter/customized_libxml/text.rb +2 -0
- data/lib/moxml/adapter/customized_libxml.rb +3 -0
- data/lib/moxml/adapter/customized_oga/entity_decoder.rb +8 -0
- data/lib/moxml/adapter/customized_oga/raw_value_override.rb +8 -0
- data/lib/moxml/adapter/customized_oga/xml_declaration.rb +8 -0
- data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
- data/lib/moxml/adapter/customized_oga.rb +4 -0
- data/lib/moxml/adapter/customized_ox/attribute.rb +5 -0
- data/lib/moxml/adapter/customized_ox/entity_reference.rb +5 -0
- data/lib/moxml/adapter/customized_ox/namespace.rb +5 -0
- data/lib/moxml/adapter/customized_ox/text.rb +6 -0
- data/lib/moxml/adapter/customized_ox.rb +3 -0
- data/lib/moxml/adapter/customized_rexml/entity_reference.rb +5 -0
- data/lib/moxml/adapter/customized_rexml/formatter.rb +6 -0
- data/lib/moxml/adapter/customized_rexml.rb +4 -0
- data/lib/moxml/adapter/leptris.rb +15 -32
- data/lib/moxml/adapter/nokogiri.rb +12 -4
- data/lib/moxml/adapter.rb +33 -0
- data/lib/moxml/context.rb +1 -0
- data/lib/moxml/version.rb +1 -1
- data/lib/moxml/xsd/builtin_lexical.rb +100 -0
- data/spec/moxml/adapter/customized_lifecycle_audit_spec.rb +49 -0
- data/spec/moxml/adapter_deprecation_spec.rb +49 -0
- data/spec/moxml/adapter_registry_spec.rb +44 -0
- data/spec/moxml/customized_lifecycle_spec.rb +47 -0
- data/spec/moxml/sax_entity_parity_spec.rb +27 -2
- data/spec/moxml/xsd_conformance_cases_spec.rb +37 -0
- metadata +8 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# rubocop:disable Style/FrozenStringLiteralComment
|
|
2
2
|
|
|
3
|
-
require "oga"
|
|
3
|
+
# Lifecycle record: engine gap (upstream oga); see the patched-class URL below.require "oga"
|
|
4
4
|
|
|
5
5
|
# monkey patch the Oga generator because it's not configurable
|
|
6
6
|
# https://github.com/yorickpeterse/oga/blob/main/lib/oga/xml/generator.rb
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Moxml
|
|
4
4
|
module Adapter
|
|
5
|
+
# Lifecycle record (#495): every override serves the :oga adapter,
|
|
6
|
+
# deprecated in moxml#491 and deleted with the Opal surface in
|
|
7
|
+
# moxml#489 phase 4. No separate upstream ask -- the adapter's
|
|
8
|
+
# fate governs this file.
|
|
5
9
|
module CustomizedOga
|
|
6
10
|
autoload :EntityDecoderOverride, "moxml/adapter/customized_oga/entity_decoder"
|
|
7
11
|
autoload :RawValueOverride, "moxml/adapter/customized_oga/raw_value_override"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Engine gap (upstream ox): an ::Ox::Attribute carries no parent
|
|
4
|
+
# back-reference and no prefix slots, so the facade's attribute node
|
|
5
|
+
# contract needs this Node subclass. Upstream tracker:
|
|
6
|
+
# https://github.com/ohler55/ox
|
|
7
|
+
|
|
3
8
|
module Moxml
|
|
4
9
|
module Adapter
|
|
5
10
|
module CustomizedOx
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Parse-path alias: ox expands the built-in entities at parse time and
|
|
4
|
+
# has no reference nodes, so parsed references are the facade's
|
|
5
|
+
# Moxml::Entity::Reference values. Lifecycle: the :ox adapter is
|
|
6
|
+
# deprecated (#491) — this record dies with it.
|
|
7
|
+
|
|
3
8
|
module Moxml
|
|
4
9
|
module Adapter
|
|
5
10
|
module CustomizedOx
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Engine gap (upstream ox): ox has no Namespace node class — prefixes
|
|
4
|
+
# live as attribute spellings — so the facade's namespace node contract
|
|
5
|
+
# needs this value object. Upstream tracker:
|
|
6
|
+
# https://github.com/ohler55/ox
|
|
7
|
+
|
|
3
8
|
module Moxml
|
|
4
9
|
module Adapter
|
|
5
10
|
module CustomizedOx
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Engine gap (upstream ox): Ox uses Strings for text content, but a
|
|
4
|
+
# String cannot carry a @parent back-reference; this ::Ox::Node subclass
|
|
5
|
+
# gives text nodes node identity. NOTE: an ::Ox::Node subclass that is
|
|
6
|
+
# neither ::Ox::Element nor ::Ox::Document changes Ox's dump behavior.
|
|
7
|
+
# Upstream tracker: https://github.com/ohler55/ox
|
|
8
|
+
|
|
3
9
|
module Moxml
|
|
4
10
|
module Adapter
|
|
5
11
|
module CustomizedOx
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Moxml
|
|
4
4
|
module Adapter
|
|
5
|
+
# Lifecycle record (#495): engine-gap shims for the :ox adapter,
|
|
6
|
+
# deprecated in moxml#491 -- removed with it after the cycle.
|
|
7
|
+
# No separate upstream ask; the adapter's fate governs.
|
|
5
8
|
module CustomizedOx
|
|
6
9
|
autoload :Attribute, "moxml/adapter/customized_ox/attribute"
|
|
7
10
|
autoload :EntityReference, "moxml/adapter/customized_ox/entity_reference"
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Parse-path alias: rexml expands the built-in entities at parse time,
|
|
4
|
+
# so parsed references are the facade's Moxml::Entity::Reference values.
|
|
5
|
+
# Lifecycle: the :rexml adapter is Opal-only and dies with the Opal
|
|
6
|
+
# surface (#489 phase 4); third-party engine, no upstream ask.
|
|
7
|
+
|
|
3
8
|
module Moxml
|
|
4
9
|
module Adapter
|
|
5
10
|
module CustomizedRexml
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Custom REXML formatter that fixes indentation and wrapping issues.
|
|
4
|
+
# Engine gap (upstream rexml): REXML's Formatters::Pretty cannot be
|
|
5
|
+
# configured to the facade's whitespace contract. Third-party engine,
|
|
6
|
+
# no upstream ask; the :rexml adapter is Opal-only and dies with the
|
|
7
|
+
# Opal surface (#489 phase 4).
|
|
8
|
+
|
|
3
9
|
require "rexml/formatters/pretty"
|
|
4
10
|
|
|
5
11
|
module Moxml
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Moxml
|
|
4
4
|
module Adapter
|
|
5
|
+
# Lifecycle record (#495): engine-gap shims for the :rexml
|
|
6
|
+
# adapter (Opal fallback; deleted with the Opal surface in
|
|
7
|
+
# moxml#489 phase 4). No separate upstream ask; the adapter's
|
|
8
|
+
# fate governs this file.
|
|
5
9
|
module CustomizedRexml
|
|
6
10
|
autoload :EntityReference,
|
|
7
11
|
"moxml/adapter/customized_rexml/entity_reference"
|
|
@@ -41,8 +41,7 @@ module Moxml
|
|
|
41
41
|
# leptris_node_digest shipped in bindings 1.9.99 (libleptris
|
|
42
42
|
# 1.9.99, engine #869): on-demand Merkle subtree hash, zero
|
|
43
43
|
# cost when unused. Below it, Node#digest answers nil.
|
|
44
|
-
DIGEST_SUPPORTED =
|
|
45
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.99")
|
|
44
|
+
DIGEST_SUPPORTED = true
|
|
46
45
|
|
|
47
46
|
# 1.9.163.7 added the XPath seams (TODO.perf/15+16):
|
|
48
47
|
# Searchable#at_xpath materializes nodeset entry 0 and frees
|
|
@@ -52,8 +51,7 @@ module Moxml
|
|
|
52
51
|
# raw pointers, skipping the per-call EvaluationContext
|
|
53
52
|
# (~2us on every multi-result query).
|
|
54
53
|
NATIVE_XPATH_SEAMS =
|
|
55
|
-
defined?(::Leptris::XML::Native)
|
|
56
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.7")
|
|
54
|
+
defined?(::Leptris::XML::Native)
|
|
57
55
|
|
|
58
56
|
# Opt-in native read layer (leptris-ruby#185, bindings >= 1.9.162.6):
|
|
59
57
|
# TypedData node wrappers with C-bound hot reads and bulk
|
|
@@ -68,8 +66,7 @@ module Moxml
|
|
|
68
66
|
nil
|
|
69
67
|
end
|
|
70
68
|
NATIVE_READ_LAYER =
|
|
71
|
-
defined?(::Leptris::XML::NativeNode)
|
|
72
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.162.6")
|
|
69
|
+
defined?(::Leptris::XML::NativeNode)
|
|
73
70
|
|
|
74
71
|
# leptris-ruby#529: the whole XPath node-set result as an Array
|
|
75
72
|
# of registered natives in ONE C crossing (Document#
|
|
@@ -118,8 +115,7 @@ module Moxml
|
|
|
118
115
|
# (1.9.162.x answered ASCII-8BIT — the reads retagged with a
|
|
119
116
|
# dup+force_encoding per call).
|
|
120
117
|
NATIVE_STRINGS_UTF8 =
|
|
121
|
-
defined?(::Leptris::XML::NativeNode)
|
|
122
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.2")
|
|
118
|
+
defined?(::Leptris::XML::NativeNode)
|
|
123
119
|
|
|
124
120
|
# 1.9.163.5 (leptris-ruby#204/#208): native mutations are
|
|
125
121
|
# version-coherent with the binding (both surfaces' memos
|
|
@@ -127,8 +123,7 @@ module Moxml
|
|
|
127
123
|
# add_child are adoptable end-to-end; NativeNode grew a
|
|
128
124
|
# document accessor and line numbers.
|
|
129
125
|
NATIVE_MUTATIONS_COHERENT =
|
|
130
|
-
defined?(::Leptris::XML::NativeNode)
|
|
131
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.5")
|
|
126
|
+
defined?(::Leptris::XML::NativeNode)
|
|
132
127
|
|
|
133
128
|
# Identity mode (#230 stage 2): the contract modules extended
|
|
134
129
|
# onto natives shadow the C surface (Element#[] over
|
|
@@ -170,16 +165,14 @@ module Moxml
|
|
|
170
165
|
# 1.9.181 (lockstep): Node#source_position — {line, col_start,
|
|
171
166
|
# col_end} from the engine's source-position API (upstream
|
|
172
167
|
# #1124); created nodes report zeros.
|
|
173
|
-
NATIVE_SOURCE_POSITION =
|
|
174
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.181")
|
|
168
|
+
NATIVE_SOURCE_POSITION = true
|
|
175
169
|
|
|
176
170
|
# Entity-reference preservation (leptris-ruby#212 / upstream
|
|
177
171
|
# #1094): 1.9.177 ships ParseOptions.keep_entity_refs plus
|
|
178
172
|
# first-class EntityReference nodes — the marker machinery
|
|
179
173
|
# becomes bypassable when the Context's entity_mode is :keep.
|
|
180
174
|
NATIVE_ENTITY_REFS =
|
|
181
|
-
defined?(::Leptris::XML::EntityReference)
|
|
182
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.177")
|
|
175
|
+
defined?(::Leptris::XML::EntityReference)
|
|
183
176
|
|
|
184
177
|
# 1.9.163.2 moved the native bulk children into C
|
|
185
178
|
# (Native.bulk_children) and fixed the 512 truncation
|
|
@@ -189,8 +182,7 @@ module Moxml
|
|
|
189
182
|
# C bulk path (version-memoized, cheaper than a per-call
|
|
190
183
|
# count+copy).
|
|
191
184
|
NATIVE_BULK_FIXED =
|
|
192
|
-
defined?(::Leptris::XML::NativeNode)
|
|
193
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.163.2")
|
|
185
|
+
defined?(::Leptris::XML::NativeNode)
|
|
194
186
|
|
|
195
187
|
# Bulk SAX record drain (leptris#1298): leptris_sax_records_parse
|
|
196
188
|
# + Leptris::XML::SAX::Records — one crossing per document,
|
|
@@ -552,17 +544,13 @@ module Moxml
|
|
|
552
544
|
# classes do not exist. NATIVE_MUTATIONS_COHERENT is false
|
|
553
545
|
# without the native layer, so all three evaluate false
|
|
554
546
|
# there.
|
|
555
|
-
NATIVE_IDENTITY =
|
|
556
|
-
NATIVE_MUTATIONS_COHERENT &&
|
|
557
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.4")
|
|
547
|
+
NATIVE_IDENTITY = NATIVE_MUTATIONS_COHERENT
|
|
558
548
|
|
|
559
549
|
NATIVE_C_MINT =
|
|
560
550
|
NATIVE_IDENTITY &&
|
|
561
551
|
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.255.2")
|
|
562
552
|
|
|
563
|
-
NATIVE_KLASS_CHILDREN =
|
|
564
|
-
NATIVE_IDENTITY &&
|
|
565
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.193.2")
|
|
553
|
+
NATIVE_KLASS_CHILDREN = NATIVE_IDENTITY
|
|
566
554
|
end
|
|
567
555
|
|
|
568
556
|
if NATIVE_READ_LAYER
|
|
@@ -870,8 +858,7 @@ module Moxml
|
|
|
870
858
|
end
|
|
871
859
|
|
|
872
860
|
NATIVE_C_WALK =
|
|
873
|
-
defined?(::Leptris::XML::NativeNode)
|
|
874
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.174.6")
|
|
861
|
+
defined?(::Leptris::XML::NativeNode)
|
|
875
862
|
|
|
876
863
|
# One C visit walk (TODO.perf/27: post-order/abort state in
|
|
877
864
|
# the C callback, rb_yield direct — no FFI closure per
|
|
@@ -1109,8 +1096,7 @@ module Moxml
|
|
|
1109
1096
|
# on released 1.9.37–1.9.39; 1.9.40 (engine 1.9.14+) restored
|
|
1110
1097
|
# the fallback. Older bindings keep the Ruby-engine routing in
|
|
1111
1098
|
# native_expression?.
|
|
1112
|
-
PREFIXED_ATTR_PREDICATES_NATIVE =
|
|
1113
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.40")
|
|
1099
|
+
PREFIXED_ATTR_PREDICATES_NATIVE = true
|
|
1114
1100
|
|
|
1115
1101
|
# leptris/leptris#636 (leptris-ruby 1.9.42): libxml2-compatible
|
|
1116
1102
|
# pretty-print — child-PI lines, DOCTYPE internal-subset layout,
|
|
@@ -1118,8 +1104,7 @@ module Moxml
|
|
|
1118
1104
|
# unit (`to_xml`'s indent_text takes the unit string). Older
|
|
1119
1105
|
# floor bindings treat indent_text as a display-form boolean, so
|
|
1120
1106
|
# the passthrough and the parity pins gate on this.
|
|
1121
|
-
LIBXML2_LAYOUT_PARITY =
|
|
1122
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.42")
|
|
1107
|
+
LIBXML2_LAYOUT_PARITY = true
|
|
1123
1108
|
|
|
1124
1109
|
# leptris-ruby#109 (1.9.45): Element#to_xml takes the
|
|
1125
1110
|
# indent-unit string, so moxml's per-child document composition
|
|
@@ -1127,13 +1112,11 @@ module Moxml
|
|
|
1127
1112
|
# emit spaces until the engine fix (leptris/leptris#658); the
|
|
1128
1113
|
# boolean display form stays document-level and is never
|
|
1129
1114
|
# forwarded (the element face raises on it).
|
|
1130
|
-
INDENT_UNIT_SUPPORTED =
|
|
1131
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.45")
|
|
1115
|
+
INDENT_UNIT_SUPPORTED = true
|
|
1132
1116
|
|
|
1133
1117
|
# leptris-ruby#115 (fixed 1.9.50): the element unit path keeps
|
|
1134
1118
|
# child comments.
|
|
1135
|
-
ELEMENT_UNIT_COMMENTS =
|
|
1136
|
-
Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.50")
|
|
1119
|
+
ELEMENT_UNIT_COMMENTS = true
|
|
1137
1120
|
|
|
1138
1121
|
# leptris/leptris#677: the engine's DROP_WS_TEXT trims boundary
|
|
1139
1122
|
# whitespace of non-blank text nodes. Probed at load rather
|
|
@@ -800,11 +800,19 @@ module Moxml
|
|
|
800
800
|
@handler.on_end_document
|
|
801
801
|
end
|
|
802
802
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
803
|
+
# libxml hands over namespace declaration URIs already decoded,
|
|
804
|
+
# while attribute values still carry their entity references;
|
|
805
|
+
# decoding the URIs again corrupts "&" into "&".
|
|
806
|
+
def start_element_namespace(name, attrs = [], prefix = nil, _uri = nil,
|
|
807
|
+
namespaces = [])
|
|
808
|
+
attr_hash = {}
|
|
809
|
+
attrs.each do |attr|
|
|
810
|
+
key = attr.prefix ? "#{attr.prefix}:#{attr.localname}" : attr.localname
|
|
811
|
+
attr_hash[key] = Moxml::Adapter::Base.decode_entities(attr.value)
|
|
806
812
|
end
|
|
807
|
-
|
|
813
|
+
ns_hash = namespaces.empty? ? EMPTY_NAMESPACES : namespaces.to_h
|
|
814
|
+
qualified = prefix ? "#{prefix}:#{name}" : name
|
|
815
|
+
@handler.on_start_element(qualified, attr_hash, ns_hash)
|
|
808
816
|
end
|
|
809
817
|
|
|
810
818
|
def end_element(name)
|
data/lib/moxml/adapter.rb
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Moxml
|
|
4
4
|
module Adapter
|
|
5
|
+
# Extension contract (#494): adding an adapter is exactly the trio
|
|
6
|
+
# below — nothing else, anywhere. The dispatch is open for
|
|
7
|
+
# extension and closed against edit: registration is data, the
|
|
8
|
+
# load path is generic, and spec/moxml/adapter_registry_spec.rb
|
|
9
|
+
# walks the registry mechanically so a skipped leg fails there.
|
|
10
|
+
#
|
|
11
|
+
# 1. one entry in AVAILABLE_ADAPTERS,
|
|
12
|
+
# 2. a requireable file at moxml/adapter/<name> (a
|
|
13
|
+
# `Customized<Name>` namespace module is optional),
|
|
14
|
+
# 3. a subclass of Adapter::Base in that file.
|
|
15
|
+
#
|
|
16
|
+
# Deprecated adapters live in DEPRECATED_ADAPTERS (still fully
|
|
17
|
+
# registered and loadable) until their removal milestone.
|
|
5
18
|
autoload :Base, "moxml/adapter/base"
|
|
6
19
|
autoload :CustomizedOga, "moxml/adapter/customized_oga"
|
|
7
20
|
autoload :CustomizedOx, "moxml/adapter/customized_ox"
|
|
@@ -25,7 +38,27 @@ module Moxml
|
|
|
25
38
|
headed_ox: "HeadedOx",
|
|
26
39
|
}.freeze
|
|
27
40
|
|
|
41
|
+
# Adapters in their deprecation cycle (#491): warned once per
|
|
42
|
+
# process at configuration, removed after the cycle. The value is
|
|
43
|
+
# the migration guidance shown in the warning.
|
|
44
|
+
DEPRECATED_ADAPTERS = {
|
|
45
|
+
headed_ox: "use :leptris (primary) or :nokogiri; headed_ox is a pure alias of :ox",
|
|
46
|
+
libxml: "use :nokogiri — the same libxml2 engine, governed by the consistency suite",
|
|
47
|
+
ox: "use :leptris (primary) or :nokogiri",
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
28
50
|
class << self
|
|
51
|
+
# One-time deprecation notice per adapter per process (#491).
|
|
52
|
+
def warn_deprecated!(name)
|
|
53
|
+
guidance = DEPRECATED_ADAPTERS[name.to_sym]
|
|
54
|
+
return unless guidance
|
|
55
|
+
return if (@deprecation_warned ||= {})[name.to_sym]
|
|
56
|
+
|
|
57
|
+
@deprecation_warned[name.to_sym] = true
|
|
58
|
+
warn "moxml: the :#{name} adapter is deprecated and will be " \
|
|
59
|
+
"removed; #{guidance}"
|
|
60
|
+
end
|
|
61
|
+
|
|
29
62
|
def load(name)
|
|
30
63
|
validate_platform!(name)
|
|
31
64
|
require_adapter(name)
|
data/lib/moxml/context.rb
CHANGED
|
@@ -26,6 +26,7 @@ module Moxml
|
|
|
26
26
|
attr_reader :config
|
|
27
27
|
|
|
28
28
|
def initialize(adapter = nil)
|
|
29
|
+
Adapter.warn_deprecated!(adapter) if adapter
|
|
29
30
|
@config = Config.new(adapter)
|
|
30
31
|
# Bumped on any namespace-scope mutation; Element scope caches
|
|
31
32
|
# compare against it so every wrapper sees changes without
|
data/lib/moxml/version.rb
CHANGED
|
@@ -255,6 +255,106 @@ module Moxml
|
|
|
255
255
|
"xs:NOTATION" => ->(lexical) { qname?(collapsed(lexical)) },
|
|
256
256
|
}.freeze
|
|
257
257
|
|
|
258
|
+
# The cross-language conformance dataset (#496): the model's
|
|
259
|
+
# builtin-lexical answers as data, pinned BY the Ruby suite
|
|
260
|
+
# (spec/moxml/xsd_conformance_cases_spec.rb) and exported to
|
|
261
|
+
# conformance/moxml-conformance.json by `rake conformance:export`
|
|
262
|
+
# — moxml-rs and moxml-ts run the same rows. Each row:
|
|
263
|
+
# [type, lexical, expected].
|
|
264
|
+
CONFORMANCE_CASES = [
|
|
265
|
+
# token family / collapse
|
|
266
|
+
["xs:token", "a b", true],
|
|
267
|
+
["xs:ID", " id ", true],
|
|
268
|
+
["xs:NMTOKEN", "\tx ", true],
|
|
269
|
+
["xs:ID", "1d", false],
|
|
270
|
+
# unbounded integers
|
|
271
|
+
["xs:integer", "1" * 29, true],
|
|
272
|
+
["xs:positiveInteger", "1" * 29, true],
|
|
273
|
+
["xs:nonNegativeInteger", "1" * 29, true],
|
|
274
|
+
["xs:negativeInteger", "-1#{'0' * 20}", true],
|
|
275
|
+
["xs:nonPositiveInteger", "0", true],
|
|
276
|
+
["xs:nonPositiveInteger", "-0", true],
|
|
277
|
+
# bounded integers
|
|
278
|
+
["xs:long", "9223372036854775807", true],
|
|
279
|
+
["xs:long", "9223372036854775808", false],
|
|
280
|
+
["xs:unsignedLong", "18446744073709551615", true],
|
|
281
|
+
["xs:unsignedLong", "18446744073709551616", false],
|
|
282
|
+
["xs:byte", "128", false],
|
|
283
|
+
["xs:unsignedByte", "256", false],
|
|
284
|
+
# float/decimal spellings
|
|
285
|
+
["xs:float", "+INF", true],
|
|
286
|
+
["xs:double", "-INF", true],
|
|
287
|
+
["xs:float", "NaN", true],
|
|
288
|
+
["xs:float", "1.5E-3", true],
|
|
289
|
+
["xs:decimal", "5.", true],
|
|
290
|
+
["xs:decimal", ".5", true],
|
|
291
|
+
["xs:decimal", "1e3", false],
|
|
292
|
+
# calendar
|
|
293
|
+
["xs:date", "2024-02-29", true],
|
|
294
|
+
["xs:date", "1999-02-29", false],
|
|
295
|
+
["xs:date", "2024-04-31", false],
|
|
296
|
+
["xs:date", "2024-13-01", false],
|
|
297
|
+
["xs:date", "not-a-date", false],
|
|
298
|
+
["xs:dateTime", "2024-02-29T23:59:59Z", true],
|
|
299
|
+
["xs:dateTime", "1999-02-29T00:00:00", false],
|
|
300
|
+
["xs:dateTime", "not-a-date", false],
|
|
301
|
+
["xs:dateTimeStamp", "2024-02-29T00:00:00Z", true],
|
|
302
|
+
["xs:dateTimeStamp", "2024-02-29T00:00:00", false],
|
|
303
|
+
["xs:time", "23:59:59.999+05:30", true],
|
|
304
|
+
["xs:time", "24:00:00", false],
|
|
305
|
+
["xs:time", "25:20:00", false],
|
|
306
|
+
["xs:time", "13:20:60", false],
|
|
307
|
+
["xs:gYearMonth", "2024-02Z", true],
|
|
308
|
+
["xs:gYearMonth", "2024-13", false],
|
|
309
|
+
["xs:gMonthDay", "--02-29", true],
|
|
310
|
+
["xs:gMonthDay", "--02-30", false],
|
|
311
|
+
["xs:gDay", "---31", true],
|
|
312
|
+
["xs:gDay", "---32", false],
|
|
313
|
+
# duration
|
|
314
|
+
["xs:duration", "P1Y2M3DT4H5M6.5S", true],
|
|
315
|
+
["xs:duration", "PT0S", true],
|
|
316
|
+
["xs:duration", "-P1D", true],
|
|
317
|
+
["xs:duration", "P", false],
|
|
318
|
+
["xs:duration", "PT", false],
|
|
319
|
+
["xs:duration", "P1Y2MT", false],
|
|
320
|
+
["xs:duration", "P200.5Y", false],
|
|
321
|
+
["xs:dayTimeDuration", "P1DT2H", true],
|
|
322
|
+
["xs:dayTimeDuration", "P1Y", false],
|
|
323
|
+
["xs:yearMonthDuration", "P1Y2M", true],
|
|
324
|
+
["xs:yearMonthDuration", "P1D", false],
|
|
325
|
+
["xs:yearMonthDuration", "P1YT1H", false],
|
|
326
|
+
# binary
|
|
327
|
+
["xs:base64Binary", "aGVsbG8=", true],
|
|
328
|
+
["xs:base64Binary", "abcd", true],
|
|
329
|
+
["xs:base64Binary", "abcde", false],
|
|
330
|
+
["xs:base64Binary", "a b", false],
|
|
331
|
+
["xs:hexBinary", "0A", true],
|
|
332
|
+
["xs:hexBinary", "0AB", false],
|
|
333
|
+
# QName family
|
|
334
|
+
["xs:QName", "foo", true],
|
|
335
|
+
["xs:QName", "ns:foo", true],
|
|
336
|
+
["xs:QName", "xmlns:xsi", false],
|
|
337
|
+
["xs:QName", ":foo", false],
|
|
338
|
+
["xs:QName", "a:b:c", false],
|
|
339
|
+
["xs:NOTATION", "foo", true],
|
|
340
|
+
# lists: items, not characters
|
|
341
|
+
["xs:IDREFS", "a b", true],
|
|
342
|
+
["xs:IDREFS", "a", true],
|
|
343
|
+
["xs:IDREFS", "", false],
|
|
344
|
+
["xs:NMTOKENS", "a.b-c:d e", true],
|
|
345
|
+
["xs:ENTITIES", "a 1", false],
|
|
346
|
+
# open lexical spaces
|
|
347
|
+
["xs:string", "anything <>&", true],
|
|
348
|
+
["xs:anyURI", "http://example.com/a b", true],
|
|
349
|
+
["xs:boolean", "true", true],
|
|
350
|
+
["xs:boolean", "0", true],
|
|
351
|
+
["xs:boolean", "TRUE", false],
|
|
352
|
+
["xs:language", "en-US", true],
|
|
353
|
+
["xs:language", "en_US", false],
|
|
354
|
+
["xs:Name", "a:b", true],
|
|
355
|
+
["xs:NCName", "a:b", false],
|
|
356
|
+
].map(&:freeze).freeze
|
|
357
|
+
|
|
258
358
|
private_constant :CHECKERS
|
|
259
359
|
end
|
|
260
360
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# #495: the Customized* adapter layer is either a removable-lifecycle
|
|
6
|
+
# record (an engine gap with a tracked upstream issue) or it dissolves
|
|
7
|
+
# into Base. This audit makes the lifecycle record checkable instead of
|
|
8
|
+
# folklore: every override file must cite its issue (an #NNN reference
|
|
9
|
+
# for our engines, or an upstream tracker URL for third-party engines
|
|
10
|
+
# we do not file against), and every Customized module must be wired
|
|
11
|
+
# into exactly its adapter (no dead records).
|
|
12
|
+
RSpec.describe "Customized adapter lifecycle audit" do
|
|
13
|
+
let(:override_files) do
|
|
14
|
+
Dir[File.expand_path("../../lib/moxml/adapter/customized_*/*.rb", __dir__)].sort
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "cites a tracked issue or upstream tracker in every override file" do
|
|
18
|
+
broken = override_files.reject do |file|
|
|
19
|
+
head = File.readlines(file).first(25).join
|
|
20
|
+
head.match?(/#\s*#\d{2,5}\b/) || head.include?("github.com/")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
expect(broken).to be_empty, <<~MSG
|
|
24
|
+
Customized* override files without a lifecycle citation:
|
|
25
|
+
#{broken.join("\n")}
|
|
26
|
+
Each file's header must cite the open upstream issue that explains
|
|
27
|
+
why the override exists (#NNN for leptris; the upstream tracker URL
|
|
28
|
+
for third-party engines), or the #491 deprecation that retires it.
|
|
29
|
+
MSG
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "wires every Customized module into exactly its adapter" do
|
|
33
|
+
shims = Dir[File.expand_path("../../lib/moxml/adapter/customized_*.rb", __dir__)]
|
|
34
|
+
|
|
35
|
+
shims.each do |shim|
|
|
36
|
+
module_name = File.read(shim)[/module (Customized\w+)/, 1]
|
|
37
|
+
adapter_file = File.expand_path(
|
|
38
|
+
"../../lib/moxml/adapter/#{module_name.delete_prefix('Customized').downcase}.rb",
|
|
39
|
+
__dir__,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
expect(File).to exist(adapter_file),
|
|
43
|
+
"#{module_name} has no #{adapter_file} to customize"
|
|
44
|
+
|
|
45
|
+
expect(File.read(adapter_file)).to include(module_name),
|
|
46
|
+
"#{module_name} is not wired into its adapter"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Moxml::Adapter do
|
|
6
|
+
describe ".warn_deprecated!" do
|
|
7
|
+
# The once-per-process state is module-level; isolate each
|
|
8
|
+
# example from prior loads and from later suites.
|
|
9
|
+
around do |example|
|
|
10
|
+
described_class.instance_variable_set(:@deprecation_warned, {})
|
|
11
|
+
example.run
|
|
12
|
+
described_class.instance_variable_set(:@deprecation_warned, {})
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "warns once per deprecated adapter per process" do
|
|
16
|
+
expect do
|
|
17
|
+
Moxml.new(:headed_ox)
|
|
18
|
+
end.to output(%r{:headed_ox adapter is deprecated}).to_stderr
|
|
19
|
+
expect do
|
|
20
|
+
Moxml.new(:headed_ox)
|
|
21
|
+
end.not_to output.to_stderr
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "guides every deprecated adapter toward the governed matrix" do
|
|
25
|
+
described_class::DEPRECATED_ADAPTERS.each_value do |guidance|
|
|
26
|
+
expect(guidance).to match(/:nokogiri|:leptris/)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "does not warn for the governed matrix" do
|
|
31
|
+
expect do
|
|
32
|
+
Moxml.new(:leptris)
|
|
33
|
+
Moxml.new(:nokogiri)
|
|
34
|
+
end.not_to output(/deprecated/).to_stderr
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "does not warn for unknown names" do
|
|
38
|
+
expect do
|
|
39
|
+
described_class.warn_deprecated!(:definitely_not_an_adapter)
|
|
40
|
+
end.not_to output.to_stderr
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "keeps the deprecated adapters loadable through the cycle" do
|
|
44
|
+
described_class::DEPRECATED_ADAPTERS.each_key do |name|
|
|
45
|
+
expect(described_class.available?(name)).to be(true)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# The registry's extension contract (#494): adding an adapter is the
|
|
6
|
+
# documented trio — one AVAILABLE_ADAPTERS entry, one requireable
|
|
7
|
+
# adapter file, one Base subclass. The dispatch is open for extension:
|
|
8
|
+
# this spec walks the registry mechanically so a new adapter that
|
|
9
|
+
# skips any leg fails here, not in a consumer.
|
|
10
|
+
RSpec.describe "Adapter registry extension contract" do
|
|
11
|
+
it "loads every registered adapter through the public face" do
|
|
12
|
+
Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
|
|
13
|
+
adapter = Moxml::Adapter.load(name)
|
|
14
|
+
expect(adapter).not_to be_nil
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "resolves every registered adapter to an Adapter::Base subclass" do
|
|
19
|
+
Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
|
|
20
|
+
adapter = Moxml::Adapter.load(name)
|
|
21
|
+
expect(adapter).to be <= Moxml::Adapter::Base
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "answers available? truthfully for every registered adapter" do
|
|
26
|
+
Moxml::Adapter::AVAILABLE_ADAPTERS.each do |name|
|
|
27
|
+
expect(Moxml::Adapter.available?(name)).to be(true)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "rejects unregistered names before any require runs" do
|
|
32
|
+
expect do
|
|
33
|
+
Moxml::Adapter.load(:definitely_not_an_adapter)
|
|
34
|
+
end.to raise_error(Moxml::AdapterError, /not available on this platform/)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "keeps the deprecated adapters inside the registry with guidance" do
|
|
38
|
+
described = Moxml::Adapter::DEPRECATED_ADAPTERS
|
|
39
|
+
expect(described.keys).to all(satisfy { |name| Moxml::Adapter::AVAILABLE_ADAPTERS.include?(name) })
|
|
40
|
+
described.each_value do |guidance|
|
|
41
|
+
expect(guidance).to match(/:nokogiri|:leptris/)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
# The customized_* layer's doctrine (#495): each namespace module is
|
|
6
|
+
# either a tracked shim (an engine gap with a stated resolution) or an
|
|
7
|
+
# adapter-fate case (dies with its deprecated adapter). A Customized*
|
|
8
|
+
# file exists only when its header says which — the record is
|
|
9
|
+
# checkable, not folklore.
|
|
10
|
+
RSpec.describe "customized adapter layer lifecycle" do
|
|
11
|
+
def customized_files
|
|
12
|
+
Dir["lib/moxml/adapter/customized_*.rb"]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "has customized namespaces to govern" do
|
|
16
|
+
expect(customized_files).not_to be_empty
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "cites its lifecycle in every module header" do
|
|
20
|
+
customized_files.each do |path|
|
|
21
|
+
header = File.readlines(path).first(30).join
|
|
22
|
+
expect(header).to match(/#\d{3}/),
|
|
23
|
+
"#{path} carries no lifecycle citation (#495)"
|
|
24
|
+
expect(header).to match(/Lifecycle record|no native representation/),
|
|
25
|
+
"#{path} header does not state its lifecycle"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "marks adapter-fate cases as deprecated or Opal-bound" do
|
|
30
|
+
adapter_fate = customized_files.reject { |p| p.include?("leptris") }
|
|
31
|
+
expect(adapter_fate).not_to be_empty
|
|
32
|
+
adapter_fate.each do |path|
|
|
33
|
+
header = File.readlines(path).first(30).join
|
|
34
|
+
expect(header).to match(/moxml#491|moxml#489/),
|
|
35
|
+
"#{path} is an adapter-fate case but cites neither " \
|
|
36
|
+
"its deprecation (#491) nor the Opal removal (#489)"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "keeps the tracked shim pointed at its resolution" do
|
|
41
|
+
header = File.readlines("lib/moxml/adapter/customized_leptris.rb")
|
|
42
|
+
.first(30).join
|
|
43
|
+
expect(header).to include("tracked shim"), "the leptris shim must say so"
|
|
44
|
+
expect(header).to include("dissolve into Base"),
|
|
45
|
+
"the leptris shim must state its end state"
|
|
46
|
+
end
|
|
47
|
+
end
|