moxml 0.5.128 → 0.5.129

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: 5b80e9601eb4a227d342aa484359a4724f3273a561c9f74cd8d375ffd5321186
4
- data.tar.gz: 987e9faa827418fe3471456b1b939ced916467c895454670c3ba1273ee5fd616
3
+ metadata.gz: 9f040a5d5fc3e041e15a54cc42aeb10ca5d3cc882ecc22ca97faa7d45cbbb2d5
4
+ data.tar.gz: de59fd55dd4eff18451ddec5a20f246325be236313a55d0aeb4cab5af56ce4f9
5
5
  SHA512:
6
- metadata.gz: 0f059f790d6a76ba2213d83ced97289d837ab0ad1b4448dd1f026bb991a5e90b8131fabc31d6f2f0eeb948564693231ea89c3899a542618d65de153db687bde7
7
- data.tar.gz: edae98439cf51d03ea590709a22fa05848f9e7b62fa7f274bf6e1ffdd34f0115eca0622b165e3f1a884d8f9a73ba4047338061d6281c4c88161ce13bbd44a7ae
6
+ metadata.gz: 668587c18c1bf04415c99e715460b1dece9f5619206d6a7fb1ffa8260a32be81f6faa20853eeca322b3b016523a589550711647362f03ff79f64ccc507ef8e03
7
+ data.tar.gz: 7ff1f188edb97ee6f36c0294745232f1e1a4078bd6e94d567d56e649c01f5939617b345ebed2fe5b21bf325b6be1863f68b5af1acfb7171bd63b0a4bef58bd2a
@@ -129,15 +129,37 @@ module Moxml
129
129
  end
130
130
 
131
131
  doctype = attachments.get(doc, :doctype)
132
- parts << doctype.to_xml << "\n" if doctype.is_a?(CustomizedLeptris::Doctype)
133
-
134
- native = native_doctype_xml(doc)
135
- parts << native << "\n" if native
132
+ if doctype.is_a?(CustomizedLeptris::Doctype)
133
+ parts << doctype.to_xml << "\n"
134
+ else
135
+ # #364: the same DOCTYPE renders ONCE — the attachment
136
+ # is the parse-time capture; native_doctype_xml re-reads
137
+ # the engine's own doctype, and both firing doubled the
138
+ # line (moxml#364's \n\n\n).
139
+ native = native_doctype_xml(doc)
140
+ parts << native << "\n" if native
141
+ end
136
142
 
137
143
  # The libxml2-model document node: prolog PIs/comments, the
138
144
  # root, epilog PIs/comments — in document order, so epilog
139
145
  # parts serialize after the root (issue #130).
140
- doc.children.each { |child| parts << raw_serialize(child, options) << "\n" }
146
+ # #364: document-level WHITESPACE-only text is layout
147
+ # residue (the input's newline after the doctype), not
148
+ # content — the newline-terminated part shape already
149
+ # carries the separation; rendering the text node stacked
150
+ # a second (or third) newline.
151
+ doc.children.each do |child|
152
+ # #364: document-level WHITESPACE-only text is layout
153
+ # residue (the input's newline after the doctype) — the
154
+ # newline-terminated part shape already carries the
155
+ # separation. Binding nodes walk here (native children).
156
+ if child.is_a?(::Leptris::XML::Text) &&
157
+ child.content.match?(/\A\s*\z/)
158
+ next
159
+ end
160
+
161
+ parts << raw_serialize(child, options) << "\n"
162
+ end
141
163
 
142
164
  texts = attachments.get(doc, :document_text)
143
165
  texts&.each { |text| parts << XmlEmitter.escape_text(text.content.to_s) }
@@ -160,7 +160,20 @@ module Moxml
160
160
  # malformed input never raises.
161
161
  def parse_html(html, _options = {}, _context = nil)
162
162
  html_string = html.is_a?(IO) || html.is_a?(StringIO) ? html.read : html.to_s
163
+ # #364: preserve-input-only — libxml2's HTML parser
164
+ # synthesizes an HTML 4.0 doctype when the input has none
165
+ # (leptris doesn't); dropping the synthesized node keeps
166
+ # the shared contract doctype-faithful. A doctype before
167
+ # the root (after optional BOM/xml-decl/whitespace) means
168
+ # the source had one.
169
+ had_doctype = html_string.match?(
170
+ /\A(\xEF\xBB\xBF|\s|<\?xml[^>]*\?>)*<!DOCTYPE/i,
171
+ )
163
172
  native_doc = ::Nokogiri::HTML(html_string, &:huge)
