better_html 1.0.14 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -0
  3. data/Rakefile +19 -14
  4. data/ext/better_html_ext/better_html.h +1 -0
  5. data/ext/better_html_ext/extconf.rb +16 -0
  6. data/ext/better_html_ext/html_tokenizer.c +12 -0
  7. data/ext/better_html_ext/html_tokenizer.h +7 -0
  8. data/ext/better_html_ext/parser.c +793 -0
  9. data/ext/better_html_ext/parser.h +93 -0
  10. data/ext/better_html_ext/tokenizer.c +717 -0
  11. data/ext/better_html_ext/tokenizer.h +80 -0
  12. data/lib/better_html/ast/iterator.rb +14 -9
  13. data/lib/better_html/ast/node.rb +4 -2
  14. data/lib/better_html/better_erb/erubi_implementation.rb +43 -39
  15. data/lib/better_html/better_erb/runtime_checks.rb +140 -133
  16. data/lib/better_html/better_erb/validated_output_buffer.rb +30 -22
  17. data/lib/better_html/better_erb.rb +58 -54
  18. data/lib/better_html/config.rb +7 -4
  19. data/lib/better_html/errors.rb +4 -2
  20. data/lib/better_html/helpers.rb +7 -3
  21. data/lib/better_html/html_attributes.rb +6 -2
  22. data/lib/better_html/parser.rb +21 -14
  23. data/lib/better_html/railtie.rb +8 -4
  24. data/lib/better_html/test_helper/ruby_node.rb +15 -9
  25. data/lib/better_html/test_helper/safe_erb/allowed_script_type.rb +8 -4
  26. data/lib/better_html/test_helper/safe_erb/base.rb +12 -9
  27. data/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb +7 -3
  28. data/lib/better_html/test_helper/safe_erb/no_statements.rb +7 -3
  29. data/lib/better_html/test_helper/safe_erb/script_interpolation.rb +9 -4
  30. data/lib/better_html/test_helper/safe_erb/tag_interpolation.rb +23 -20
  31. data/lib/better_html/test_helper/safe_erb_tester.rb +35 -33
  32. data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -34
  33. data/lib/better_html/test_helper/safety_error.rb +2 -0
  34. data/lib/better_html/tokenizer/base_erb.rb +19 -15
  35. data/lib/better_html/tokenizer/html_erb.rb +3 -2
  36. data/lib/better_html/tokenizer/html_lodash.rb +22 -13
  37. data/lib/better_html/tokenizer/javascript_erb.rb +3 -1
  38. data/lib/better_html/tokenizer/location.rb +17 -6
  39. data/lib/better_html/tokenizer/token.rb +2 -0
  40. data/lib/better_html/tokenizer/token_array.rb +8 -8
  41. data/lib/better_html/tree/attribute.rb +10 -6
  42. data/lib/better_html/tree/attributes_list.rb +9 -5
  43. data/lib/better_html/tree/tag.rb +10 -6
  44. data/lib/better_html/version.rb +3 -1
  45. data/lib/better_html.rb +19 -17
  46. data/lib/tasks/better_html_tasks.rake +1 -0
  47. metadata +43 -148
  48. data/lib/better_html/better_erb/erubis_implementation.rb +0 -44
  49. data/test/better_html/better_erb/implementation_test.rb +0 -406
  50. data/test/better_html/errors_test.rb +0 -13
  51. data/test/better_html/helpers_test.rb +0 -49
  52. data/test/better_html/parser_test.rb +0 -314
  53. data/test/better_html/test_helper/ruby_node_test.rb +0 -288
  54. data/test/better_html/test_helper/safe_erb/allowed_script_type_test.rb +0 -45
  55. data/test/better_html/test_helper/safe_erb/no_javascript_tag_helper_test.rb +0 -37
  56. data/test/better_html/test_helper/safe_erb/no_statements_test.rb +0 -128
  57. data/test/better_html/test_helper/safe_erb/script_interpolation_test.rb +0 -149
  58. data/test/better_html/test_helper/safe_erb/tag_interpolation_test.rb +0 -303
  59. data/test/better_html/test_helper/safe_lodash_tester_test.rb +0 -90
  60. data/test/better_html/tokenizer/html_erb_test.rb +0 -180
  61. data/test/better_html/tokenizer/html_lodash_test.rb +0 -98
  62. data/test/better_html/tokenizer/location_test.rb +0 -75
  63. data/test/better_html/tokenizer/token_array_test.rb +0 -145
  64. data/test/better_html/tokenizer/token_test.rb +0 -15
  65. data/test/dummy/README.rdoc +0 -28
  66. data/test/dummy/Rakefile +0 -6
  67. data/test/dummy/app/assets/javascripts/application.js +0 -13
  68. data/test/dummy/app/assets/stylesheets/application.css +0 -15
  69. data/test/dummy/app/controllers/application_controller.rb +0 -5
  70. data/test/dummy/app/helpers/application_helper.rb +0 -2
  71. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  72. data/test/dummy/bin/bundle +0 -3
  73. data/test/dummy/bin/rails +0 -4
  74. data/test/dummy/bin/rake +0 -4
  75. data/test/dummy/bin/setup +0 -29
  76. data/test/dummy/config/application.rb +0 -26
  77. data/test/dummy/config/boot.rb +0 -5
  78. data/test/dummy/config/database.yml +0 -25
  79. data/test/dummy/config/environment.rb +0 -5
  80. data/test/dummy/config/environments/development.rb +0 -41
  81. data/test/dummy/config/environments/production.rb +0 -79
  82. data/test/dummy/config/environments/test.rb +0 -42
  83. data/test/dummy/config/initializers/assets.rb +0 -11
  84. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  85. data/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  86. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  87. data/test/dummy/config/initializers/inflections.rb +0 -16
  88. data/test/dummy/config/initializers/mime_types.rb +0 -4
  89. data/test/dummy/config/initializers/session_store.rb +0 -3
  90. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  91. data/test/dummy/config/locales/en.yml +0 -23
  92. data/test/dummy/config/routes.rb +0 -56
  93. data/test/dummy/config/secrets.yml +0 -22
  94. data/test/dummy/config.ru +0 -4
  95. data/test/dummy/public/404.html +0 -67
  96. data/test/dummy/public/422.html +0 -67
  97. data/test/dummy/public/500.html +0 -66
  98. data/test/dummy/public/favicon.ico +0 -0
  99. data/test/test_helper.rb +0 -30
