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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57e1fd14d76e9f1aa4901740844aa4f0f535bb7f06f467b64c86a9f997932aa2
4
- data.tar.gz: c70b5a6b99acd064832369a6ca5972d7565682a03e4cd823f28957c67790d591
3
+ metadata.gz: 4e993fe0bac445ec82e1a02983bfa2f297c1b7d9d112bcb7c724f3f010a1b353
4
+ data.tar.gz: 869324e74378af4f4cb9493fbfdc04ac1ba8e76c0378159547bc7e23ebf66084
5
5
  SHA512:
6
- metadata.gz: 0c0862c5b94807d6f391d7b75d7f030c7563d6d695d250d04dd0092f2cbb199f8fa9fca8fce843a15797bfa983f4f65bae419b95f40c0f413fd086e5e726e245
7
- data.tar.gz: 916f8899f1ed551d89ef95fdea4463df895bba4350a8cd5211aafb1fcc9e8d04c3cff3fa64970a85151ce801e7a04bf3797504d76d253b0c912462af6671dd97
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
- # Pretty format
300
- Canon.format(html_string, :html)
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
- # Pretty format the HTML
380
- Canon.format(node, :html)
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 from the fragment children.
61
- # Whitespace text nodes are preserved unconditionally — HTML
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
- frag = Canon::XmlParsing.moxml_context.parse_html_fragment(html)
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] HTML version (:html4 or :html5)
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
- # Detect if this is a full document (has <html> tag) or fragment
18
- # Full documents should use document parser to preserve structure
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
- # HTML doesn't have strict namespace requirements like XML,
61
- # so skip the relative namespace URI check
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
- # Convert to XPath data model (reuse XML infrastructure)
64
- build_from_nokogiri(doc)
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
- 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)
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 ||= Nokogiri::HTML::Document.new
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canon
4
- VERSION = "0.3.97"
4
+ VERSION = "0.3.99"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.97
4
+ version: 0.3.99
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.