canon 0.3.101 → 0.3.102
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7585f36fdd0ec2e32f315db6393c265e885eeff9e1ad705ad792bfcaeab3373d
|
|
4
|
+
data.tar.gz: f29aa48def44453b036c2dad82883839f19fc846b6e9e4d095f25da4b1a82eb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e05e33a87145653f7986ee62c076bd12ded0c95ff96dc74cc5ddf81c2bf48b5286d000715b9c7c32da4160c73f419b6fde7597e46f6e4fbf667324bf0a3d5ca
|
|
7
|
+
data.tar.gz: 3c11b01ca57ab4d737aafd8c159040934ab4e7c80d697d9be07c0b1e05bbfc9bbc7db6e1718a0bb54f1629cad16f5a0f4c80431846babceb236d74b63ec4226e
|
|
@@ -571,8 +571,8 @@ module Canon
|
|
|
571
571
|
return Comparison::UNEQUAL_NODES_TYPES
|
|
572
572
|
end
|
|
573
573
|
|
|
574
|
-
content1 =
|
|
575
|
-
content2 =
|
|
574
|
+
content1 = pi_content(n1)
|
|
575
|
+
content2 = pi_content(n2)
|
|
576
576
|
|
|
577
577
|
if content1 == content2
|
|
578
578
|
Comparison::EQUIVALENT
|
|
@@ -597,8 +597,60 @@ module Canon
|
|
|
597
597
|
return Comparison::MISSING_NODE
|
|
598
598
|
end
|
|
599
599
|
|
|
600
|
-
compare_nodes(root1, root2, opts, child_opts, diff_children,
|
|
601
|
-
|
|
600
|
+
status = compare_nodes(root1, root2, opts, child_opts, diff_children,
|
|
601
|
+
differences)
|
|
602
|
+
compare_document_siblings(n1, n2, root1, root2, opts, differences)
|
|
603
|
+
status
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Document-level processing instructions and comments (the XML
|
|
607
|
+
# declaration never appears here - it is stripped at parse time
|
|
608
|
+
# by policy) participate in the comparison as an ordered sibling
|
|
609
|
+
# sequence around the root element.
|
|
610
|
+
def compare_document_siblings(n1, n2, root1, root2, opts, differences)
|
|
611
|
+
sibs1 = document_siblings(n1, root1)
|
|
612
|
+
sibs2 = document_siblings(n2, root2)
|
|
613
|
+
|
|
614
|
+
if sibs1.length != sibs2.length
|
|
615
|
+
add_difference(n1, n2, Comparison::MISSING_NODE,
|
|
616
|
+
Comparison::MISSING_NODE, :text_content, opts,
|
|
617
|
+
differences)
|
|
618
|
+
return
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
sibs1.zip(sibs2).each do |a, b|
|
|
622
|
+
next unless a.class != b.class ||
|
|
623
|
+
(a.is_a?(Canon::Xml::Nodes::ProcessingInstructionNode) &&
|
|
624
|
+
compare_processing_instruction_nodes(a, b, opts,
|
|
625
|
+
differences) ==
|
|
626
|
+
Comparison::UNEQUAL_TEXT_CONTENTS) ||
|
|
627
|
+
(a.is_a?(Canon::Xml::Nodes::CommentNode) &&
|
|
628
|
+
compare_comment_nodes(a, b, opts, differences) ==
|
|
629
|
+
Comparison::UNEQUAL_COMMENTS)
|
|
630
|
+
|
|
631
|
+
add_difference(a, b, Comparison::UNEQUAL_NODES_TYPES,
|
|
632
|
+
Comparison::UNEQUAL_NODES_TYPES, :text_content,
|
|
633
|
+
opts, differences)
|
|
634
|
+
end
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def document_siblings(doc, root)
|
|
638
|
+
return [] unless doc.respond_to?(:children)
|
|
639
|
+
|
|
640
|
+
doc.children.reject do |child|
|
|
641
|
+
child.equal?(root) || child.is_a?(Canon::Xml::Nodes::TextNode) ||
|
|
642
|
+
child.is_a?(Canon::Xml::Nodes::ElementNode)
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
# PI content lives in different accessors per node flavour:
|
|
647
|
+
# Canon::Xml::Nodes::ProcessingInstructionNode#data, engine
|
|
648
|
+
# processing instructions expose #content.
|
|
649
|
+
def pi_content(node)
|
|
650
|
+
return node.data.to_s if node.is_a?(Canon::Xml::Nodes::ProcessingInstructionNode)
|
|
651
|
+
return node.content.to_s if Canon::XmlParsing.xml_node?(node)
|
|
652
|
+
|
|
653
|
+
node.respond_to?(:data) ? node.data.to_s : ""
|
|
602
654
|
end
|
|
603
655
|
|
|
604
656
|
# Compare children of two nodes using semantic matching
|
|
@@ -98,29 +98,24 @@ module Canon
|
|
|
98
98
|
# indentation; +AS_XHTML+ produces well-shaped output (void
|
|
99
99
|
# elements self-closed, non-void paired); +NO_DECLARATION+
|
|
100
100
|
# suppresses the +<?xml ...?>+ prefix.
|
|
101
|
-
# Engine parse + significance-aware strip + the
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
101
|
+
# Engine parse + significance-aware strip + the engine's
|
|
102
|
+
# XHTML serialization mode (moxml#390 delivered to_xhtml /
|
|
103
|
+
# to_html(xhtml: true)): void elements self-closed, non-void
|
|
104
|
+
# paired — the AS_XHTML contract, engine-served.
|
|
105
105
|
def format_fixture_ready(html_string)
|
|
106
106
|
doc = Canon::Html::DataModel.engine_html_parse(html_string)
|
|
107
107
|
Canon::Html::DataModel.strip_insignificant_whitespace!(doc.root)
|
|
108
108
|
html = if @indent_type == "tab"
|
|
109
|
-
doc.
|
|
109
|
+
doc.to_xhtml(indent: 1, indent_text: "\t")
|
|
110
110
|
else
|
|
111
|
-
doc.
|
|
111
|
+
doc.to_xhtml(indent: @indent)
|
|
112
112
|
end
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
html
|
|
119
|
-
name = ::Regexp.last_match(1)
|
|
120
|
-
next ::Regexp.last_match(0) unless HtmlVoidElements.void?(name)
|
|
121
|
-
|
|
122
|
-
"<#{name}#{::Regexp.last_match(2)}/>"
|
|
123
|
-
end
|
|
113
|
+
# The AS_XHTML contract pairs empty NON-void elements (<a .../>)
|
|
114
|
+
# for browser-safe pasting; the engine's to_xhtml self-closes
|
|
115
|
+
# every empty element. The existing transform restores the
|
|
116
|
+
# pairing for non-voids while voids stay self-closed — the
|
|
117
|
+
# #135 regression pins it.
|
|
118
|
+
expand_non_void_self_closing(html)
|
|
124
119
|
end
|
|
125
120
|
|
|
126
121
|
# Rewrite +<tag …/>+ into +<tag …></tag>+ for every element name
|
|
@@ -49,6 +49,12 @@ module Canon
|
|
|
49
49
|
return to_tree_from_canon_processing_instruction(node)
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# Raw moxml documents/elements (the leptris engine's parse
|
|
53
|
+
# output) convert through the shared data model.
|
|
54
|
+
if defined?(Moxml) && node.is_a?(Moxml::Node)
|
|
55
|
+
return to_tree(Canon::Xml::DataModel.build_from_moxml(node))
|
|
56
|
+
end
|
|
57
|
+
|
|
52
58
|
# Fallback to raw engine documents (legacy support). Nokogiri
|
|
53
59
|
# is optional — under the moxml/leptris engine a raw Nokogiri
|
|
54
60
|
# object cannot exist, so the constants stay unresolved.
|
|
@@ -200,10 +206,34 @@ module Canon
|
|
|
200
206
|
# @param root_node [Canon::Xml::Nodes::RootNode] Root node
|
|
201
207
|
# @return [Core::TreeNode, nil] Tree node for first child (document element)
|
|
202
208
|
def to_tree_from_canon_root(root_node)
|
|
203
|
-
# Root node:
|
|
209
|
+
# Root node: convert the document element, keeping
|
|
210
|
+
# document-level processing instructions / comments as
|
|
211
|
+
# siblings of it (before it first, then after) so they
|
|
212
|
+
# participate in the diff. The XML declaration never reaches
|
|
213
|
+
# here - it is stripped at parse time by policy.
|
|
204
214
|
return nil if root_node.children.empty?
|
|
205
215
|
|
|
206
|
-
|
|
216
|
+
element_child = root_node.children.find do |child|
|
|
217
|
+
child.is_a?(Canon::Xml::Nodes::ElementNode)
|
|
218
|
+
end
|
|
219
|
+
return to_tree(root_node.children.first) if element_child.nil?
|
|
220
|
+
|
|
221
|
+
tree = to_tree(element_child)
|
|
222
|
+
seen_element = false
|
|
223
|
+
root_node.children.each do |child|
|
|
224
|
+
if child.equal?(element_child)
|
|
225
|
+
seen_element = true
|
|
226
|
+
next
|
|
227
|
+
end
|
|
228
|
+
next if child.is_a?(Canon::Xml::Nodes::ElementNode)
|
|
229
|
+
next if child.is_a?(Canon::Xml::Nodes::TextNode)
|
|
230
|
+
|
|
231
|
+
sibling = to_tree(child)
|
|
232
|
+
next if sibling.nil?
|
|
233
|
+
|
|
234
|
+
seen_element ? tree.children << sibling : tree.children.unshift(sibling)
|
|
235
|
+
end
|
|
236
|
+
tree
|
|
207
237
|
end
|
|
208
238
|
|
|
209
239
|
# Convert Canon::Xml::Nodes::ElementNode to TreeNode
|
|
@@ -4,7 +4,9 @@ module Canon
|
|
|
4
4
|
module Validators
|
|
5
5
|
# Validator for HTML input
|
|
6
6
|
#
|
|
7
|
-
# Validates HTML input (HTML4, HTML5, or XHTML)
|
|
7
|
+
# Validates HTML input (HTML4, HTML5, or XHTML) through the
|
|
8
|
+
# engine (strict XML parse for XHTML; the WHATWG parse-lint
|
|
9
|
+
# surface for HTML5 — leptris#1771).
|
|
8
10
|
# Automatically detects the HTML type and applies appropriate validation.
|
|
9
11
|
# Raises detailed ValidationError with line and column information
|
|
10
12
|
# when malformed HTML is detected.
|
|
@@ -73,74 +75,44 @@ module Canon
|
|
|
73
75
|
# @param input [String] The HTML5 string to validate
|
|
74
76
|
# @raise [Canon::ValidationError] If HTML5 is malformed
|
|
75
77
|
# @return [void]
|
|
78
|
+
# The HTML5 lint layer is engine-served (leptris#1771 delivered
|
|
79
|
+
# the §13.2.5 parse-error token stream: machine-readable code +
|
|
80
|
+
# line + column, RECOVER class). Engine-direct call — the
|
|
81
|
+
# documented seam exception (moxml's parse_html does not forward
|
|
82
|
+
# the collect_errors option yet; ask filed upstream).
|
|
76
83
|
def self.validate_html5!(input)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Canon::NokogiriLoader.require!("HTML5 validation (leptris#1771)")
|
|
82
|
-
doc = Nokogiri::HTML5(input, max_errors: 100)
|
|
84
|
+
doc = Leptris::XML.parse_html(input, mode: :whatwg,
|
|
85
|
+
collect_errors: true)
|
|
86
|
+
errors = doc.errors
|
|
87
|
+
return if errors.empty?
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
return unless doc.errors.any?
|
|
86
|
-
|
|
87
|
-
# Find first significant error (level 2 = error, level 1 = warning)
|
|
88
|
-
# Filter out doctype warnings and other non-critical issues
|
|
89
|
-
significant_errors = doc.errors.select do |e|
|
|
90
|
-
e.level >= 2 && !doctype_or_warning?(e)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
return if significant_errors.empty?
|
|
94
|
-
|
|
95
|
-
error = significant_errors.first
|
|
96
|
-
location = extract_location(error)
|
|
89
|
+
error = errors.first
|
|
97
90
|
raise Canon::ValidationError.new(
|
|
98
|
-
error.message,
|
|
91
|
+
"[#{error.code}] #{error.message}",
|
|
99
92
|
format: :html,
|
|
100
|
-
line:
|
|
101
|
-
column:
|
|
102
|
-
details: build_error_details(
|
|
93
|
+
line: error.line,
|
|
94
|
+
column: error.column,
|
|
95
|
+
details: build_error_details(errors),
|
|
103
96
|
)
|
|
104
97
|
end
|
|
105
98
|
|
|
106
|
-
# Extract additional error details
|
|
107
|
-
#
|
|
108
|
-
# @param error [Nokogiri::XML::SyntaxError] The syntax error
|
|
109
|
-
# @return [String, nil] Additional details about the error
|
|
110
|
-
def self.extract_details(_error)
|
|
111
|
-
nil
|
|
112
|
-
end
|
|
113
|
-
|
|
114
99
|
# Build error details from multiple errors
|
|
115
100
|
#
|
|
116
|
-
# @param errors [Array<
|
|
101
|
+
# @param errors [Array<Leptris::XML::SyntaxError>] Array of errors
|
|
117
102
|
# @return [String, nil] Combined error details
|
|
118
103
|
def self.build_error_details(errors)
|
|
119
104
|
return nil if errors.size <= 1
|
|
120
105
|
|
|
121
|
-
|
|
122
|
-
return nil if significant.empty?
|
|
123
|
-
|
|
124
|
-
details = significant[1..3].map do |e|
|
|
125
|
-
loc = extract_location(e)
|
|
106
|
+
errors[1..3].map do |e|
|
|
126
107
|
msg = e.message
|
|
127
|
-
msg += " (line #{
|
|
108
|
+
msg += " (line #{e.line})" if e.respond_to?(:line) && e.line
|
|
128
109
|
msg
|
|
129
|
-
end
|
|
130
|
-
details.join("; ")
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Check if error is a doctype or other non-critical warning
|
|
134
|
-
#
|
|
135
|
-
# @param error [Nokogiri::XML::SyntaxError] The error to check
|
|
136
|
-
# @return [Boolean] true if error is non-critical
|
|
137
|
-
def self.doctype_or_warning?(error)
|
|
138
|
-
error.message.match?(/doctype|Expected a doctype token/i)
|
|
110
|
+
end.join("; ")
|
|
139
111
|
end
|
|
140
112
|
|
|
141
113
|
private_class_method :xhtml?, :validate_xhtml!, :validate_html5!,
|
|
142
|
-
:
|
|
143
|
-
:
|
|
114
|
+
:build_error_details,
|
|
115
|
+
:extract_engine_location
|
|
144
116
|
end
|
|
145
117
|
end
|
|
146
118
|
end
|
data/lib/canon/version.rb
CHANGED