moxml 0.5.22 → 0.5.23

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: 9d5a6ef9ddc86140c162e868d05587e8e70552bcfb17f7964024063856b60b5f
4
- data.tar.gz: 664be87c9b0e0b00757a696ae49d7c97a67994bc225fbde8e224c20a642133ba
3
+ metadata.gz: 4c88f88d6ab8eafea4b11c8007e7b667e1413bfc60715142ef280af00c1909de
4
+ data.tar.gz: 8302e755e10fa582f9349c443fc91ed63ddd684ce24c45f0baedd88f0541c16f
5
5
  SHA512:
6
- metadata.gz: 8f9927536fa366b63c1673a7f7721ec772e0964f653d2c4c71861e58ddfc3d3c46855fe1fc288471045357416e40f908de9c60f1b2adf12849e44438f60df22b
7
- data.tar.gz: 7cf7c5a05eed212702b2a58b18e4deecae8e474e2f9ff74483188fc353fec8320465695e843f8aec0663f9e67130b6f5fa66b01992b6ac1e903dc88e35f3000b
6
+ metadata.gz: b7067b38f81a13bd29e1c542f66bd99636f594f91a754997744419549e259c0267a69daea3ab147410e425f243e9ddc807441860df8d91f00644b93f5608fd2e
7
+ data.tar.gz: 662df0f63b4600b923eeedf4747b04bbd71a3a0cd732d25f2ca4fccaf6b563b710f09d8fd470497434962aa024c1e9354f718ebd8b793649a4aa06033149852b
@@ -71,9 +71,8 @@ implementation over one `leptris_node_traverse` call.
71
71
 
72
72
  * Tab indentation honors the indent-unit string on leptris >= 1.9.45
73
73
  (`indent_text:`), byte-identical to Nokogiri including
