canon 0.3.97 → 0.3.99
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/canon/comparison/html_comparator.rb +16 -5
- data/lib/canon/comparison/html_parser.rb +8 -24
- data/lib/canon/html/data_model.rb +42 -49
- data/lib/canon/tree_diff/adapters/html_adapter.rb +20 -14
- data/lib/canon/version.rb +1 -1
- 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: 4e993fe0bac445ec82e1a02983bfa2f297c1b7d9d112bcb7c724f3f010a1b353
|
|
4
|
+
data.tar.gz: 869324e74378af4f4cb9493fbfdc04ac1ba8e76c0378159547bc7e23ebf66084
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70c6dcb6bb9da7ef764f9aed9e1e8d02fb95193c446102df62496a10dbe33010d29b5d0e90293d929cf3adc267b5128ad1dd7e9666dff38b892d221bc681f20c
|
|
7
|
+
data.tar.gz: dc45835bdab2c58479e8413eb87aee5c8615237ebf6bd2c435ae725b58b7df247c3740c25a3ab192fb0aaf86c6776d3634c5a66170ea6834a3f3bedf5582fd27
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "set"
|
|
4
|
-
Canon::NokogiriLoader.require!("HTML comparison")
|
|
5
4
|
|
|
6
5
|
module Canon
|
|
7
6
|
module Comparison
|
|
@@ -296,8 +295,8 @@ module Canon
|
|
|
296
295
|
Canon::Xml::C14n.canonicalize(html_string,
|
|
297
296
|
with_comments: false)
|
|
298
297
|
when :format
|
|
299
|
-
#
|
|
300
|
-
Canon.
|
|
298
|
+
# Engine single-line structural normalizer
|
|
299
|
+
Canon::Html::DataModel.engine_html_format(html_string)
|
|
301
300
|
else
|
|
302
301
|
# :none or unrecognized
|
|
303
302
|
html_string
|
|
@@ -376,13 +375,25 @@ module Canon
|
|
|
376
375
|
Canon::Xml::C14n.canonicalize(node,
|
|
377
376
|
with_comments: false)
|
|
378
377
|
when :format
|
|
379
|
-
#
|
|
380
|
-
Canon.
|
|
378
|
+
# Engine single-line structural normalizer
|
|
379
|
+
Canon::Html::DataModel.engine_html_format(node)
|
|
381
380
|
else
|
|
382
381
|
# :none, :rendered or unrecognized - use as-is
|
|
383
382
|
node
|
|
384
383
|
end
|
|
385
384
|
|
|
385
|
+
# Direct comparator calls parse through the legacy XML-fragment
|
|
386
|
+
# lane: both sides of a comparison must land on the SAME tree
|
|
387
|
+
# shape, and a string paired with a user-supplied Nokogiri node
|
|
388
|
+
# must take the Nokogiri path. The pipeline preparse converts
|
|
389
|
+
# string PAIRS through the engine lane (HtmlParser.parse), so
|
|
390
|
+
# user-facing comparisons run engine-served either way.
|
|
391
|
+
# NOTE: the engine fragment parse currently drops comments
|
|
392
|
+
# that follow ignored <html>/<body> start tags (filed
|
|
393
|
+
# upstream — moxml#389) — the pipeline lane's comment
|
|
394
|
+
# handling for strict comments rides that fix.
|
|
395
|
+
Canon::NokogiriLoader.require!("the HTML comparison legacy lane")
|
|
396
|
+
|
|
386
397
|
frag = Html::NokogiriSupport.xml_fragment(
|
|
387
398
|
decode_html_named_entities(html_string),
|
|
388
399
|
)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Canon::NokogiriLoader.require!("HTML comparison")
|
|
4
|
-
|
|
5
3
|
module Canon
|
|
6
4
|
module Comparison
|
|
7
5
|
# HTML parsing service with version detection and fragment support
|
|
@@ -39,6 +37,11 @@ module Canon
|
|
|
39
37
|
|
|
40
38
|
return engine_fragment(content) if engine_html_fragments?
|
|
41
39
|
|
|
40
|
+
# Nokogiri fallback lane (runtimes without the engine HTML
|
|
41
|
+
# surface): Nokogiri is required lazily, at use — the engine
|
|
42
|
+
# lane above keeps HTML comparison nokogiri-less.
|
|
43
|
+
Canon::NokogiriLoader.require!("the HTML fallback lane")
|
|
44
|
+
|
|
42
45
|
begin
|
|
43
46
|
case format
|
|
44
47
|
when :html5
|
|
@@ -57,29 +60,10 @@ module Canon
|
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
# Parse an HTML string through the engine's WHATWG fragment
|
|
60
|
-
# parsing and build a Canon tree
|
|
61
|
-
#
|
|
62
|
-
# whitespace significance is the comparison profile's decision
|
|
63
|
-
# (#112/#137 ride on stray whitespace surviving the parse),
|
|
64
|
-
# unlike the XML DOM lane's parse-time policy. Top-level
|
|
65
|
-
# element children convert through the materialize-records
|
|
66
|
-
# fast path; the wrapper walk serves the rare non-element
|
|
67
|
-
# top-level nodes (text, comments).
|
|
63
|
+
# parsing and build a Canon tree. Canon::Html::DataModel owns
|
|
64
|
+
# the conversion (the single home for HTML→Canon-tree building).
|
|
68
65
|
def engine_fragment(html)
|
|
69
|
-
|
|
70
|
-
root = Canon::Xml::Nodes::RootNode.new
|
|
71
|
-
frag.children.each do |child|
|
|
72
|
-
node =
|
|
73
|
-
if child.is_a?(Moxml::Element)
|
|
74
|
-
Canon::Xml::DataModel.build_moxml_subtree_from_records(
|
|
75
|
-
child, preserve_whitespace: true
|
|
76
|
-
)
|
|
77
|
-
else
|
|
78
|
-
Canon::Xml::DataModel.walk_moxml(child, preserve_whitespace: true)
|
|
79
|
-
end
|
|
80
|
-
root.add_child(node) if node
|
|
81
|
-
end
|
|
82
|
-
root
|
|
66
|
+
Canon::Html::DataModel.engine_fragment(html)
|
|
83
67
|
end
|
|
84
68
|
|
|
85
69
|
# Engine HTML fragments are served by adapters with an engine
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Canon::NokogiriLoader.require!("HTML support")
|
|
4
|
-
|
|
5
3
|
module Canon
|
|
6
4
|
module Html
|
|
7
5
|
# Builds XPath data model from HTML
|
|
@@ -10,58 +8,53 @@ module Canon
|
|
|
10
8
|
class DataModel < Canon::DataModel
|
|
11
9
|
# Build XPath data model from HTML string
|
|
12
10
|
#
|
|
11
|
+
# The engine lane: moxml Context#parse_html_fragment (WHATWG
|
|
12
|
+
# fragment parsing) → Canon tree. The +version:+ parameter is
|
|
13
|
+
# accepted for API compatibility — the engine serves every input
|
|
14
|
+
# through one WHATWG parse (the #118 html4/html5 unification).
|
|
15
|
+
# Full documents keep their head content at the fragment top
|
|
16
|
+
# level (the in-body rules ignore the html/body start tags but
|
|
17
|
+
# preserve every other node); the former Nokogiri body-extraction
|
|
18
|
+
# dropped head content entirely.
|
|
19
|
+
#
|
|
13
20
|
# @param html_string [String] HTML content to parse
|
|
14
|
-
# @param version [Symbol]
|
|
21
|
+
# @param version [Symbol] accepted for compatibility; unused
|
|
15
22
|
# @return [Canon::Xml::Nodes::RootNode] Root of the data model tree
|
|
23
|
+
# rubocop:disable-next Lint/UnusedMethodArgument -- API compatibility
|
|
16
24
|
def self.from_html(html_string, version: :html4)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Fragments should use fragment parser to avoid adding implicit wrappers
|
|
20
|
-
is_full_document = html_string.match?(%r{<html[\s>]}i)
|
|
21
|
-
|
|
22
|
-
# Parse with Nokogiri using appropriate parser
|
|
23
|
-
doc = if is_full_document
|
|
24
|
-
# CRITICAL FIX: For full HTML documents, parse as document first
|
|
25
|
-
# and extract the body element. This avoids Nokogiri::HTML.fragment()
|
|
26
|
-
# incorrectly moving head elements (like meta) to the body.
|
|
27
|
-
# Parse as full document to get proper structure
|
|
28
|
-
full_doc = if version == :html5
|
|
29
|
-
Nokogiri::HTML5(html_string)
|
|
30
|
-
else
|
|
31
|
-
Nokogiri::HTML4(html_string)
|
|
32
|
-
end
|
|
33
|
-
# Extract body element and create fragment from it
|
|
34
|
-
body = full_doc.at_css("body")
|
|
35
|
-
if body
|
|
36
|
-
# Create a fragment and copy body children to it
|
|
37
|
-
# This preserves the body structure without head elements
|
|
38
|
-
frag = if version == :html5
|
|
39
|
-
Nokogiri::HTML5::DocumentFragment.new(full_doc)
|
|
40
|
-
else
|
|
41
|
-
Nokogiri::HTML4::DocumentFragment.new(full_doc)
|
|
42
|
-
end
|
|
43
|
-
body.children.each do |child|
|
|
44
|
-
frag.add_child(child.dup)
|
|
45
|
-
end
|
|
46
|
-
frag
|
|
47
|
-
elsif version == :html5
|
|
48
|
-
# No body found, fall back to fragment parsing
|
|
49
|
-
Nokogiri::HTML5.fragment(html_string)
|
|
50
|
-
else
|
|
51
|
-
Nokogiri::HTML4.fragment(html_string)
|
|
52
|
-
end
|
|
53
|
-
elsif version == :html5
|
|
54
|
-
# Fragment - use fragment parser to avoid implicit wrappers
|
|
55
|
-
Nokogiri::HTML5.fragment(html_string)
|
|
56
|
-
else
|
|
57
|
-
Nokogiri::HTML4.fragment(html_string)
|
|
58
|
-
end
|
|
25
|
+
engine_fragment(html_string)
|
|
26
|
+
end
|
|
59
27
|
|
|
60
|
-
|
|
61
|
-
|
|
28
|
+
# Parse an HTML string through the engine's WHATWG fragment
|
|
29
|
+
# parsing and build a Canon tree from the fragment children.
|
|
30
|
+
# Whitespace text nodes are preserved unconditionally — HTML
|
|
31
|
+
# whitespace significance is the comparison profile's decision
|
|
32
|
+
# (#112/#137), unlike the XML DOM lane's parse-time policy.
|
|
33
|
+
# Top-level element children convert through the
|
|
34
|
+
# materialize-records fast path; the wrapper walk serves the
|
|
35
|
+
# rare non-element top-level nodes (text, comments).
|
|
36
|
+
def self.engine_fragment(html)
|
|
37
|
+
frag = Canon::XmlParsing.moxml_context.parse_html_fragment(html)
|
|
38
|
+
root = Canon::Xml::Nodes::RootNode.new
|
|
39
|
+
frag.children.each do |child|
|
|
40
|
+
node =
|
|
41
|
+
if child.is_a?(Moxml::Element)
|
|
42
|
+
Canon::Xml::DataModel.build_moxml_subtree_from_records(
|
|
43
|
+
child, preserve_whitespace: true
|
|
44
|
+
)
|
|
45
|
+
else
|
|
46
|
+
Canon::Xml::DataModel.walk_moxml(child, preserve_whitespace: true)
|
|
47
|
+
end
|
|
48
|
+
root.add_child(node) if node
|
|
49
|
+
end
|
|
50
|
+
root
|
|
51
|
+
end
|
|
62
52
|
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
# Deterministic single-line engine formatting for the :format
|
|
54
|
+
# string preprocessor — parse + re-serialize as a pure function
|
|
55
|
+
# of the parsed tree.
|
|
56
|
+
def self.engine_html_format(html)
|
|
57
|
+
Canon::XmlParsing.moxml_context.parse_html(html).to_html
|
|
65
58
|
end
|
|
66
59
|
|
|
67
60
|
# Alias for compatibility
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Canon::NokogiriLoader.require!("HTML tree diffing")
|
|
4
|
-
|
|
5
3
|
module Canon
|
|
6
4
|
module TreeDiff
|
|
7
5
|
module Adapters
|
|
@@ -46,17 +44,22 @@ module Canon
|
|
|
46
44
|
return to_tree_from_canon_comment(node)
|
|
47
45
|
end
|
|
48
46
|
|
|
49
|
-
# Fallback to Nokogiri (legacy support
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
# Fallback to Nokogiri (legacy support for user-supplied
|
|
48
|
+
# nodes) — the Canon tree branches above run nokogiri-less.
|
|
49
|
+
if defined?(Nokogiri)
|
|
50
|
+
case node
|
|
51
|
+
when Nokogiri::HTML::Document, Nokogiri::HTML4::Document, Nokogiri::HTML5::Document
|
|
52
|
+
# Start from html element or root element
|
|
53
|
+
root = node.at_css("html") || node.root
|
|
54
|
+
root ? to_tree(root) : nil
|
|
55
|
+
when Nokogiri::HTML4::DocumentFragment, Nokogiri::HTML5::DocumentFragment, Nokogiri::XML::DocumentFragment
|
|
56
|
+
# For DocumentFragment, create a wrapper root node and add all fragment children
|
|
57
|
+
convert_fragment(node)
|
|
58
|
+
when Nokogiri::XML::Element
|
|
59
|
+
convert_element(node)
|
|
60
|
+
else
|
|
61
|
+
raise ArgumentError, "Unsupported node type: #{node.class}"
|
|
62
|
+
end
|
|
60
63
|
else
|
|
61
64
|
raise ArgumentError, "Unsupported node type: #{node.class}"
|
|
62
65
|
end
|
|
@@ -68,7 +71,10 @@ module Canon
|
|
|
68
71
|
# @param doc [Nokogiri::HTML::Document] Optional document to use
|
|
69
72
|
# @return [Nokogiri::HTML::Document, Nokogiri::XML::Element]
|
|
70
73
|
def from_tree(tree_node, doc = nil)
|
|
71
|
-
doc
|
|
74
|
+
unless doc
|
|
75
|
+
Canon::NokogiriLoader.require!("HTML tree reconstruction")
|
|
76
|
+
doc = Nokogiri::HTML::Document.new
|
|
77
|
+
end
|
|
72
78
|
|
|
73
79
|
element = build_element(tree_node, doc)
|
|
74
80
|
|
data/lib/canon/version.rb
CHANGED