moxml 0.5.171 → 0.5.173

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +23 -0
  3. data/conformance/moxml-conformance.json +413 -0
  4. data/docs/_pages/model-coverage.adoc +1 -0
  5. data/docs/_references/processing-model.adoc +7 -1
  6. data/lib/moxml/adapter/base.rb +9 -0
  7. data/lib/moxml/adapter/customized_leptris/declaration.rb +9 -0
  8. data/lib/moxml/adapter/customized_leptris/doctype.rb +8 -0
  9. data/lib/moxml/adapter/customized_leptris/entity_reference.rb +8 -0
  10. data/lib/moxml/adapter/customized_leptris/text_segment.rb +8 -0
  11. data/lib/moxml/adapter/customized_leptris.rb +5 -0
  12. data/lib/moxml/adapter/customized_libxml/cdata.rb +2 -0
  13. data/lib/moxml/adapter/customized_libxml/comment.rb +2 -0
  14. data/lib/moxml/adapter/customized_libxml/declaration.rb +2 -0
  15. data/lib/moxml/adapter/customized_libxml/element.rb +2 -0
  16. data/lib/moxml/adapter/customized_libxml/entity_reference.rb +2 -0
  17. data/lib/moxml/adapter/customized_libxml/node.rb +2 -0
  18. data/lib/moxml/adapter/customized_libxml/processing_instruction.rb +2 -0
  19. data/lib/moxml/adapter/customized_libxml/text.rb +2 -0
  20. data/lib/moxml/adapter/customized_libxml.rb +3 -0
  21. data/lib/moxml/adapter/customized_oga/entity_decoder.rb +8 -0
  22. data/lib/moxml/adapter/customized_oga/raw_value_override.rb +8 -0
  23. data/lib/moxml/adapter/customized_oga/xml_declaration.rb +8 -0
  24. data/lib/moxml/adapter/customized_oga/xml_generator.rb +1 -1
  25. data/lib/moxml/adapter/customized_oga.rb +4 -0
  26. data/lib/moxml/adapter/customized_ox/attribute.rb +5 -0
  27. data/lib/moxml/adapter/customized_ox/entity_reference.rb +5 -0
  28. data/lib/moxml/adapter/customized_ox/namespace.rb +5 -0
  29. data/lib/moxml/adapter/customized_ox/text.rb +6 -0
  30. data/lib/moxml/adapter/customized_ox.rb +3 -0
  31. data/lib/moxml/adapter/customized_rexml/entity_reference.rb +5 -0
  32. data/lib/moxml/adapter/customized_rexml/formatter.rb +6 -0
  33. data/lib/moxml/adapter/customized_rexml.rb +4 -0
  34. data/lib/moxml/adapter/leptris.rb +31 -0
  35. data/lib/moxml/adapter.rb +13 -0
  36. data/lib/moxml/version.rb +1 -1
  37. data/lib/moxml/xsd/builtin_lexical.rb +100 -0
  38. data/spec/moxml/adapter/customized_lifecycle_audit_spec.rb +49 -0
  39. data/spec/moxml/adapter_capabilities_spec.rb +48 -0
  40. data/spec/moxml/adapter_registry_spec.rb +44 -0
  41. data/spec/moxml/customized_lifecycle_spec.rb +47 -0
  42. data/spec/moxml/xsd_conformance_cases_spec.rb +37 -0
  43. data/spec/support/model_coverage.rb +4 -0
  44. metadata +7 -1
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Stand-in XML declaration node.
4
+ #
5
+ # Engine gap (upstream oga): Oga::XML::XmlDeclaration is not writable —
6
+ # version/encoding/standalone cannot be set on an instance the way the
7
+ # facade contract requires, so this subclass owns the state and renders
8
+ # the declaration itself. Upstream tracker:
9
+ # https://github.com/yorickpeterse/oga
10
+
3
11
  require "oga"
4
12
 
5
13
  module Moxml
@@ -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"
@@ -320,6 +320,37 @@ module Moxml
320
320
  BINDING_OF_CAP = 8_192
321
321
 
322
322
  class << self