173
+ if !had_doctype &&
174
+ (dtd = native_doc.children.find { |n| n.is_a?(::Nokogiri::XML::DTD) })
175
+ dtd.unlink
176
+ end
164
177
  Wrappers::Document.new(native_doc, _context || Context.new(:nokogiri))
165
178
  end
166
179
 
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.128"
4
+ VERSION = "0.5.129"
5
5
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # #364: DOCTYPE semantics converge across HTML-capable adapters —
6
+ # preserve-input-only (never synthesize: libxml2's HTML parser
7
+ # invents an HTML 4.0 doctype, which the nokogiri adapter drops),
8
+ # and the post-DOCTYPE separator is one newline (the leptris
9
+ # document-part walk used to stack the input's whitespace residue
10
+ # on top of the part newlines).
11
+ RSpec.describe "HTML DOCTYPE convergence (#364)" do
12
+ let(:body) do
13
+ "<html><head><title>t</title></head><body><p>x</p></body></html>"
14
+ end
15
+
16
+ %i[leptris nokogiri].each do |adapter_name|
17
+ describe "on #{adapter_name}" do
18
+ let(:ctx) { Moxml.new(adapter_name) }
19
+
20
+ it "never synthesizes a DOCTYPE the input lacked" do
21
+ out = ctx.parse_html(body).to_s
22
+ expect(out).not_to include("<!DOCTYPE")
23
+ expect(out).to include("<html")
24
+ end
25
+
26
+ it "preserves the input DOCTYPE with a single separator" do
27
+ out = ctx.parse_html("<!DOCTYPE html>\n#{body}").to_s
28
+ expect(out).to start_with("<!DOCTYPE html>\n")
29
+ expect(out.scan("<!DOCTYPE").size).to eq(1)
30
+ expect(out).not_to include("\n\n\n")
31
+ end
32
+
33
+ it "preserves public/system identifiers verbatim" do
34
+ src =
35
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' \
36
+ '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' \
37
+ "\n#{body}"
38
+ out = ctx.parse_html(src).to_s
39
+ expect(out).to include("XHTML 1.0 Strict")
40
+ expect(out.scan("<!DOCTYPE").size).to eq(1)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -13,20 +13,30 @@ RSpec.describe "Moxml::Document#to_html" do
13
13
  end
14
14
 
15
15
  describe "nokogiri" do
16
+ # The oracle mirrors the adapter's #364 preserve-input-only
17
+ # parse: the synthesized HTML 4.0 doctype is dropped the same
18
+ # way, so pass-through equality stays meaningful.
19
+ let(:oracle) do
20
+ d = Nokogiri::HTML(src)
21
+ if (dtd = d.children.find { |n| n.is_a?(Nokogiri::XML::DTD) })
22
+ dtd.unlink
23
+ end
24
+ d
25
+ end
16
26
  let(:doc) { Moxml.new(:nokogiri).parse_html(src) }
17
27
 
18
28
  it "serializes the engine's HTML bytes by default" do
19
- expect(doc.to_html).to eq(Nokogiri::HTML(src).to_html)
29
+ expect(doc.to_html).to eq(oracle.to_html)
20
30
  end
21
31
 
22
32
  it "threads indent through to the engine" do
23
33
  expect(doc.to_html(indent: 2))
24
- .to eq(Nokogiri::HTML(src).to_html(indent: 2))
34
+ .to eq(oracle.to_html(indent: 2))
25
35
  end
26
36
 
27
37
  it "threads the indent unit through to the engine" do
28
38
  expect(doc.to_html(indent: 1, indent_text: "\t"))
29
- .to eq(Nokogiri::HTML(src).to_html(indent: 1, indent_text: "\t"))
39
+ .to eq(oracle.to_html(indent: 1, indent_text: "\t"))
30
40
  end
31
41
  end
32
42
 
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.128
4
+ version: 0.5.129
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -312,6 +312,7 @@ files:
312
312
  - spec/consistency/README.md
313
313
  - spec/consistency/adapter_parity_spec.rb
314
314
  - spec/consistency/eol_normalization_spec.rb
315
+ - spec/consistency/html_doctype_spec.rb
315
316
  - spec/consistency/round_trip_spec.rb
316
317
  - spec/consistency/xslt_spec.rb
317
318
  - spec/examples/README.md