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.
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 -10
  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 +33 -31
  32. data/lib/better_html/test_helper/safe_lodash_tester.rb +36 -35
  33. data/lib/better_html/test_helper/safety_error.rb +2 -0
  34. data/lib/better_html/tokenizer/base_erb.rb +14 -10
  35. data/lib/better_html/tokenizer/html_erb.rb +3 -2
  36. data/lib/better_html/tokenizer/html_lodash.rb +22 -14
  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 +39 -147
  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 -46
  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 -129
  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 -146
  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 -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
- 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