moxml 0.5.21 → 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 +4 -4
- data/docs/_pages/adapters/leptris.adoc +2 -3
- data/lib/moxml/adapter/leptris/document_parts.rb +4 -3
- data/lib/moxml/adapter/leptris/serialize.rb +18 -8
- data/lib/moxml/adapter/leptris.rb +38 -7
- data/lib/moxml/version.rb +1 -1
- data/spec/moxml/adapter/leptris_spec.rb +14 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c88f88d6ab8eafea4b11c8007e7b667e1413bfc60715142ef280af00c1909de
|
|
4
|
+
data.tar.gz: 8302e755e10fa582f9349c443fc91ed63ddd684ce24c45f0baedd88f0541c16f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
75
|
-
1.9.46
|
|
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 (
|
|
151
|
-
# correct, so
|
|
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).
|
|
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
|
-
|
|
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?("'")
|
|
86
92
|
needs_expand = options[:expand_empty] && xml.include?("/>")
|
|
87
|
-
|
|
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("'", "'") 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, "&") 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
|
|
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
|
|
132
|
-
#
|
|
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
|
@@ -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
|