323
+ # The engine capability report (#493): one structured face
324
+ # answering what THIS installed binding+engine serves, queried
325
+ # once at attach — the replacement for the version-gate-
326
+ # constant class. The support faces (xsd, relaxng, xslt,
327
+ # digest) answer even when the floor makes them unconditional —
328
+ # consumers ask for them by name; the internal routing gates
329
+ # the floor made constant are absent by design. Frozen; the
330
+ # keys are the model's capability names.
331
+ def capabilities
332
+ @capabilities ||= {
333
+ engine_version: ::Leptris::VERSION,
334
+ native_read_layer: !NATIVE_READ_LAYER.nil?,
335
+ native_xpath_seams: !NATIVE_XPATH_SEAMS.nil?,
336
+ bulk_xpath_rows: BULK_XPATH_ROWS,
337
+ add_element_full: ADD_ELEMENT_FULL,
338
+ native_strings_utf8: !NATIVE_STRINGS_UTF8.nil?,
339
+ native_mutations_coherent: !NATIVE_MUTATIONS_COHERENT.nil?,
340
+ native_entity_refs: !NATIVE_ENTITY_REFS.nil?,
341
+ native_bulk_fixed: !NATIVE_BULK_FIXED.nil?,
342
+ native_bulk_sax_records: NATIVE_SAX_RECORDS,
343
+ native_document_parts: NATIVE_DOC_PARTS,
344
+ native_plan_rows: NATIVE_PLAN_ROWS,
345
+ native_plan_structs: NATIVE_PLAN_STRUCTS,
346
+ native_plan_typed: NATIVE_PLAN_TYPED,
347
+ digest: DIGEST_SUPPORTED,
348
+ xsd: XSD_SUPPORTED,
349
+ relaxng: RELAXNG_SUPPORTED,
350
+ xslt: XSLT_SUPPORTED,
351
+ }.freeze
352
+ end
353
+
323
354
  # WeakMap has no #clear — 0.5.100's valve crashed every parse
324
355
  # crossing the cap on 3.4+ (NoMethodError). Replace the
325
356
  # registry wholesale, type-preserving: the WeakMap flavor
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"
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.171"
4
+ VERSION = "0.5.173"
5
5
  end
@@ -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,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Processing model §Errors and loud degradation (normative): pinned here.
4
+
5
+ require "spec_helper"
6
+
7
+ # The engine capability report (#493): one structured face answering
8
+ # what the installed binding+engine serves — queried once at attach,
9
+ # the replacement for the version-gate-constant class. Only
10
+ # runtime-variable faces appear; anything the floor makes
11
+ # unconditional is absent by design.
12
+ RSpec.describe "engine capability report" do
13
+ it "answers for leptris with the engine version and known faces" do
14
+ caps = Moxml::Adapter::Leptris.capabilities
15
+ expect(caps).to be_frozen
16
+ expect(caps[:engine_version]).to match(/\A1\.\d+\.\d+/)
17
+ expect(caps.keys).to include(
18
+ :native_read_layer, :native_plan_structs, :native_bulk_sax_records,
19
+ :native_document_parts, :xsd, :relaxng, :xslt, :digest
20
+ )
21
+ end
22
+
23
+ it "reports only booleans beyond the version" do
24
+ caps = Moxml::Adapter::Leptris.capabilities
25
+ caps.except(:engine_version).each_value do |value|
26
+ expect(value).to eql(true).or eql(false)
27
+ end
28
+ end
29
+
30
+ it "keeps the unwound internal gates out of the report" do
31
+ caps = Moxml::Adapter::Leptris.capabilities
32
+ expect(caps.keys).not_to include(
33
+ :prefixed_attr_predicates_native, :libxml2_layout_parity,
34
+ :indent_unit_supported, :element_unit_comments
35
+ )
36
+ expect(caps[:xsd]).to be(true)
37
+ expect(caps[:xslt]).to be(true)
38
+ end
39
+
40
+ it "is memoized — repeated queries answer the same frozen object" do
41
+ expect(Moxml::Adapter::Leptris.capabilities)
42
+ .to equal(Moxml::Adapter::Leptris.capabilities)
43
+ end
44
+
45
+ it "answers nil on adapters without a capability story" do
46
+ expect(Moxml::Adapter::Base.capabilities).to be_nil
47
+ end
48
+ 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
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Processing model §Validation (normative): pinned here.
4
+
5
+ require "spec_helper"
6
+
7
+ # The conformance dataset is the model's builtin-lexical answers AS
8
+ # DATA (#496): the Ruby suite pins every row here, and the exported
9
+ # JSON (rake conformance:export) is what moxml-rs and moxml-ts run.
10
+ # A row that fails here breaks the cross-language contract, not just
11
+ # this implementation.
12
+ RSpec.describe "builtin lexical conformance dataset" do
13
+ def valid_lexical?(type, lexical)
14
+ Moxml::XSD.builtin_valid?(Moxml.new(:leptris), type, lexical)
15
+ end
16
+
17
+ it "carries the dataset" do
18
+ expect(Moxml::XSD::BuiltinLexical::CONFORMANCE_CASES.size).to be >= 80
19
+ end
20
+
21
+ it "answers every row exactly as the dataset expects" do
22
+ Moxml::XSD::BuiltinLexical::CONFORMANCE_CASES.each do |type, lexical, expected|
23
+ answer = valid_lexical?(type, lexical)
24
+ expect(answer).to eql(expected),
25
+ "#{type} #{lexical.inspect}: got #{answer.inspect}, " \
26
+ "dataset expects #{expected.inspect}"
27
+ end
28
+ end
29
+
30
+ it "keeps every dataset type in the table" do
31
+ Moxml::XSD::BuiltinLexical::CONFORMANCE_CASES.each do |type, _lexical, _expected|
32
+ expect do
33
+ valid_lexical?(type, "")
34
+ end.not_to raise_error
35
+ end
36
+ end
37
+ end
@@ -113,6 +113,10 @@ module ModelCoverage
113
113
  section: "Errors and loud degradation",
