moxml 0.5.128 → 0.5.130

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: c2f099a606cb34b83d050cd7127593bc1403b3c2ae0d75f1d2d5afaad0107177
4
+ data.tar.gz: 1d1d777052f784a68363fc7efee322aeb53cfea53781927df1c0e424a165327e
5
5
  SHA512:
6
- metadata.gz: 0f059f790d6a76ba2213d83ced97289d837ab0ad1b4448dd1f026bb991a5e90b8131fabc31d6f2f0eeb948564693231ea89c3899a542618d65de153db687bde7
7
- data.tar.gz: edae98439cf51d03ea590709a22fa05848f9e7b62fa7f274bf6e1ffdd34f0115eca0622b165e3f1a884d8f9a73ba4047338061d6281c4c88161ce13bbd44a7ae
6
+ metadata.gz: 28d300923fb7a5225aae5241ccbf3af90269ce73258ce1351260fb437df8a70f390a889fcccc98556202fcbcda15a0dbc2b1a9a8ab753e54f81d79a4132ab431
7
+ data.tar.gz: f8085292e7b0bad93fc80075f1b1e6422021afb972dd11fd9088798b254cff3268068a7ae8786a29511440e7b716c6f48550671bebd772e34c6b509c96357f84
@@ -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) }
@@ -23,6 +23,18 @@ module Moxml
23
23
  CHILDREN_CAPACITY = 64
24
24
  private_constant :CHILDREN_CAPACITY
25
25
 
26
+ # #367: HTML-parsed trees carry no namespace declarations,
27
+ # so the engine's attribute-URI face answers nil for the
28
+ # RESERVED prefixes too (xml:lang loses its identity). The
29
+ # wrapper path resolves them from the builtin table
30
+ # (AttributeResolver) — the bulk stride mirrors that, so
31
+ # records and wrapper API agree on every tree flavor.
32
+ RESERVED_ATTR_PREFIX_URIS = {
33
+ "xml" => "http://www.w3.org/XML/1998/namespace",
34
+ "xmlns" => "http://www.w3.org/2000/xmlns/",
35
+ }.freeze
36
+ private_constant :RESERVED_ATTR_PREFIX_URIS
37
+
26
38
  # Bulk materialization (issues #132/#143): a Ruby-side
27
39
  # post-order recursion over RAW C pointers — no binding
28
40
  # wrapper per node, no per-node FFI callback, no depth memo
@@ -132,6 +144,7 @@ module Moxml
132
144
  # path's resolver semantics (moxml's canonical shape).
133
145
  attr_name = attr_name[(colon + 1)..]
134
146
  attr_uri = f.leptris_attribute_namespace_uri(attr)
147
+ attr_uri ||= RESERVED_ATTR_PREFIX_URIS[attr_prefix]
135
148
  else
136
149
  attr_prefix = nil
137
150
  attr_uri = nil
@@ -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.130"
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
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # #367: the bulk records stride carries the bound namespace URI
6
+ # for RESERVED-prefixed attributes on HTML-fragment-parsed trees —
7
+ # HTML trees carry no declarations, so the engine's attribute-URI
8
+ # face answers nil for xml:lang there; the bulk path mirrors the
9
+ # wrapper resolver's builtin table so records and wrapper agree.
10
+ RSpec.describe "materialize_fields reserved-prefix URIs (#367)" do
11
+ let(:ctx) { Moxml.new(:leptris) }
12
+ let(:frag) { ctx.parse_html_fragment('<span lang="en" xml:lang="en">text</span>') }
13
+ let(:span) { frag.children.first }
14
+
15
+ def records_attrs
16
+ out = nil
17
+ span.materialize_fields do |kind, _q, _p, _u, _ns, attrs, _t, _d| # rubocop:disable Metrics/ParameterLists,Lint/UnusedBlockArgument
18
+ out = attrs.dup if kind == :element
19
+ end
20
+ out
21
+ end
22
+
23
+ it "matches the wrapper API for prefixed attributes on fragment trees" do
24
+ records = records_attrs
25
+ expect(records).to eq(
26
+ ["lang", "en", nil, nil,
27
+ "lang", "en", "http://www.w3.org/XML/1998/namespace", "xml"],
28
+ )
29
+ end
30
+
31
+ it "agrees with the wrapper attribute API stride-for-stride" do
32
+ wrapper = span.attributes.map { |a| [a.name, a.value, a.namespace&.uri, a.namespace&.prefix] }
33
+ expect(records_attrs.each_slice(4).to_a).to eq(wrapper)
34
+ end
35
+ 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.130
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
@@ -507,6 +508,7 @@ files:
507
508
  - spec/moxml/error_spec.rb
508
509
  - spec/moxml/html_fragment_spec.rb
509
510
  - spec/moxml/lazy_parse_spec.rb
511
+ - spec/moxml/materialize_namespace_uri_spec.rb
510
512
  - spec/moxml/materializer_spec.rb
511
513
  - spec/moxml/moxml_spec.rb
512
514
  - spec/moxml/namespace_spec.rb