moxml 0.5.170 → 0.5.171

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: 68f6b7acd8107269dc0e15246d3990ef57d3afaa0516896c43fa2db3acfc4cc5
4
- data.tar.gz: b6511730c2af6f71b66b1a07b118260fc76c6069bd000be29539beb7462522b5
3
+ metadata.gz: dd5e790791e306829e87770a45424b006c9a535c95c825adfc7d0800816ceb7d
4
+ data.tar.gz: c3348f9fb7c6c0ccf00ab4bc16bba51b0812331d12d8478d2a77e09b8a600e58
5
5
  SHA512:
6
- metadata.gz: b3c3f675bc7ecc526414d7cf045e33dbbcd14ff4b52794552a7ccd4d69b6efab0e3bf14cf5d56e05e3b48e7852795a6a9181ac15c98504233dac324c0c54a6ec
7
- data.tar.gz: 47fdcc6d4e46766f75bf4927b89ae95931d8920e0fc5f8213a48167b2f5b19ded9f1807bbf13c2351e8735e71e3210f7829f913a7bbb2a82781ca88a68405729
6
+ metadata.gz: 2972a94dc0bf75896d29432c1856b949090cbd8547b60c402376f2d715c743c8e029af2181531fbd327d6615a3a6aef5cb83144c5ff2b6389f35bd3ab6dfea59
7
+ data.tar.gz: c89e1be189130b17f0a7f61c67c9caba0624f589c29a2407effba7fb50da72304d33db77001185cb7b079729a7a29140d32e8a492269a58cf748a8e99f0276a2
data/README.adoc CHANGED
@@ -95,7 +95,8 @@ adapter refuses to load below its floor.
95
95
 
96
96
  |Ox
97
97
  |>= 2.14
98
- |Tested against the 2.14 line.
98
+ |DEPRECATED (#491) — warned at configuration; use :leptris (primary)
99
+ or :nokogiri. Removal after the deprecation cycle.
99
100
 
100
101
  |Oga
101
102
  |3.4 / 3.5
@@ -105,8 +106,9 @@ moxml's customized generator and decoder overrides.
105
106
 
106
107
  |LibXML (libxml-ruby)
107
108
  |>= 5.0
108
- |6.x works and is the installed default on Ruby >= 3.2 (libxml-ruby
109
- 6.0 itself requires Ruby >= 3.2); Ruby 3.1 resolves 5.x.
109
+ |DEPRECATED (#491) — warned at configuration; use :nokogiri (the
110
+ same libxml2 engine, governed by the consistency suite). Removal
111
+ after the deprecation cycle.
110
112
 
111
113
  |Leptris (leptris-ruby)
112
114
  |>= 1.9.194.1
@@ -114,10 +116,7 @@ moxml's customized generator and decoder overrides.
114
116
  Raised from 1.9.32 (2026-09-20): every known consumer resolves a
115
117
  modern binding, and the sub-1.9.194 accommodation paths are deleted.
116
118
  Newer bindings unlock more native surface automatically:
117
- 1.9.163.7+ single-result XPath seams, 1.9.174.4+ C-minted identity
118
- wrappers, 1.9.174.6+ one-dispatch subtree walks, 1.9.177+ first-class
119
- entity references (`entity_mode: :keep`), 1.9.193.4+ native plan rows
120
- (`Moxml::Plan`), 1.9.194.1+ hot-read aliases, 1.9.201.1+ the C struct
119
+ 1.9.194.1+ hot-read aliases, 1.9.201.1+ the C struct
121
120
  executor (`Moxml::StructPlan`; the floor is the varargs-mint fix),
122
121
  1.9.204+ native document-level parts (created DOCTYPEs as native
123
122
  nodes, document-PI removal, declaration clear), 1.9.240+ the bulk
@@ -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
- def start_element(name, attributes = [])
804
- attr_hash, ns_hash = split_attributes_and_namespaces(attributes) do |v|
805
- Moxml::Adapter::Base.decode_entities(v)
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
- @handler.on_start_element(name, attr_hash, ns_hash)
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
@@ -25,7 +25,27 @@ module Moxml
25
25
  headed_ox: "HeadedOx",
26
26
  }.freeze
27
27
 
28
+ # Adapters in their deprecation cycle (#491): warned once per
29
+ # process at configuration, removed after the cycle. The value is
30
+ # the migration guidance shown in the warning.
31
+ DEPRECATED_ADAPTERS = {
32
+ headed_ox: "use :leptris (primary) or :nokogiri; headed_ox is a pure alias of :ox",
33
+ libxml: "use :nokogiri — the same libxml2 engine, governed by the consistency suite",
34
+ ox: "use :leptris (primary) or :nokogiri",
35
+ }.freeze
36
+
28
37
  class << self