@@ -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
- require 'ast'
2
- require 'active_support/core_ext/array/wrap'
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
@@ -1,5 +1,7 @@
1
- require 'ast'
2
- require_relative 'iterator'
1
+ # frozen_string_literal: true
2
+
3
+ require "ast"
4
+ require_relative "iterator"
3
5
 
4
6
  module BetterHtml
5
7
  module AST
@@ -1,50 +1,54 @@
1
- require 'action_view'
2
- require_relative 'runtime_checks'
3
-
4
- class BetterHtml::BetterErb
5
- class ErubiImplementation < ActionView::Template::Handlers::ERB::Erubi
6
- include RuntimeChecks
7
-
8
- def add_text(text)
9
- return if text.empty?
10
-
11
- if text == "\n"
12
- @parser.parse("\n")
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
- @newline_pending = 0
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
- def add_expression(indicator, code)
29
- if (indicator == "==") || @escape
30
- add_expr_auto_escaped(src, code, false)
31
- else
32
- add_expr_auto_escaped(src, code, true)
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
- def add_code(code)
37
- flush_newline_if_pending(src)
39
+ def add_code(code)
40
+ flush_newline_if_pending(src)
38
41
 
39
- block_check(src, "<%#{code}%>")
40
- @parser.append_placeholder(code)
41
- super
42
- end
42
+ block_check(src, "<%#{code}%>")
43
+ @parser.append_placeholder(code)
44
+ super
45
+ end
43
46
 
44
- private
47
+ private
45
48
 
46
- def escape_text(text)
47
- text.gsub(/['\\]/, '\\\\\&')
49
+ def escape_text(text)
50
+ text.gsub(/['\\]/, '\\\\\&')
51
+ end
48
52
  end
49
53
  end
50
54
  end
@@ -1,162 +1,169 @@
1
- require 'html_tokenizer'
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
- def validate!
13
- check_parser_errors
3
+ require "action_view"
14
4
 
15
- unless @parser.context == :none
16
- raise BetterHtml::HtmlError, 'Detected an open tag at the end of this document.'
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
- private
14
+ def validate!
15
+ check_parser_errors unless @config.disable_parser_validation
21
16
 
22
- def class_name
23
- "BetterHtml::BetterErb::ValidatedOutputBuffer"
24
- end
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
- def wrap_method
27
- "#{class_name}.wrap"
28
- end
22
+ private
29
23
 
30
- def add_expr_auto_escaped(src, code, auto_escape)
31
- flush_newline_if_pending(src)
24
+ def class_name
25
+ "BetterHtml::BetterErb::ValidatedOutputBuffer"
26
+ end
32
27
 
33
- src << "#{wrap_method}(@output_buffer, (#{parser_context.inspect}), '#{escape_text(code)}'.freeze, #{auto_escape})"
34
- method_name = "safe_#{@parser.context}_append"
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
- def parser_context
45
- if [:quoted_value, :unquoted_value, :space_after_attribute].include?(@parser.context)
46
- {
47
- tag_name: @parser.tag_name,
48
- attribute_name: @parser.attribute_name,
49
- attribute_value: @parser.attribute_value,
50
- attribute_quoted: @parser.attribute_quoted?,
51
- quote_character: @parser.quote_character,
52
- }
53
- elsif [:attribute_name, :after_attribute_name, :after_equal].include?(@parser.context)
54
- {
55
- tag_name: @parser.tag_name,
56
- attribute_name: @parser.attribute_name,
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
- def block_check(src, code)
79
- unless @parser.context == :none || @parser.context == :rawtext
80
- s = "Ruby statement not allowed.\n"
81
- s << "In '#{@parser.context}' on line #{@parser.line_number} column #{@parser.column_number}:\n"
82
- prefix = extract_line(@parser.line_number)
83
- code = code.lines.first
84
- s << "#{prefix}#{code}\n"
85
- s << "#{' ' * prefix.size}#{'^' * code.size}"
86
- raise BetterHtml::DontInterpolateHere, s
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
- def check_parser_errors
91
- errors = @parser.errors
92
- return if errors.empty?
93
-
94
- s = "#{errors.size} error(s) found in HTML document.\n"
95
- errors.each do |error|
96
- s = "#{error.message}\n"
97
- s << "On line #{error.line} column #{error.column}:\n"
98
- line = extract_line(error.line)
99
- s << "#{line}\n"
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
- raise BetterHtml::HtmlError, s
104
- end
94
+ def check_parser_errors
95
+ errors = @parser.errors
96
+ return if errors.empty?
105
97
 
106
- def check_token(type, *args)
107
- check_tag_name(type, *args) if type == :tag_name
108
- check_attribute_name(type, *args) if type == :attribute_name
109
- check_quoted_value(type, *args) if type == :attribute_quoted_value_start
110
- check_unquoted_value(type, *args) if type == :attribute_unquoted_value
111
- end
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
- def check_tag_name(type, start, stop, line, column)
114
- text = @parser.document[start...stop]
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
- s = "Invalid tag name #{text.inspect} does not match "\
119
- "regular expression #{@config.partial_tag_name_pattern.inspect}\n"
120
- s << build_location(line, column, text.size)
121
- raise BetterHtml::HtmlError, s
122
- end
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
- def check_attribute_name(type, start, stop, line, column)
125
- text = @parser.document[start...stop]
126
- return if @config.partial_attribute_name_pattern === text
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
- s = "Invalid attribute name #{text.inspect} does not match "\
129
- "regular expression #{@config.partial_attribute_name_pattern.inspect}\n"
130
- s << build_location(line, column, text.size)
131
- raise BetterHtml::HtmlError, s
132
- end
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
- def check_quoted_value(type, start, stop, line, column)
135
- return if @config.allow_single_quoted_attributes
136
- text = @parser.document[start...stop]
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
- s = "Single-quoted attributes are not allowed\n"
140
- s << build_location(line, column, text.size)
141
- raise BetterHtml::HtmlError, s
142
- end
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
- def check_unquoted_value(type, start, stop, line, column)
145
- return if @config.allow_unquoted_attributes
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
- def build_location(line, column, length)
152
- s = "On line #{line} column #{column}:\n"
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
- def extract_line(line)
158
- line = @parser.document.lines[line-1]
159
- line.nil? ? "" : line.gsub(/\n$/, '')
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