canon 0.3.99 → 0.3.100

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: 4e993fe0bac445ec82e1a02983bfa2f297c1b7d9d112bcb7c724f3f010a1b353
4
- data.tar.gz: 869324e74378af4f4cb9493fbfdc04ac1ba8e76c0378159547bc7e23ebf66084
3
+ metadata.gz: 47ebab684f05c7445139ea4b0ed5133ab734c1d16918f3dadc055b3ddbc5692f
4
+ data.tar.gz: d80c1ae4a66f43ffb92c3eb8a14ebca8e2bcd2eb373130797a71e7f79a748d9a
5
5
  SHA512:
6
- metadata.gz: 70c6dcb6bb9da7ef764f9aed9e1e8d02fb95193c446102df62496a10dbe33010d29b5d0e90293d929cf3adc267b5128ad1dd7e9666dff38b892d221bc681f20c
7
- data.tar.gz: dc45835bdab2c58479e8413eb87aee5c8615237ebf6bd2c435ae725b58b7df247c3740c25a3ab192fb0aaf86c6776d3634c5a66170ea6834a3f3bedf5582fd27
6
+ metadata.gz: 3555261e4b8280e6967bd63900dec75bb19d2225ff2da16748136ee7ddcb076fcee4f04b6502801afd92d344ef67b578c692868e63c3c181dff8008efc472986
7
+ data.tar.gz: 590b8d12f49526bd3965d78f2000b4d7e64452e0b026547285d0762f6fb0565311ffb00c595aaac85813c90658656842aca1a8970e25f88f20e4f6d09e6ddeb8
@@ -50,11 +50,62 @@ module Canon
50
50
  root
51
51
  end
52
52
 
53
+ # Elements whose inner whitespace is always significant.
54
+ WHITESPACE_PRESERVING = %w[pre textarea script style].freeze
55
+
53
56
  # Deterministic single-line engine formatting for the :format
54
- # string preprocessor — parse + re-serialize as a pure function
55
- # of the parsed tree.
57
+ # string preprocessor: parse + strip whitespace-only text that
58
+ # is insignificant between block siblings + re-serialize — a
59
+ # pure function of the parsed tree, so equivalent inputs
60
+ # converge to identical bytes regardless of source indentation.
61
+ # Inline-significant whitespace (between two inline siblings,
62
+ # inside whitespace-preserving elements) survives.
56
63
  def self.engine_html_format(html)
57
- Canon::XmlParsing.moxml_context.parse_html(html).to_html
64
+ doc = engine_html_parse(html)
65
+ strip_insignificant_whitespace!(doc.root)
66
+ doc.to_html
67
+ end
68
+
69
+ # Parse a full HTML document through the engine WHATWG lane.
70
+ def self.engine_html_parse(html)
71
+ Canon::XmlParsing.moxml_context.parse_html(html)
72
+ end
73
+
74
+ def self.strip_insignificant_whitespace!(node)
75
+ return unless node.is_a?(Moxml::Element)
76
+
77
+ preserve_parent = WHITESPACE_PRESERVING.include?(node.name)
78
+ node.children.to_a.each do |child|
79
+ if child.is_a?(Moxml::Text)
80
+ next unless child.content.strip.empty?
81
+ next if preserve_parent
82
+ next if inline_significant?(child, node)
83
+
84
+ child.remove
85
+ elsif child.is_a?(Moxml::Element)
86
+ strip_insignificant_whitespace!(child)
87
+ end
88
+ end
89
+ end
90
+
91
+ # Whitespace between two inline siblings (or anchored by content)
92
+ # is significant — the #112 rule. Edge or block-anchored runs are
93
+ # formatting artifacts.
94
+ def self.inline_significant?(text, parent)
95
+ siblings = parent.children.to_a
96
+ idx = siblings.index(text)
97
+ prev = siblings[0...idx].reverse_each.find { |s| !whitespace_only?(s) }
98
+ nxt = siblings[(idx + 1)..].find { |s| !whitespace_only?(s) }
99
+ inline_node?(prev) && inline_node?(nxt)
100
+ end
101
+
102
+ def self.whitespace_only?(node)
103
+ node.is_a?(Moxml::Text) && node.content.strip.empty?
104
+ end
105
+
106
+ def self.inline_node?(node)
107
+ !node.nil? && node.is_a?(Moxml::Element) &&
108
+ Canon::Comparison::WhitespaceSensitivity::INLINE_ELEMENTS.include?(node.name)
58
109
  end
59
110
 
60
111
  # Alias for compatibility
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Canon::NokogiriLoader.require!("HTML pretty-printing")
4
3
  require "stringio"
5
4
 
6
5
  module Canon