114
114
  pin: "spec/consistency/loud_degradation_spec.rb",
115
115
  },
116
+ "engine capability report" => {
117
+ section: "Errors and loud degradation",
118
+ pin: "spec/moxml/adapter_capabilities_spec.rb",
119
+ },
116
120
  }.freeze
117
121
 
118
122
  # Normative sections that are not face families (meta/apparatus).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moxml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.171
4
+ version: 0.5.173
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -53,6 +53,7 @@ files:
53
53
  - bin/console
54
54
  - bin/generate-model-coverage
55
55
  - bin/setup
56
+ - conformance/moxml-conformance.json
56
57
  - data/w3c_entities.json
57
58
  - docs/ENTITY_SUPPORT_FOR_LUTAML_MODEL.md
58
59
  - docs/Gemfile
@@ -479,6 +480,7 @@ files:
479
480
  - spec/moxml/adapter/README.md
480
481
  - spec/moxml/adapter/base_spec.rb
481
482
  - spec/moxml/adapter/columnar_invalidation_spec.rb
483
+ - spec/moxml/adapter/customized_lifecycle_audit_spec.rb
482
484
  - spec/moxml/adapter/entity_restoration_spec.rb
483
485
  - spec/moxml/adapter/headed_ox_spec.rb
484
486
  - spec/moxml/adapter/leptris_adoption_spec.rb
@@ -512,7 +514,9 @@ files:
512
514
  - spec/moxml/adapter/rexml_spec.rb
513
515
  - spec/moxml/adapter/shared_examples/.gitkeep
514
516
  - spec/moxml/adapter/shared_examples/adapter_contract.rb
517
+ - spec/moxml/adapter_capabilities_spec.rb
515
518
  - spec/moxml/adapter_deprecation_spec.rb
519
+ - spec/moxml/adapter_registry_spec.rb
516
520
  - spec/moxml/adapter_spec.rb
517
521
  - spec/moxml/allocation_benchmark_spec.rb
518
522
  - spec/moxml/allocation_guard_spec.rb
@@ -538,6 +542,7 @@ files:
538
542
  - spec/moxml/config_spec.rb
539
543
  - spec/moxml/context_spec.rb
540
544
  - spec/moxml/css_spec.rb
545
+ - spec/moxml/customized_lifecycle_spec.rb
541
546
  - spec/moxml/cycle_rejection_spec.rb
542
547
  - spec/moxml/declaration_preservation_spec.rb
543
548
  - spec/moxml/declaration_spec.rb
@@ -634,6 +639,7 @@ files:
634
639
  - spec/moxml/xpath_capabilities_spec.rb
635
640
  - spec/moxml/xpath_spec.rb
636
641
  - spec/moxml/xsd_builtin_lexical_spec.rb
642
+ - spec/moxml/xsd_conformance_cases_spec.rb
637
643
  - spec/moxml/xsd_engine_meta_spec.rb
638
644
  - spec/moxml/xsd_error_contract_spec.rb
639
645
  - spec/moxml/xsd_handle_mismatch_spec.rb