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 +4 -4
- data/lib/moxml/adapter/leptris/document_parts.rb +27 -5
- data/lib/moxml/adapter/leptris/materialize.rb +13 -0
- data/lib/moxml/adapter/nokogiri.rb +13 -0
- data/lib/moxml/version.rb +1 -1
- data/spec/consistency/html_doctype_spec.rb +44 -0
- data/spec/moxml/materialize_namespace_uri_spec.rb +35 -0
- data/spec/moxml/to_html_spec.rb +13 -3
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2f099a606cb34b83d050cd7127593bc1403b3c2ae0d75f1d2d5afaad0107177
|
|
4
|
+
data.tar.gz: 1d1d777052f784a68363fc7efee322aeb53cfea53781927df1c0e424a165327e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
data/spec/moxml/to_html_spec.rb
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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.
|
|
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
|