better_html 1.0.16 → 2.0.0
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/MIT-LICENSE +1 -0
- data/Rakefile +19 -14
- data/ext/better_html_ext/better_html.h +1 -0
- data/ext/better_html_ext/extconf.rb +16 -0
- data/ext/better_html_ext/html_tokenizer.c +12 -0
- data/ext/better_html_ext/html_tokenizer.h +7 -0
- data/ext/better_html_ext/parser.c +793 -0
- data/ext/better_html_ext/parser.h +93 -0
- data/ext/better_html_ext/tokenizer.c +717 -0
- data/ext/better_html_ext/tokenizer.h +80 -0
- data/lib/better_html/ast/iterator.rb +14 -9
- data/lib/better_html/ast/node.rb +4 -2
- data/lib/better_html/better_erb/erubi_implementation.rb +43 -39
- data/lib/better_html/better_erb/runtime_checks.rb +140 -133
- data/lib/better_html/better_erb/validated_output_buffer.rb +30 -22
- data/lib/better_html/better_erb.rb +58 -54
- data/lib/better_html/config.rb +7 -4
- data/lib/better_html/errors.rb +4 -2
- data/lib/better_html/helpers.rb +7 -3
- data/lib/better_html/html_attributes.rb +6 -2
- data/lib/better_html/parser.rb +21 -14
- data/lib/better_html/railtie.rb +8 -4
- data/lib/better_html/test_helper/ruby_node.rb +15 -10
- data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +8 -4
- data/lib/better_html/test_helper/safe_erb/base.rb +12 -9
- data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/no_statements.rb +7 -3
- data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +9 -4
- data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +23 -20
- data/lib/better_html/test_helper/safe_erb_tester.rb +33 -31
- data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -35
- data/lib/better_html/test_helper/safety_error.rb +2 -0
- data/lib/better_html/tokenizer/base_erb.rb +14 -10
- data/lib/better_html/tokenizer/html_erb.rb +3 -2
- data/lib/better_html/tokenizer/html_lodash.rb +22 -14
- data/lib/better_html/tokenizer/javascript_erb.rb +3 -1
- data/lib/better_html/tokenizer/location.rb +17 -6
- data/lib/better_html/tokenizer/token.rb +2 -0
- data/lib/better_html/tokenizer/token_array.rb +8 -8
- data/lib/better_html/tree/attribute.rb +10 -6
- data/lib/better_html/tree/attributes_list.rb +9 -5
- data/lib/better_html/tree/tag.rb +10 -6
- data/lib/better_html/version.rb +3 -1
- data/lib/better_html.rb +19 -17
- data/lib/tasks/better_html_tasks.rake +1 -0
- metadata +39 -147
- data/lib/better_html/better_erb/erubis_implementation.rb +0 -44
- data/test/better_html/better_erb/implementation_test.rb +0 -406
- data/test/better_html/errors_test.rb +0 -13
- data/test/better_html/helpers_test.rb +0 -49
- data/test/better_html/parser_test.rb +0 -314
- data/test/better_html/test_helper/ruby_node_test.rb +0 -288
- data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +0 -46
- data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +0 -37
- data/test/better_html/test_helper/safe_erb/no_statements_test.rb +0 -129
- data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +0 -149
- data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +0 -303
- data/test/better_html/test_helper/safe_lodash_tester_test.rb +0 -90
- data/test/better_html/tokenizer/html_erb_test.rb +0 -180
- data/test/better_html/tokenizer/html_lodash_test.rb +0 -98
- data/test/better_html/tokenizer/location_test.rb +0 -75
- data/test/better_html/tokenizer/token_array_test.rb +0 -146
- data/test/better_html/tokenizer/token_test.rb +0 -15
- data/test/dummy/README.rdoc +0 -28
- data/test/dummy/Rakefile +0 -6
- data/test/dummy/app/assets/javascripts/application.js +0 -13
- data/test/dummy/app/assets/stylesheets/application.css +0 -15
- data/test/dummy/app/controllers/application_controller.rb +0 -5
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/bin/bundle +0 -3
- data/test/dummy/bin/rails +0 -4
- data/test/dummy/bin/rake +0 -4
- data/test/dummy/bin/setup +0 -29
- data/test/dummy/config/application.rb +0 -26
- data/test/dummy/config/boot.rb +0 -5
- data/test/dummy/config/database.yml +0 -25
- data/test/dummy/config/environment.rb +0 -5
- data/test/dummy/config/environments/development.rb +0 -41
- data/test/dummy/config/environments/production.rb +0 -79
- data/test/dummy/config/environments/test.rb +0 -42
- data/test/dummy/config/initializers/assets.rb +0 -11
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
- data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
- data/test/dummy/config/initializers/inflections.rb +0 -16
- data/test/dummy/config/initializers/mime_types.rb +0 -4
- data/test/dummy/config/initializers/session_store.rb +0 -3
- data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/test/dummy/config/locales/en.yml +0 -23
- data/test/dummy/config/routes.rb +0 -56
- data/test/dummy/config/secrets.yml +0 -22
- data/test/dummy/config.ru +0 -4
- data/test/dummy/public/404.html +0 -67
- data/test/dummy/public/422.html +0 -67
- data/test/dummy/public/500.html +0 -66
- data/test/dummy/public/favicon.ico +0 -0
- data/test/test_helper.rb +0 -29
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
enum tokenizer_context {
|
|
4
|
+
TOKENIZER_NONE = 0,
|
|
5
|
+
TOKENIZER_HTML,
|
|
6
|
+
TOKENIZER_OPEN_TAG,
|
|
7
|
+
TOKENIZER_SOLIDUS_OR_TAG_NAME,
|
|
8
|
+
TOKENIZER_TAG_NAME,
|
|
9
|
+
TOKENIZER_CDATA,
|
|
10
|
+
TOKENIZER_RCDATA, // title, textarea
|
|
11
|
+
TOKENIZER_RAWTEXT, // style, xmp, iframe, noembed, noframes
|
|
12
|
+
TOKENIZER_SCRIPT_DATA, // script
|
|
13
|
+
TOKENIZER_PLAINTEXT, // plaintext
|
|
14
|
+
TOKENIZER_COMMENT,
|
|
15
|
+
TOKENIZER_ATTRIBUTE_NAME,
|
|
16
|
+
TOKENIZER_ATTRIBUTE_VALUE,
|
|
17
|
+
TOKENIZER_ATTRIBUTE_UNQUOTED,
|
|
18
|
+
TOKENIZER_ATTRIBUTE_QUOTED,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
enum token_type {
|
|
22
|
+
TOKEN_NONE = 0,
|
|
23
|
+
TOKEN_TEXT,
|
|
24
|
+
TOKEN_WHITESPACE,
|
|
25
|
+
TOKEN_COMMENT_START,
|
|
26
|
+
TOKEN_COMMENT_END,
|
|
27
|
+
TOKEN_TAG_START,
|
|
28
|
+
TOKEN_TAG_NAME,
|
|
29
|
+
TOKEN_TAG_END,
|
|
30
|
+
TOKEN_ATTRIBUTE_NAME,
|
|
31
|
+
TOKEN_ATTRIBUTE_QUOTED_VALUE_START,
|
|
32
|
+
TOKEN_ATTRIBUTE_QUOTED_VALUE,
|
|
33
|
+
TOKEN_ATTRIBUTE_QUOTED_VALUE_END,
|
|
34
|
+
TOKEN_ATTRIBUTE_UNQUOTED_VALUE,
|
|
35
|
+
TOKEN_CDATA_START,
|
|
36
|
+
TOKEN_CDATA_END,
|
|
37
|
+
TOKEN_SOLIDUS,
|
|
38
|
+
TOKEN_EQUAL,
|
|
39
|
+
TOKEN_MALFORMED,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
struct scan_t {
|
|
43
|
+
char *string;
|
|
44
|
+
long unsigned int cursor;
|
|
45
|
+
long unsigned int length;
|
|
46
|
+
|
|
47
|
+
int enc_index;
|
|
48
|
+
long unsigned int mb_cursor;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
struct tokenizer_t
|
|
52
|
+
{
|
|
53
|
+
enum tokenizer_context context[1000];
|
|
54
|
+
uint32_t current_context;
|
|
55
|
+
|
|
56
|
+
void *callback_data;
|
|
57
|
+
void (*f_callback)(struct tokenizer_t *tk, enum token_type type, long unsigned int length, void *data);
|
|
58
|
+
|
|
59
|
+
char attribute_value_start;
|
|
60
|
+
int found_attribute;
|
|
61
|
+
|
|
62
|
+
char *current_tag;
|
|
63
|
+
|
|
64
|
+
int is_closing_tag;
|
|
65
|
+
enum token_type last_token;
|
|
66
|
+
|
|
67
|
+
struct scan_t scan;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
void Init_html_tokenizer_tokenizer(VALUE mHtmlTokenizer);
|
|
72
|
+
void tokenizer_init(struct tokenizer_t *tk);
|
|
73
|
+
void tokenizer_free_members(struct tokenizer_t *tk);
|
|
74
|
+
void tokenizer_set_scan_string(struct tokenizer_t *tk, const char *string, long unsigned int length);
|
|
75
|
+
void tokenizer_free_scan_string(struct tokenizer_t *tk);
|
|
76
|
+
void tokenizer_scan_all(struct tokenizer_t *tk);
|
|
77
|
+
VALUE token_type_to_symbol(enum token_type type);
|
|
78
|
+
|
|
79
|
+
extern const rb_data_type_t ht_tokenizer_data_type;
|
|
80
|
+
#define Tokenizer_Get_Struct(obj, sval) TypedData_Get_Struct(obj, struct tokenizer_t, &ht_tokenizer_data_type, sval)
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ast"
|
|
4
|
+
require "active_support/core_ext/array/wrap"
|
|
3
5
|
|
|
4
6
|
module BetterHtml
|
|
5
7
|
module AST
|
|
6
8
|
class Iterator
|
|
9
|
+
class << self
|
|
10
|
+
def descendants(root_node, type)
|
|
11
|
+
Enumerator.new do |yielder|
|
|
12
|
+
t = new(type) { |node| yielder << node }
|
|
13
|
+
t.traverse(root_node)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
7
18
|
def initialize(types, &block)
|
|
8
19
|
@types = types.nil? ? nil : Array.wrap(types)
|
|
9
20
|
@block = block
|
|
@@ -11,6 +22,7 @@ module BetterHtml
|
|
|
11
22
|
|
|
12
23
|
def traverse(node)
|
|
13
24
|
return unless node.is_a?(::AST::Node)
|
|
25
|
+
|
|
14
26
|
@block.call(node) if @types.nil? || @types.include?(node.type)
|
|
15
27
|
traverse_all(node)
|
|
16
28
|
end
|
|
@@ -20,13 +32,6 @@ module BetterHtml
|
|
|
20
32
|
traverse(node) if node.is_a?(::AST::Node)
|
|
21
33
|
end
|
|
22
34
|
end
|
|
23
|
-
|
|
24
|
-
def self.descendants(root_node, type)
|
|
25
|
-
Enumerator.new do |yielder|
|
|
26
|
-
t = new(type) { |node| yielder << node }
|
|
27
|
-
t.traverse(root_node)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
35
|
end
|
|
31
36
|
end
|
|
32
37
|
end
|
data/lib/better_html/ast/node.rb
CHANGED
|
@@ -1,50 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@newline_pending += 1
|
|
14
|
-
else
|
|
15
|
-
src << "@output_buffer.safe_append='"
|
|
16
|
-
src << "\n" * @newline_pending if @newline_pending > 0
|
|
17
|
-
src << escape_text(text)
|
|
18
|
-
src << "'.freeze;"
|
|
19
|
-
|
|
20
|
-
@parser.parse(text) do |*args|
|
|
21
|
-
check_token(*args)
|
|
22
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "action_view"
|
|
4
|
+
require_relative "runtime_checks"
|
|
5
|
+
|
|
6
|
+
module BetterHtml
|
|
7
|
+
class BetterErb
|
|
8
|
+
class ErubiImplementation < ActionView::Template::Handlers::ERB::Erubi
|
|
9
|
+
include RuntimeChecks
|
|
10
|
+
|
|
11
|
+
def add_text(text)
|
|
12
|
+
return if text.empty?
|
|
23
13
|
|
|
24
|
-
|
|
14
|
+
if text == "\n"
|
|
15
|
+
@parser.parse("\n")
|
|
16
|
+
@newline_pending += 1
|
|
17
|
+
else
|
|
18
|
+
src << "@output_buffer.safe_append='"
|
|
19
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
|
20
|
+
src << escape_text(text)
|
|
21
|
+
src << "'.freeze;"
|
|
22
|
+
|
|
23
|
+
@parser.parse(text) do |*args|
|
|
24
|
+
check_token(*args)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@newline_pending = 0
|
|
28
|
+
end
|
|
25
29
|
end
|
|
26
|
-
end
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
def add_expression(indicator, code)
|
|
32
|
+
if (indicator == "==") || @escape
|
|
33
|
+
add_expr_auto_escaped(src, code, false)
|
|
34
|
+
else
|
|
35
|
+
add_expr_auto_escaped(src, code, true)
|
|
36
|
+
end
|
|
33
37
|
end
|
|
34
|
-
end
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
def add_code(code)
|
|
40
|
+
flush_newline_if_pending(src)
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
block_check(src, "<%#{code}%>")
|
|
43
|
+
@parser.append_placeholder(code)
|
|
44
|
+
super
|
|
45
|
+
end
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
private
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
def escape_text(text)
|
|
50
|
+
text.gsub(/['\\]/, '\\\\\&')
|
|
51
|
+
end
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
end
|
|
@@ -1,162 +1,169 @@
|
|
|
1
|
-
|
|
2
|
-
require 'action_view'
|
|
3
|
-
|
|
4
|
-
class BetterHtml::BetterErb
|
|
5
|
-
module RuntimeChecks
|
|
6
|
-
def initialize(erb, config: BetterHtml.config, **options)
|
|
7
|
-
@parser = HtmlTokenizer::Parser.new
|
|
8
|
-
@config = config
|
|
9
|
-
super(erb, **options)
|
|
10
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
check_parser_errors
|
|
3
|
+
require "action_view"
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
5
|
+
module BetterHtml
|
|
6
|
+
class BetterErb
|
|
7
|
+
module RuntimeChecks
|
|
8
|
+
def initialize(erb, config: BetterHtml.config, **options)
|
|
9
|
+
@parser = HtmlTokenizer::Parser.new
|
|
10
|
+
@config = config
|
|
11
|
+
super(erb, **options)
|
|
17
12
|
end
|
|
18
|
-
end
|
|
19
13
|
|
|
20
|
-
|
|
14
|
+
def validate!
|
|
15
|
+
check_parser_errors unless @config.disable_parser_validation
|
|
21
16
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
unless @parser.context == :none
|
|
18
|
+
raise BetterHtml::HtmlError, "Detected an open tag at the end of this document."
|
|
19
|
+
end
|
|
20
|
+
end
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
"#{class_name}.wrap"
|
|
28
|
-
end
|
|
22
|
+
private
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
def class_name
|
|
25
|
+
"BetterHtml::BetterErb::ValidatedOutputBuffer"
|
|
26
|
+
end
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if code =~ self.class::BLOCK_EXPR
|
|
36
|
-
block_check(src, "<%=#{code}%>")
|
|
37
|
-
src << ".#{method_name}= " << code
|
|
38
|
-
else
|
|
39
|
-
src << ".#{method_name}=(" << code << ");"
|
|
28
|
+
def wrap_method
|
|
29
|
+
"#{class_name}.wrap"
|
|
40
30
|
end
|
|
41
|
-
@parser.append_placeholder("<%=#{code}%>")
|
|
42
|
-
end
|
|
43
31
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
elsif [:tag, :tag_name, :tag_end].include?(@parser.context)
|
|
59
|
-
{
|
|
60
|
-
tag_name: @parser.tag_name,
|
|
61
|
-
}
|
|
62
|
-
elsif @parser.context == :rawtext
|
|
63
|
-
{
|
|
64
|
-
tag_name: @parser.tag_name,
|
|
65
|
-
rawtext_text: @parser.rawtext_text,
|
|
66
|
-
}
|
|
67
|
-
elsif @parser.context == :comment
|
|
68
|
-
{
|
|
69
|
-
comment_text: @parser.comment_text,
|
|
70
|
-
}
|
|
71
|
-
elsif [:none, :solidus_or_tag_name].include?(@parser.context)
|
|
72
|
-
{}
|
|
73
|
-
else
|
|
74
|
-
raise RuntimeError, "Tried to interpolate into unknown location #{@parser.context}."
|
|
32
|
+
def add_expr_auto_escaped(src, code, auto_escape)
|
|
33
|
+
flush_newline_if_pending(src)
|
|
34
|
+
|
|
35
|
+
escaped_code = escape_text(code)
|
|
36
|
+
|
|
37
|
+
src << "#{wrap_method}(@output_buffer, (#{parser_context.inspect}), '#{escaped_code}'.freeze, #{auto_escape})"
|
|
38
|
+
method_name = "safe_#{@parser.context}_append"
|
|
39
|
+
if code =~ self.class::BLOCK_EXPR
|
|
40
|
+
block_check(src, "<%=#{code}%>")
|
|
41
|
+
src << ".#{method_name}= " << code
|
|
42
|
+
else
|
|
43
|
+
src << ".#{method_name}=(" << code << ");"
|
|
44
|
+
end
|
|
45
|
+
@parser.append_placeholder("<%=#{code}%>")
|
|
75
46
|
end
|
|
76
|
-
end
|
|
77
47
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
48
|
+
def parser_context
|
|
49
|
+
if [:quoted_value, :unquoted_value, :space_after_attribute].include?(@parser.context)
|
|
50
|
+
{
|
|
51
|
+
tag_name: @parser.tag_name,
|
|
52
|
+
attribute_name: @parser.attribute_name,
|
|
53
|
+
attribute_value: @parser.attribute_value,
|
|
54
|
+
attribute_quoted: @parser.attribute_quoted?,
|
|
55
|
+
quote_character: @parser.quote_character,
|
|
56
|
+
}
|
|
57
|
+
elsif [:attribute_name, :after_attribute_name, :after_equal].include?(@parser.context)
|
|
58
|
+
{
|
|
59
|
+
tag_name: @parser.tag_name,
|
|
60
|
+
attribute_name: @parser.attribute_name,
|
|
61
|
+
}
|
|
62
|
+
elsif [:tag, :tag_name, :tag_end].include?(@parser.context)
|
|
63
|
+
{
|
|
64
|
+
tag_name: @parser.tag_name,
|
|
65
|
+
}
|
|
66
|
+
elsif @parser.context == :rawtext
|
|
67
|
+
{
|
|
68
|
+
tag_name: @parser.tag_name,
|
|
69
|
+
rawtext_text: @parser.rawtext_text,
|
|
70
|
+
}
|
|
71
|
+
elsif @parser.context == :comment
|
|
72
|
+
{
|
|
73
|
+
comment_text: @parser.comment_text,
|
|
74
|
+
}
|
|
75
|
+
elsif [:none, :solidus_or_tag_name].include?(@parser.context)
|
|
76
|
+
{}
|
|
77
|
+
else
|
|
78
|
+
raise "Tried to interpolate into unknown location #{@parser.context}."
|
|
79
|
+
end
|
|
87
80
|
end
|
|
88
|
-
end
|
|
89
81
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
s << "#{' ' * (error.column)}#{'^' * (line.size - error.column)}"
|
|
82
|
+
def block_check(src, code)
|
|
83
|
+
unless @parser.context == :none || @parser.context == :rawtext
|
|
84
|
+
s = +"Ruby statement not allowed.\n"
|
|
85
|
+
s << "In '#{@parser.context}' on line #{@parser.line_number} column #{@parser.column_number}:\n"
|
|
86
|
+
prefix = extract_line(@parser.line_number)
|
|
87
|
+
code = code.lines.first
|
|
88
|
+
s << "#{prefix}#{code}\n"
|
|
89
|
+
s << "#{" " * prefix.size}#{"^" * code.size}"
|
|
90
|
+
raise BetterHtml::DontInterpolateHere, s
|
|
91
|
+
end
|
|
101
92
|
end
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
def check_parser_errors
|
|
95
|
+
errors = @parser.errors
|
|
96
|
+
return if errors.empty?
|
|
105
97
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
s = +"#{errors.size} error(s) found in HTML document.\n"
|
|
99
|
+
errors.each do |error|
|
|
100
|
+
s << "#{error.message}\n"
|
|
101
|
+
s << "On line #{error.line} column #{error.column}:\n"
|
|
102
|
+
line = extract_line(error.line)
|
|
103
|
+
s << "#{line}\n"
|
|
104
|
+
s << "#{" " * error.column}#{"^" * (line.size - error.column)}"
|
|
105
|
+
end
|
|
112
106
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return if text.upcase == "!DOCTYPE"
|
|
116
|
-
return if @config.partial_tag_name_pattern === text
|
|
107
|
+
raise BetterHtml::HtmlError, s
|
|
108
|
+
end
|
|
117
109
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
def check_token(type, *args)
|
|
111
|
+
check_tag_name(type, *args) if type == :tag_name
|
|
112
|
+
check_attribute_name(type, *args) if type == :attribute_name
|
|
113
|
+
check_quoted_value(type, *args) if type == :attribute_quoted_value_start
|
|
114
|
+
check_unquoted_value(type, *args) if type == :attribute_unquoted_value
|
|
115
|
+
end
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
117
|
+
def check_tag_name(type, start, stop, line, column)
|
|
118
|
+
text = @parser.document[start...stop]
|
|
119
|
+
return if text.upcase == "!DOCTYPE"
|
|
120
|
+
return if @config.partial_tag_name_pattern.match?(text)
|
|
127
121
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
s = +"Invalid tag name #{text.inspect} does not match "\
|
|
123
|
+
"regular expression #{@config.partial_tag_name_pattern.inspect}\n"
|
|
124
|
+
s << build_location(line, column, text.size)
|
|
125
|
+
raise BetterHtml::HtmlError, s
|
|
126
|
+
end
|
|
133
127
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return if text == '"'
|
|
128
|
+
def check_attribute_name(type, start, stop, line, column)
|
|
129
|
+
text = @parser.document[start...stop]
|
|
130
|
+
return if @config.partial_attribute_name_pattern.match?(text)
|
|
138
131
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
s = +"Invalid attribute name #{text.inspect} does not match "\
|
|
133
|
+
"regular expression #{@config.partial_attribute_name_pattern.inspect}\n"
|
|
134
|
+
s << build_location(line, column, text.size)
|
|
135
|
+
raise BetterHtml::HtmlError, s
|
|
136
|
+
end
|
|
143
137
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
s = "Unquoted attribute values are not allowed\n"
|
|
147
|
-
s << build_location(line, column, stop-start)
|
|
148
|
-
raise BetterHtml::HtmlError, s
|
|
149
|
-
end
|
|
138
|
+
def check_quoted_value(type, start, stop, line, column)
|
|
139
|
+
return if @config.allow_single_quoted_attributes
|
|
150
140
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
s << "#{extract_line(line)}\n"
|
|
154
|
-
s << "#{' ' * column}#{'^' * length}"
|
|
155
|
-
end
|
|
141
|
+
text = @parser.document[start...stop]
|
|
142
|
+
return if text == '"'
|
|
156
143
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
144
|
+
s = +"Single-quoted attributes are not allowed\n"
|
|
145
|
+
s << build_location(line, column, text.size)
|
|
146
|
+
raise BetterHtml::HtmlError, s
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def check_unquoted_value(type, start, stop, line, column)
|
|
150
|
+
return if @config.allow_unquoted_attributes
|
|
151
|
+
|
|
152
|
+
s = +"Unquoted attribute values are not allowed\n"
|
|
153
|
+
s << build_location(line, column, stop - start)
|
|
154
|
+
raise BetterHtml::HtmlError, s
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def build_location(line, column, length)
|
|
158
|
+
s = +"On line #{line} column #{column}:\n"
|
|
159
|
+
s << "#{extract_line(line)}\n"
|
|
160
|
+
s << "#{" " * column}#{"^" * length}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def extract_line(line)
|
|
164
|
+
line = @parser.document.lines[line - 1]
|
|
165
|
+
line.nil? ? "" : line.gsub(/\n$/, "")
|
|
166
|
+
end
|
|
160
167
|
end
|
|
161
168
|
end
|
|
162
169
|
end
|