74
- text-bearing children since the engine fix (leptris/leptris#658,
75
- 1.9.46); comment children still diverge on the element unit path
76
- (leptris-ruby#115).
74
+ text-bearing children and comment children alike
75
+ (leptris/leptris#658 at 1.9.46; leptris-ruby#115 at 1.9.50).
77
76
  * A PI preceding the DOCTYPE reorders on serialization — libleptris
78
77
  normalizes DOCTYPE-first and the document node does not record its
79
78
  relative position.
@@ -147,10 +147,11 @@ module Moxml
147
147
  end
148
148
 
149
149
  # The engine's subset serializer mangles every declaration
150
- # after the first (loses its "<") — moxml's own formatter is
151
- # correct, so multi-declaration subsets compose.
150
+ # after the first (leptris/leptris#687) — moxml's own
151
+ # formatter is correct, so those compose until the probe
152
+ # says the engine is fixed.
152
153
  dt = doc.doctype
153
- if dt&.class&.method_defined?(:internal_subset) &&
154
+ if !ENGINE_MULTI_DECL_SUBSET_OK && dt&.class&.method_defined?(:internal_subset) &&
154
155
  (subset = dt.internal_subset) && subset.scan("<!").size > 1
155
156
  return nil
156
157
  end
@@ -75,27 +75,34 @@ module Moxml
75
75
  # Element output always ends with the close tag — but the
76
76
  # engine's serializer appends a stray trailing newline when
77
77
  # the element's last text child is non-ASCII (fixed engine
78
- # side in 1.9.42; kept for older floor bindings). Note: the
79
- # binding's indent_text kwarg is the display-form boolean,
80
- # not Nokogiri's indent-unit string — do not forward it.
78
+ # side in 1.9.42; kept for older floor bindings).
81
79
  xml.sub(/\n+\z/, "")
82
80
  end
83
81
 
82
+ # A bare ampersand — not starting a named or numeric entity
83
+ # reference. Some engine builds (observed: the Linux 1.9.50
84
+ # platform gem) emit text-content ampersands unescaped; the
85
+ # detection below is a no-op on correct builds.
86
+ RAW_AMP_RE = /&(?!#{Entity::NAME_PATTERN};|#\d+;|#x[0-9A-Fa-f]+;)/
87
+
84
88
  def normalize_serialization(xml, options)
85
- needs_apos = xml.include?("&apos;")
89
+ # The libxml2-layout serializer (>= 1.9.42) keeps attribute
90
+ # apostrophes literal; older engines escaped them.
91
+ needs_apos = !LIBXML2_LAYOUT_PARITY && xml.include?("&apos;")
86
92
  needs_expand = options[:expand_empty] && xml.include?("/>")
87
- return xml unless needs_apos || needs_expand
93
+ needs_amp = xml.include?("&") && xml.match?(RAW_AMP_RE)
94
+ return xml unless needs_apos || needs_expand || needs_amp
88
95
 
89
96
  out = +""
90
97
  pos = 0
91
98
  while pos < xml.length
92
99
  opener_at, terminator = next_literal_region(xml, pos)
93
100
  if opener_at.nil?
94
- out << normalize_markup(xml[pos..], needs_apos, needs_expand)
101
+ out << normalize_markup(xml[pos..], needs_apos, needs_expand, needs_amp: needs_amp)
95
102
  break
96
103
  end
97
104
 
98
- out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand)
105
+ out << normalize_markup(xml[pos...opener_at], needs_apos, needs_expand, needs_amp: needs_amp)
99
106
  search_from = opener_at + opener_at_offset(terminator)
100
107
  close = xml.index(terminator, search_from)
101
108
  close_end = close.nil? ? xml.length : close + terminator.length
@@ -127,13 +134,16 @@ module Moxml
127
134
  terminator == "-->" ? 4 : 0
128
135
  end
129
136
 
130
- def normalize_markup(markup, needs_apos, needs_expand)
137
+ def normalize_markup(markup, needs_apos, needs_expand, needs_amp: false)
131
138
  markup = markup.gsub("&apos;", "'") if needs_apos
132
139
  if needs_expand
133
140
  markup = markup.gsub(EMPTY_ELEMENT_RE) do
134
141
  "<#{Regexp.last_match(1)}#{Regexp.last_match(2)}></#{Regexp.last_match(1)}>"
135
142
  end
136
143
  end
144
+ # Segment-aware: this never sees CDATA/comment/PI content,
145
+ # where a bare & is literal data.
146
+ markup = markup.gsub(RAW_AMP_RE, "&amp;") if needs_amp
137
147
  markup
138
148
  end
139
149
  end
@@ -49,6 +49,39 @@ module Moxml
49
49
  INDENT_UNIT_SUPPORTED =
50
50
  Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.45")
51
51
 
52
+ # leptris-ruby#115 (fixed 1.9.50): the element unit path keeps
53
+ # child comments.
54
+ ELEMENT_UNIT_COMMENTS =
55
+ Gem::Version.new(::Leptris::VERSION) >= Gem::Version.new("1.9.50")
56
+
57
+ # leptris/leptris#677: the engine's DROP_WS_TEXT trims boundary
58
+ # whitespace of non-blank text nodes. Probed at load rather
59
+ # than version-gated so the C fix is adopted the moment a fixed
60
+ # binding installs — no moxml release needed. While the probe
61
+ # fails, noblanks drops blanks moxml-side after parse.
62
+ ENGINE_NOBLANKS_SAFE = begin
63
+ doc = ::Leptris::XML::Document.parse(
64
+ "<r> x</r>", options: ::Leptris::XML::ParseOptions.noblanks
65
+ )
66
+ text = doc.root.children.first
67
+ text.is_a?(::Leptris::XML::Text) && text.content == " x"
68
+ rescue StandardError
69
+ false
70
+ end
71
+
72
+ # leptris/leptris#687: the engine's subset serializer mangles
73
+ # every internal-subset declaration after the first. Probed for
74
+ # the same reason — once fixed, the whole-document fast path
75
+ # serves multi-declaration subsets too.
76
+ ENGINE_MULTI_DECL_SUBSET_OK = begin
77
+ doc = ::Leptris::XML::Document.parse(
78
+ %(<?xml version="1.0"?><!DOCTYPE r [<!ELEMENT r (#PCDATA)><!ATTLIST e a CDATA "d">]><r/>),
79
+ )
80
+ doc.to_xml.include?("<!ATTLIST")
81
+ rescue StandardError
82
+ false
83
+ end
84
+
52
85
  NO_PARSE_ERRORS = [].freeze
53
86
  private_constant :NO_PARSE_ERRORS
54
87
 
@@ -106,10 +139,10 @@ module Moxml
106
139
  ctx = _context || Context.new(:leptris)
107
140
  doc = Document.new(native_doc, ctx)
108
141
 
109
- if options[:noblanks]
142
+ if options[:noblanks] && !ENGINE_NOBLANKS_SAFE
110
143
  # The engine's DROP_WS_TEXT trims boundary whitespace of
111
144
  # NON-blank text nodes at parse (leptris/leptris#677), so
112
- # the flag is never forwarded — blanks drop here instead.
145
+ # the flag is not forwarded — blanks drop here instead.
113
146
  if options[:readonly] == true
114
147
  raise ArgumentError,
115
148
  "noblanks: true requires a mutable document (readonly: true freezes the tree at parse)"
@@ -128,14 +161,12 @@ module Moxml
128
161
  # nil when no parse flag is requested — the binding treats a
129
162
  # nil options hash as plain defaults (matching libxml2/
130
163
  # Nokogiri semantics: blanks kept, no ATTLIST defaults).
131
- # noblanks is deliberately absent: the engine's
132
- # LEPTRIS_PARSE_DROP_WS_TEXT trims boundary whitespace of
133
- # non-blank text nodes too (leptris/leptris#677), so moxml
134
- # implements it itself after parse — see
135
- # strip_blank_text_nodes!.
164
+ # noblanks forwards only once the engine flag is libxml2-safe
165
+ # (see ENGINE_NOBLANKS_SAFE); otherwise it is moxml-side.
136
166
  def parse_flags(options)
137
167
  flags = 0
138
168
  flags |= ::Leptris::XML::ParseOptions::DTDATTR if options[:dtdattr] == true
169
+ flags |= ::Leptris::XML::ParseOptions::NOBLANKS if options[:noblanks] == true && ENGINE_NOBLANKS_SAFE
139
170
  flags.zero? ? nil : ::Leptris::XML::ParseOptions.new(flags)
140
171
  end
141
172
 
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.22"
4
+ VERSION = "0.5.23"
5
5
  end
@@ -175,6 +175,14 @@ RSpec.describe Moxml::Adapter::Leptris do
175
175
  end
176
176
  end
177
177
 
178
+ it "keeps comments in element serialization with tab units (leptris-ruby#115)" do
179
+ skip "requires the element unit fix (leptris 1.9.50+)" unless described_class::ELEMENT_UNIT_COMMENTS
180
+
181
+ doc = ctx.parse(%(<r><a/><!-- c --></r>))
182
+ expect(doc.root.to_xml(indent: 1, indent_text: "\t"))
183
+ .to eq("<r>\n\t<a></a>\n\t<!-- c -->\n</r>")
184
+ end
185
+
178
186
  it "round-trips document-level PI mutations through serialization" do
179
187
  # leptris/leptris#612: parse-created document PIs carry doc
180
188
  # linkage — the setters work and the tree round-trips.
@@ -235,8 +243,12 @@ RSpec.describe Moxml::Adapter::Leptris do
235
243
  expect(doc.root.children.to_a.select(&:text?)).to be_empty
236
244
  end
237
245
 
238
- it "refuses noblanks on readonly parses" do
239
- # The strip mutates the tree; readonly freezes it at parse.
246
+ it "refuses noblanks on readonly parses while the strip path is active" do
247
+ # The strip mutates the tree; readonly freezes it at parse. On
248
+ # bindings whose engine flag is libxml2-safe (probe), the flag
249
+ # forwards at parse and no mutability is needed.
250
+ skip "engine noblanks flag is libxml2-safe — no strip path" if described_class::ENGINE_NOBLANKS_SAFE
251
+
240
252
  expect { ctx.parse("<a/>", noblanks: true, readonly: true) }
241
253
  .to raise_error(ArgumentError, /noblanks.*mutable/)
242
254
  end
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.22
4
+ version: 0.5.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.