38
+ # One-time deprecation notice per adapter per process (#491).
39
+ def warn_deprecated!(name)
40
+ guidance = DEPRECATED_ADAPTERS[name.to_sym]
41
+ return unless guidance
42
+ return if (@deprecation_warned ||= {})[name.to_sym]
43
+
44
+ @deprecation_warned[name.to_sym] = true
45
+ warn "moxml: the :#{name} adapter is deprecated and will be " \
46
+ "removed; #{guidance}"
47
+ end
48
+
29
49
  def load(name)
30
50
  validate_platform!(name)
31
51
  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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Moxml
4
- VERSION = "0.5.170"
4
+ VERSION = "0.5.171"
5
5
  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
@@ -15,16 +15,18 @@ require "spec_helper"
15
15
  # the RSpec/* cops that forbid local-variable use inside examples.
16
16
  module SaxEntityParityFixtures
17
17
  class CaptureHandler < Moxml::SAX::ElementHandler
18
- attr_reader :first_attrs, :text
18
+ attr_reader :first_attrs, :first_namespaces, :text
19
19
 
20
20
  def initialize
21
21
  super
22
22
  @first_attrs = nil
23
+ @first_namespaces = nil
23
24
  @text = +""
24
25
  end
25
26
 
26
- def on_start_element(_name, attrs = {}, _namespaces = {})
27
+ def on_start_element(_name, attrs = {}, namespaces = {})
27
28
  @first_attrs = attrs.dup if @first_attrs.nil?
29
+ @first_namespaces = namespaces.dup if @first_namespaces.nil?
28
30
  end
29
31
 
30
32
  def on_characters(chunk)
@@ -51,6 +53,19 @@ module SaxEntityParityFixtures
51
53
  "no entities" => ['<doc x="plain"/>', "plain", "plain"],
52
54
  }.freeze
53
55
 
56
+ # Adapters whose SAX bridge reports namespace declarations in the
57
+ # handler's namespaces hash with the URI decoded exactly once.
58
+ SAX_NAMESPACE_ADAPTERS = %i[nokogiri ox rexml headed_ox].freeze
59
+
60
+ # Each row: [input XML, decoded namespace URI]. The parser decodes a
61
+ # declaration's URI exactly once; a decoded "&#38;" is data and must
62
+ # reach the handler as-is.
63
+ NAMESPACE_CASES = {
64
+ "wrapped amp + decimal ref" => ['<f:doc xmlns:f="http://f/?x=1&amp;#38;y"/>', "http://f/?x=1&#38;y"],
65
+ "wrapped amp + amp" => ['<f:doc xmlns:f="http://f/?x=&amp;amp;"/>', "http://f/?x=&amp;"],
66
+ "plain amp" => ['<f:doc xmlns:f="http://f/?x=1&amp;y=2"/>', "http://f/?x=1&y=2"],
67
+ }.freeze
68
+
54
69
  # Each row: [input XML, decoded text, expected text as it appears in
55
70
  # serialized XML].
56
71
  TEXT_CASES = {
@@ -138,6 +153,16 @@ RSpec.describe "SAX/DOM entity parity" do
138
153
  end
139
154
  end
140
155
 
156
+ if SaxEntityParityFixtures::SAX_NAMESPACE_ADAPTERS.include?(adapter)
157
+ SaxEntityParityFixtures::NAMESPACE_CASES.each do |label, (xml, expected)|
158
+ it "decodes namespace URI once via SAX: #{label}" do
159
+ handler = SaxEntityParityFixtures::CaptureHandler.new
160
+ ctx.sax_parse(xml, handler)
161
+ expect(handler.first_namespaces["f"]).to eq(expected)
162
+ end
163
+ end
164
+ end
165
+
141
166
  SaxEntityParityFixtures::TEXT_CASES.each do |label, (xml, expected, expected_serialized)|
142
167
  it "decodes text via SAX: #{label}" do
143
168
  handler = SaxEntityParityFixtures::CaptureHandler.new
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.170
4
+ version: 0.5.171
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-29 00:00:00.000000000 Z
11
+ date: 2026-10-30 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
@@ -512,6 +512,7 @@ files:
512
512
  - spec/moxml/adapter/rexml_spec.rb
513
513
  - spec/moxml/adapter/shared_examples/.gitkeep
514
514
  - spec/moxml/adapter/shared_examples/adapter_contract.rb
515
+ - spec/moxml/adapter_deprecation_spec.rb
515
516
  - spec/moxml/adapter_spec.rb
516
517
  - spec/moxml/allocation_benchmark_spec.rb
517
518
  - spec/moxml/allocation_guard_spec.rb