@@ -37,7 +36,14 @@ module Canon
37
36
  end
38
37
 
39
38
  def format(html_string)
40
- return format_fixture_ready(html_string) if @fixture_ready
39
+ if @fixture_ready
40
+ # The fixture-ready display mode is the one Nokogiri lane
41
+ # left (libxml FORMAT|AS_XHTML save flags — moxml#390 filed
42
+ # for an engine XHTML mode); everything else is engine-
43
+ # served.
44
+ Canon::NokogiriLoader.require!("HTML fixture-ready display")
45
+ return format_fixture_ready(html_string)
46
+ end
41
47
 
42
48
  if xhtml?(html_string)
43
49
  format_as_xhtml(html_string)
@@ -73,13 +79,15 @@ module Canon
73
79
  expand_non_void_self_closing(out)
74
80
  end
75
81
 
82
+ # Engine serialization with the #354 indent knobs — the engine's
83
+ # own nested layout (a deliberate byte change vs the Nokogiri
84
+ # lane: synthesis shape differs, e.g. no empty <head></head>).
76
85
  def format_as_html(html_string)
77
- doc = Nokogiri::HTML5(html_string)
78
-
86
+ doc = Canon::Html::DataModel.engine_html_parse(html_string)
79
87
  if @indent_type == "tab"
80
- doc.to_html(indent: 1, indent_text: "\t", encoding: "UTF-8")
88
+ doc.to_html(indent: 1, indent_text: "\t")
81
89
  else
82
- doc.to_html(indent: @indent, encoding: "UTF-8")
90
+ doc.to_html(indent: @indent)
83
91
  end
84
92
  end
85
93
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Canon::NokogiriLoader.require!("HTML validation")
4
-
5
3
  module Canon
6
4
  module Validators
7
5
  # Validator for HTML input
@@ -44,27 +42,43 @@ module Canon
44
42
  # @param input [String] The XHTML string to validate
45
43
  # @raise [Canon::ValidationError] If XHTML is malformed
46
44
  # @return [void]
45
+ # XHTML is XML: the engine strict parse is the validator. The
46
+ # engine reports the failure position as a byte offset embedded
47
+ # in the error message; line/column derive from it.
47
48
  def self.validate_xhtml!(input)
48
- Nokogiri::XML(input) do |config|
49
- config.strict.nonet
50
- end
51
- rescue Nokogiri::XML::SyntaxError => e
52
- location = extract_location(e)
49
+ Canon::XmlParsing.moxml_context.parse(input, strict: true)
50
+ rescue Moxml::ParseError => e
51
+ location = extract_engine_location(e, input)
53
52
  raise Canon::ValidationError.new(
54
53
  e.message.split("\n").first,
55
54
  format: :html,
56
55
  line: location[:line],
57
56
  column: location[:column],
58
- details: "XHTML validation failed: #{extract_details(e)}",
57
+ details: "XHTML validation failed",
59
58
  )
60
59
  end
61
60
 
61
+ # Derive line/column from the engine error's "at byte N" offset.
62
+ def self.extract_engine_location(error, input)
63
+ byte = error.message[/at byte (\d+)/, 1].to_i
64
+ before = input.byteslice(0, byte) || ""
65
+ line = before.count("\n") + 1
66
+ last_nl = before.rindex("\n")
67
+ column = last_nl ? byte - (last_nl + 1) : byte
68
+ { line: line, column: column }
69
+ end
70
+
62
71
  # Validate HTML5 input
63
72
  #
64
73
  # @param input [String] The HTML5 string to validate
65
74
  # @raise [Canon::ValidationError] If HTML5 is malformed
66
75
  # @return [void]
67
76
  def self.validate_html5!(input)
77
+ # The HTML5 lint layer stays on Nokogiri/gumbo: the WHATWG
78
+ # parser never fails, so the engine has no parse-error token
79
+ # stream to classify (filed as leptris#1771 - the ask carries
80
+ # canon's acceptance protocol).
81
+ Canon::NokogiriLoader.require!("HTML5 validation (leptris#1771)")
68
82
  doc = Nokogiri::HTML5(input, max_errors: 100)
69
83
 
70
84
  # Check for parse errors
@@ -126,7 +140,7 @@ module Canon
126
140
 
127
141
  private_class_method :xhtml?, :validate_xhtml!, :validate_html5!,
128
142
  :extract_details, :build_error_details,
129
- :doctype_or_warning?
143
+ :doctype_or_warning?, :extract_engine_location
130
144
  end
131
145
  end
132
146
  end
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.99"
4
+ VERSION = "0.3.100"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.99
4
+ version: 0.3.100
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-18 00:00:00.000000000 Z
11
+ date: 2026-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs