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 +4 -4
- data/lib/canon/html/data_model.rb +54 -3
- data/lib/canon/pretty_printer/html.rb +14 -6
- data/lib/canon/validators/html_validator.rb +23 -9
- data/lib/canon/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47ebab684f05c7445139ea4b0ed5133ab734c1d16918f3dadc055b3ddbc5692f
|
|
4
|
+
data.tar.gz: d80c1ae4a66f43ffb92c3eb8a14ebca8e2bcd2eb373130797a71e7f79a748d9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
55
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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"
|
|
88
|
+
doc.to_html(indent: 1, indent_text: "\t")
|
|
81
89
|
else
|
|
82
|
-
doc.to_html(indent: @indent
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
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
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.
|
|
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-
|
|
11
|
+
date: 2026-10-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|