moxml 0.5.26 → 0.5.27

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: b7d4ecc2bb794f60a6b240faf3828c7847da9eebf8d45e44b35dd863f8ef7b09
4
- data.tar.gz: 04d2ecbc979863a35825a8ba95a7897d4f7d395629c5ca959dfa8fd96c71a662
3
+ metadata.gz: b9d156e4f8dfd71fc334b5626690f325a78abf95496bf19b6141bf649beeee58
4
+ data.tar.gz: 5f3c24b3211e8381d14a34c7c23781a808e2f55b1d73d06af3be0fe7d3640a75
5
5
  SHA512:
6
- metadata.gz: a63b4c25f0838edbb6ba6cb787b68000e614afbfbb700a3167a5222ab7219fef4f7add25a009991de44b78eeb2b117c96f0743655e0fa058e4ac3d697877093b
7
- data.tar.gz: c8700770e6873e0a9af730cc94b149c019d63df0aadd585f3f382e1c4fe4246d9d4bb243e9f8347ed4bdb83b67d84aa497de69152cf3ea2921c6be55b9037d05
6
+ metadata.gz: 1c5cd5d01ece3fcced5049148d2df776a1dcb9b1e95b0fe9a145caef05c0e2d5080b32214c7eb9f8f7bb277412827b413b3c59e38a49162241208944791a6ed6
7
+ data.tar.gz: 33faf5c9269c3c74a6def61ef3933a9bdc2caedf484e0144ca1c8e6465aeb1640bb47c3f44ce9c777ed5bbe8e9682efd840cb87d4eceedac764f4c496519fff3
@@ -85,26 +85,37 @@ module Moxml
85
85
  # detection below is a no-op on correct builds.
86
86
  RAW_AMP_RE = /&(?!#{Entity::NAME_PATTERN};|#\d+;|#x[0-9A-Fa-f]+;)/
87
87
 
88
+ # A raw "<" in text position — the engine intermittently
89
+ # loses the escape when parsing under allocation pressure
90
+ # (leptris-ruby#131), so a decoded &#x3c; serializes bare and
91
+ # reparsing truncates at it. Valid markup NEVER puts "<"
92
+ # before a non-name, non-delimiter character, so this is
93
+ # deterministic in markup segments (CDATA is literal and
94
+ # excluded by the segment walker).
95
+ RAW_LT_RE = /<(?![A-Za-z_:\/?!])/
96
+
88
97
  def normalize_serialization(xml, options)
89
98
  # The libxml2-layout serializer (>= 1.9.42) keeps attribute
90
99
  # apostrophes literal; older engines escaped them.
91
100
  needs_apos = !LIBXML2_LAYOUT_PARITY && xml.include?("&apos;")
92
101
  needs_expand = options[:expand_empty] && xml.include?("/>")
93
- # One scan: the bare-ampersand regex fails fast on
94
- # ampersand-free output — no include? pre-filter needed.
102
+ # One scan each: both regexes fail fast on clean output.
95
103
  needs_amp = xml.match?(RAW_AMP_RE)
96
- return xml unless needs_apos || needs_expand || needs_amp
104
+ needs_lt = xml.match?(RAW_LT_RE)
105
+ return xml unless needs_apos || needs_expand || needs_amp || needs_lt
97
106
 
98
107
  out = +""
99
108
  pos = 0
100
109
  while pos < xml.length
101
110
  opener_at, terminator = next_literal_region(xml, pos)
102
111
  if opener_at.nil?
103
- out << normalize_markup(xml[pos..], needs_apos, needs_expand, needs_amp: needs_amp)
112
+ out << normalize_markup(xml[pos..], needs_apos, needs_expand,
113
+ needs_amp: needs_amp, needs_lt: needs_lt)
104
114
  break
105
115
  end
106
116
 
107
- out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand, needs_amp: needs_amp)
117
+ out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand,
118
+ needs_amp: needs_amp, needs_lt: needs_lt)
108
119
  search_from = opener_at + opener_at_offset(terminator)
109
120
  close = xml.index(terminator, search_from)
110
121
  close_end = close.nil? ? xml.length : close + terminator.length
@@ -136,7 +147,7 @@ module Moxml
136
147
  terminator == "-->" ? 4 : 0
137
148
  end
138
149
 
139
- def normalize_markup(markup, needs_apos, needs_expand, needs_amp: false)
150
+ def normalize_markup(markup, needs_apos, needs_expand, needs_amp: false, needs_lt: false)
140
151
  markup = markup.gsub("&apos;", "'") if needs_apos
141
152
  if needs_expand
142
153
  markup = markup.gsub(EMPTY_ELEMENT_RE) do
@@ -144,8 +155,9 @@ module Moxml
144
155
  end
145
156
  end
146
157
  # Segment-aware: this never sees CDATA/comment/PI content,
147
- # where a bare & is literal data.
158
+ # where bare & and < are literal data.
148
159
  markup = markup.gsub(RAW_AMP_RE, "&amp;") if needs_amp
160
+ markup = markup.gsub(RAW_LT_RE, "&lt;") if needs_lt
149
161
  markup
150
162
  end
151
163
  end
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.26"
4
+ VERSION = "0.5.27"
5
5
  end
@@ -243,6 +243,36 @@ RSpec.describe Moxml::Adapter::Leptris do
243
243
  expect(doc.root.children.to_a.select(&:text?)).to be_empty
244
244
  end
245
245
 
246
+ it "repairs a raw < in text position (issue #167 / leptris-ruby#131)" do
247
+ # The engine intermittently drops the escape when parsing under
248
+ # allocation pressure: a decoded &#x3c; serializes bare and the
249
+ # output fails to reparse. Valid markup never puts < before a
250
+ # non-name character, so the damage is deterministically
251
+ # repairable in markup segments.
252
+ repaired = described_class.normalize_serialization(
253
+ %(<?xml version="1.0"?><r><pre>A <\n B</pre></r>\n), {}
254
+ )
255
+ expect(repaired).to include("A &lt;")
256
+ expect(repaired).not_to match("A <")
257
+ end
258
+
259
+ it "keeps a literal < inside CDATA untouched by the repair" do
260
+ doc = ctx.parse(%(<r><![CDATA[A < B]]></r>))
261
+ expect(doc.to_xml).to include("<![CDATA[A < B]]>")
262
+ end
263
+
264
+ it "survives parse-under-allocation-pressure without emitting raw < (issue #167)" do
265
+ xml = "<?xml version=\"1.0\"?><doc>#{%(<s><pre alt="A B">A &#x3c;\n B</pre></s>) * 10}</doc>"
266
+ 30.times do
267
+ 100.times { |j| "pressure#{j}" * 20 }
268
+ out = ctx.parse(xml, noblanks: true).to_xml(
269
+ declaration: true, encoding: "UTF-8", indent: 2, expand_empty: false,
270
+ )
271
+ expect(out).not_to match("A <"), "raw unescaped < leaked into text"
272
+ expect(Nokogiri::XML(out, &:strict)).to be_a(Nokogiri::XML::Document)
273
+ end
274
+ end
275
+
246
276
  it "refuses noblanks on readonly parses while the strip path is active" do
247
277
  # The strip mutates the tree; readonly freezes it at parse. On
248
278
  # bindings whose engine flag is libxml2-safe (probe), the flag
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.26
4
+ version: 0.5.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.