rails-deprecated_sanitizer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/README.md +16 -0
- data/lib/rails-deprecated_sanitizer.rb +1 -0
- data/lib/rails/deprecated_sanitizer.rb +152 -0
- data/lib/rails/deprecated_sanitizer/html-scanner.rb +20 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/document.rb +68 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/node.rb +532 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/sanitizer.rb +188 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/selector.rb +830 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/tokenizer.rb +107 -0
- data/lib/rails/deprecated_sanitizer/html-scanner/html/version.rb +11 -0
- data/lib/rails/deprecated_sanitizer/version.rb +5 -0
- data/test/cdata_node_test.rb +16 -0
- data/test/deprecated_sanitizer_test.rb +30 -0
- data/test/document_test.rb +149 -0
- data/test/node_test.rb +90 -0
- data/test/tag_node_test.rb +244 -0
- data/test/test_helper.rb +10 -0
- data/test/text_node_test.rb +51 -0
- data/test/tokenizer_test.rb +132 -0
- metadata +114 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module HTML #:nodoc:
|
4
|
+
|
5
|
+
# A simple HTML tokenizer. It simply breaks a stream of text into tokens, where each
|
6
|
+
# token is a string. Each string represents either "text", or an HTML element.
|
7
|
+
#
|
8
|
+
# This currently assumes valid XHTML, which means no free < or > characters.
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
#
|
12
|
+
# tokenizer = HTML::Tokenizer.new(text)
|
13
|
+
# while token = tokenizer.next
|
14
|
+
# p token
|
15
|
+
# end
|
16
|
+
class Tokenizer #:nodoc:
|
17
|
+
|
18
|
+
# The current (byte) position in the text
|
19
|
+
attr_reader :position
|
20
|
+
|
21
|
+
# The current line number
|
22
|
+
attr_reader :line
|
23
|
+
|
24
|
+
# Create a new Tokenizer for the given text.
|
25
|
+
def initialize(text)
|
26
|
+
text.encode!
|
27
|
+
@scanner = StringScanner.new(text)
|
28
|
+
@position = 0
|
29
|
+
@line = 0
|
30
|
+
@current_line = 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the next token in the sequence, or +nil+ if there are no more tokens in
|
34
|
+
# the stream.
|
35
|
+
def next
|
36
|
+
return nil if @scanner.eos?
|
37
|
+
@position = @scanner.pos
|
38
|
+
@line = @current_line
|
39
|
+
if @scanner.check(/<\S/)
|
40
|
+
update_current_line(scan_tag)
|
41
|
+
else
|
42
|
+
update_current_line(scan_text)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Treat the text at the current position as a tag, and scan it. Supports
|
49
|
+
# comments, doctype tags, and regular tags, and ignores less-than and
|
50
|
+
# greater-than characters within quoted strings.
|
51
|
+
def scan_tag
|
52
|
+
tag = @scanner.getch
|
53
|
+
if @scanner.scan(/!--/) # comment
|
54
|
+
tag << @scanner.matched
|
55
|
+
tag << (@scanner.scan_until(/--\s*>/) || @scanner.scan_until(/\Z/))
|
56
|
+
elsif @scanner.scan(/!\[CDATA\[/)
|
57
|
+
tag << @scanner.matched
|
58
|
+
tag << (@scanner.scan_until(/\]\]>/) || @scanner.scan_until(/\Z/))
|
59
|
+
elsif @scanner.scan(/!/) # doctype
|
60
|
+
tag << @scanner.matched
|
61
|
+
tag << consume_quoted_regions
|
62
|
+
else
|
63
|
+
tag << consume_quoted_regions
|
64
|
+
end
|
65
|
+
tag
|
66
|
+
end
|
67
|
+
|
68
|
+
# Scan all text up to the next < character and return it.
|
69
|
+
def scan_text
|
70
|
+
"#{@scanner.getch}#{@scanner.scan(/[^<]*/)}"
|
71
|
+
end
|
72
|
+
|
73
|
+
# Counts the number of newlines in the text and updates the current line
|
74
|
+
# accordingly.
|
75
|
+
def update_current_line(text)
|
76
|
+
text.scan(/\r?\n/) { @current_line += 1 }
|
77
|
+
end
|
78
|
+
|
79
|
+
# Skips over quoted strings, so that less-than and greater-than characters
|
80
|
+
# within the strings are ignored.
|
81
|
+
def consume_quoted_regions
|
82
|
+
text = ""
|
83
|
+
loop do
|
84
|
+
match = @scanner.scan_until(/['"<>]/) or break
|
85
|
+
|
86
|
+
delim = @scanner.matched
|
87
|
+
if delim == "<"
|
88
|
+
match = match.chop
|
89
|
+
@scanner.pos -= 1
|
90
|
+
end
|
91
|
+
|
92
|
+
text << match
|
93
|
+
break if delim == "<" || delim == ">"
|
94
|
+
|
95
|
+
# consume the quoted region
|
96
|
+
while match = @scanner.scan_until(/[\\#{delim}]/)
|
97
|
+
text << match
|
98
|
+
break if @scanner.matched == delim
|
99
|
+
break if @scanner.eos?
|
100
|
+
text << @scanner.getch # skip the escaped character
|
101
|
+
end
|
102
|
+
end
|
103
|
+
text
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rails/deprecated_sanitizer/html-scanner/html/node'
|
3
|
+
|
4
|
+
class CDATANodeTest < ActiveSupport::TestCase
|
5
|
+
def setup
|
6
|
+
@node = HTML::CDATA.new(nil, 0, 0, "<p>howdy</p>")
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_to_s
|
10
|
+
assert_equal "<![CDATA[<p>howdy</p>]]>", @node.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_content
|
14
|
+
assert_equal "<p>howdy</p>", @node.content
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_view'
|
3
|
+
require 'action_view/helpers/sanitize_helper'
|
4
|
+
|
5
|
+
class DeprecatedSanitizerTest < ActiveSupport::TestCase
|
6
|
+
def sanitize_helper
|
7
|
+
ActionView::Helpers::SanitizeHelper
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'Action View sanitizer vendor is set to deprecated sanitizer' do
|
11
|
+
assert_equal Rails::DeprecatedSanitizer, sanitize_helper.sanitizer_vendor
|
12
|
+
end
|
13
|
+
|
14
|
+
test 'Action View sanitizer vendor returns constant from HTML module' do
|
15
|
+
assert_equal HTML::LinkSanitizer, sanitize_helper.sanitizer_vendor.link_sanitizer
|
16
|
+
end
|
17
|
+
|
18
|
+
test 'setting allowed tags modifies HTML::WhiteListSanitizers allowed tags' do
|
19
|
+
sanitize_helper.sanitized_allowed_tags = %w(horse)
|
20
|
+
assert_includes HTML::WhiteListSanitizer.allowed_tags, 'horse'
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'setting allowed attributes modifies HTML::WhiteListSanitizers allowed attributes' do
|
24
|
+
attrs = %w(for your health)
|
25
|
+
sanitize_helper.sanitized_allowed_attributes = attrs
|
26
|
+
attrs.each do |attr|
|
27
|
+
assert_includes HTML::WhiteListSanitizer.allowed_attributes, attr
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rails/deprecated_sanitizer/html-scanner'
|
3
|
+
|
4
|
+
class DocumentTest < ActiveSupport::TestCase
|
5
|
+
def test_handle_doctype
|
6
|
+
doc = nil
|
7
|
+
assert_nothing_raised do
|
8
|
+
doc = HTML::Document.new <<-HTML.strip
|
9
|
+
<!DOCTYPE "blah" "blah" "blah">
|
10
|
+
<html>
|
11
|
+
</html>
|
12
|
+
HTML
|
13
|
+
end
|
14
|
+
assert_equal 3, doc.root.children.length
|
15
|
+
assert_equal %{<!DOCTYPE "blah" "blah" "blah">}, doc.root.children[0].content
|
16
|
+
assert_match %r{\s+}m, doc.root.children[1].content
|
17
|
+
assert_equal "html", doc.root.children[2].name
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_find_img
|
21
|
+
doc = HTML::Document.new <<-HTML.strip
|
22
|
+
<html>
|
23
|
+
<body>
|
24
|
+
<p><img src="hello.gif"></p>
|
25
|
+
</body>
|
26
|
+
</html>
|
27
|
+
HTML
|
28
|
+
assert doc.find(:tag=>"img", :attributes=>{"src"=>"hello.gif"})
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_find_all
|
32
|
+
doc = HTML::Document.new <<-HTML.strip
|
33
|
+
<html>
|
34
|
+
<body>
|
35
|
+
<p class="test"><img src="hello.gif"></p>
|
36
|
+
<div class="foo">
|
37
|
+
<p class="test">something</p>
|
38
|
+
<p>here is <em class="test">more</em></p>
|
39
|
+
</div>
|
40
|
+
</body>
|
41
|
+
</html>
|
42
|
+
HTML
|
43
|
+
all = doc.find_all :attributes => { :class => "test" }
|
44
|
+
assert_equal 3, all.length
|
45
|
+
assert_equal [ "p", "p", "em" ], all.map { |n| n.name }
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_find_with_text
|
49
|
+
doc = HTML::Document.new <<-HTML.strip
|
50
|
+
<html>
|
51
|
+
<body>
|
52
|
+
<p>Some text</p>
|
53
|
+
</body>
|
54
|
+
</html>
|
55
|
+
HTML
|
56
|
+
assert doc.find(:content => "Some text")
|
57
|
+
assert doc.find(:tag => "p", :child => { :content => "Some text" })
|
58
|
+
assert doc.find(:tag => "p", :child => "Some text")
|
59
|
+
assert doc.find(:tag => "p", :content => "Some text")
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_parse_xml
|
63
|
+
assert_nothing_raised { HTML::Document.new("<tags><tag/></tags>", true, true) }
|
64
|
+
assert_nothing_raised { HTML::Document.new("<outer><link>something</link></outer>", true, true) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_parse_document
|
68
|
+
doc = HTML::Document.new(<<-HTML)
|
69
|
+
<div>
|
70
|
+
<h2>blah</h2>
|
71
|
+
<table>
|
72
|
+
</table>
|
73
|
+
</div>
|
74
|
+
HTML
|
75
|
+
assert_not_nil doc.find(:tag => "div", :children => { :count => 1, :only => { :tag => "table" } })
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_tag_nesting_nothing_to_s
|
79
|
+
doc = HTML::Document.new("<tag></tag>")
|
80
|
+
assert_equal "<tag></tag>", doc.root.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_tag_nesting_space_to_s
|
84
|
+
doc = HTML::Document.new("<tag> </tag>")
|
85
|
+
assert_equal "<tag> </tag>", doc.root.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_tag_nesting_text_to_s
|
89
|
+
doc = HTML::Document.new("<tag>text</tag>")
|
90
|
+
assert_equal "<tag>text</tag>", doc.root.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_tag_nesting_tag_to_s
|
94
|
+
doc = HTML::Document.new("<tag><nested /></tag>")
|
95
|
+
assert_equal "<tag><nested /></tag>", doc.root.to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_parse_cdata
|
99
|
+
doc = HTML::Document.new(<<-HTML)
|
100
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
101
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
102
|
+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
103
|
+
<head>
|
104
|
+
<title><![CDATA[<br>]]></title>
|
105
|
+
</head>
|
106
|
+
<body>
|
107
|
+
<p>this document has <br> for a title</p>
|
108
|
+
</body>
|
109
|
+
</html>
|
110
|
+
HTML
|
111
|
+
|
112
|
+
assert_nil doc.find(:tag => "title", :descendant => { :tag => "br" })
|
113
|
+
assert doc.find(:tag => "title", :child => "<br>")
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_find_empty_tag
|
117
|
+
doc = HTML::Document.new("<div id='map'></div>")
|
118
|
+
assert_nil doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /./)
|
119
|
+
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /\A\Z/)
|
120
|
+
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => /^$/)
|
121
|
+
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => "")
|
122
|
+
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => nil)
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_parse_invalid_document
|
126
|
+
assert_nothing_raised do
|
127
|
+
HTML::Document.new("<html>
|
128
|
+
<table>
|
129
|
+
<tr>
|
130
|
+
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
|
131
|
+
</tr>
|
132
|
+
</table>
|
133
|
+
</html>")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_invalid_document_raises_exception_when_strict
|
138
|
+
assert_raise RuntimeError do
|
139
|
+
HTML::Document.new("<html>
|
140
|
+
<table>
|
141
|
+
<tr>
|
142
|
+
<td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
|
143
|
+
</tr>
|
144
|
+
</table>
|
145
|
+
</html>", true)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/test/node_test.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rails/deprecated_sanitizer/html-scanner/html/node'
|
3
|
+
|
4
|
+
class NodeTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
class MockNode
|
7
|
+
def initialize(matched, value)
|
8
|
+
@matched = matched
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(conditions)
|
13
|
+
@matched && self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@value.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
@node = HTML::Node.new("parent")
|
23
|
+
@node.children.concat [MockNode.new(false,1), MockNode.new(true,"two"), MockNode.new(false,:three)]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_match
|
27
|
+
assert !@node.match("foo")
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_tag
|
31
|
+
assert !@node.tag?
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_to_s
|
35
|
+
assert_equal "1twothree", @node.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_find
|
39
|
+
assert_equal "two", @node.find('blah').to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_parse_strict
|
43
|
+
s = "<b foo='hello'' bar='baz'>"
|
44
|
+
assert_raise(RuntimeError) { HTML::Node.parse(nil,0,0,s) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_parse_relaxed
|
48
|
+
s = "<b foo='hello'' bar='baz'>"
|
49
|
+
node = nil
|
50
|
+
assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
|
51
|
+
assert node.attributes.has_key?("foo")
|
52
|
+
assert !node.attributes.has_key?("bar")
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_to_s_with_boolean_attrs
|
56
|
+
s = "<b foo bar>"
|
57
|
+
node = HTML::Node.parse(nil,0,0,s)
|
58
|
+
assert node.attributes.has_key?("foo")
|
59
|
+
assert node.attributes.has_key?("bar")
|
60
|
+
assert "<b foo bar>", node.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_parse_with_unclosed_tag
|
64
|
+
s = "<span onmouseover='bang'"
|
65
|
+
node = nil
|
66
|
+
assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
|
67
|
+
assert node.attributes.has_key?("onmouseover")
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_parse_with_valid_cdata_section
|
71
|
+
s = "<![CDATA[<span>contents</span>]]>"
|
72
|
+
node = nil
|
73
|
+
assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
|
74
|
+
assert_kind_of HTML::CDATA, node
|
75
|
+
assert_equal '<span>contents</span>', node.content
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_parse_strict_with_unterminated_cdata_section
|
79
|
+
s = "<![CDATA[neverending..."
|
80
|
+
assert_raise(RuntimeError) { HTML::Node.parse(nil,0,0,s) }
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_parse_relaxed_with_unterminated_cdata_section
|
84
|
+
s = "<![CDATA[neverending..."
|
85
|
+
node = nil
|
86
|
+
assert_nothing_raised { node = HTML::Node.parse(nil,0,0,s,false) }
|
87
|
+
assert_kind_of HTML::CDATA, node
|
88
|
+
assert_equal 'neverending...', node.content
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'rails/deprecated_sanitizer/html-scanner/html/node'
|
3
|
+
|
4
|
+
class TagNodeTest < ActiveSupport::TestCase
|
5
|
+
def test_open_without_attributes
|
6
|
+
node = tag("<tag>")
|
7
|
+
assert_equal "tag", node.name
|
8
|
+
assert_equal Hash.new, node.attributes
|
9
|
+
assert_nil node.closing
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_open_with_attributes
|
13
|
+
node = tag("<TAG1 foo=hey_ho x:bar=\"blah blah\" BAZ='blah blah blah' >")
|
14
|
+
assert_equal "tag1", node.name
|
15
|
+
assert_equal "hey_ho", node["foo"]
|
16
|
+
assert_equal "blah blah", node["x:bar"]
|
17
|
+
assert_equal "blah blah blah", node["baz"]
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_self_closing_without_attributes
|
21
|
+
node = tag("<tag/>")
|
22
|
+
assert_equal "tag", node.name
|
23
|
+
assert_equal Hash.new, node.attributes
|
24
|
+
assert_equal :self, node.closing
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_self_closing_with_attributes
|
28
|
+
node = tag("<tag a=b/>")
|
29
|
+
assert_equal "tag", node.name
|
30
|
+
assert_equal( { "a" => "b" }, node.attributes )
|
31
|
+
assert_equal :self, node.closing
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_closing_without_attributes
|
35
|
+
node = tag("</tag>")
|
36
|
+
assert_equal "tag", node.name
|
37
|
+
assert_nil node.attributes
|
38
|
+
assert_equal :close, node.closing
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_bracket_op_when_no_attributes
|
42
|
+
node = tag("</tag>")
|
43
|
+
assert_nil node["foo"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_bracket_op_when_attributes
|
47
|
+
node = tag("<tag a=b/>")
|
48
|
+
assert_equal "b", node["a"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_attributes_with_escaped_quotes
|
52
|
+
node = tag("<tag a='b\\'c' b=\"bob \\\"float\\\"\">")
|
53
|
+
assert_equal "b\\'c", node["a"]
|
54
|
+
assert_equal "bob \\\"float\\\"", node["b"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_to_s
|
58
|
+
node = tag("<a b=c d='f' g=\"h 'i'\" />")
|
59
|
+
node = node.to_s
|
60
|
+
assert node.include?('a')
|
61
|
+
assert node.include?('b="c"')
|
62
|
+
assert node.include?('d="f"')
|
63
|
+
assert node.include?('g="h')
|
64
|
+
assert node.include?('i')
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_tag
|
68
|
+
assert tag("<tag>").tag?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_match_tag_as_string
|
72
|
+
assert tag("<tag>").match(:tag => "tag")
|
73
|
+
assert !tag("<tag>").match(:tag => "b")
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_match_tag_as_regexp
|
77
|
+
assert tag("<tag>").match(:tag => /t.g/)
|
78
|
+
assert !tag("<tag>").match(:tag => /t[bqs]g/)
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_match_attributes_as_string
|
82
|
+
t = tag("<tag a=something b=else />")
|
83
|
+
assert t.match(:attributes => {"a" => "something"})
|
84
|
+
assert t.match(:attributes => {"b" => "else"})
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_match_attributes_as_regexp
|
88
|
+
t = tag("<tag a=something b=else />")
|
89
|
+
assert t.match(:attributes => {"a" => /^something$/})
|
90
|
+
assert t.match(:attributes => {"b" => /e.*e/})
|
91
|
+
assert t.match(:attributes => {"a" => /me..i/, "b" => /.ls.$/})
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_match_attributes_as_number
|
95
|
+
t = tag("<tag a=15 b=3.1415 />")
|
96
|
+
assert t.match(:attributes => {"a" => 15})
|
97
|
+
assert t.match(:attributes => {"b" => 3.1415})
|
98
|
+
assert t.match(:attributes => {"a" => 15, "b" => 3.1415})
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_match_attributes_exist
|
102
|
+
t = tag("<tag a=15 b=3.1415 />")
|
103
|
+
assert t.match(:attributes => {"a" => true})
|
104
|
+
assert t.match(:attributes => {"b" => true})
|
105
|
+
assert t.match(:attributes => {"a" => true, "b" => true})
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_match_attributes_not_exist
|
109
|
+
t = tag("<tag a=15 b=3.1415 />")
|
110
|
+
assert t.match(:attributes => {"c" => false})
|
111
|
+
assert t.match(:attributes => {"c" => nil})
|
112
|
+
assert t.match(:attributes => {"a" => true, "c" => false})
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_match_parent_success
|
116
|
+
t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
|
117
|
+
assert t.match(:parent => {:tag => "foo", :attributes => {"k" => /v.l/, "j" => false}})
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_match_parent_fail
|
121
|
+
t = tag("<tag a=15 b='hello'>", tag("<foo k='value'>"))
|
122
|
+
assert !t.match(:parent => {:tag => /kafka/})
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_match_child_success
|
126
|
+
t = tag("<tag x:k='something'>")
|
127
|
+
tag("<child v=john a=kelly>", t)
|
128
|
+
tag("<sib m=vaughn v=james>", t)
|
129
|
+
assert t.match(:child => { :tag => "sib", :attributes => {"v" => /j/}})
|
130
|
+
assert t.match(:child => { :attributes => {"a" => "kelly"}})
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_match_child_fail
|
134
|
+
t = tag("<tag x:k='something'>")
|
135
|
+
tag("<child v=john a=kelly>", t)
|
136
|
+
tag("<sib m=vaughn v=james>", t)
|
137
|
+
assert !t.match(:child => { :tag => "sib", :attributes => {"v" => /r/}})
|
138
|
+
assert !t.match(:child => { :attributes => {"v" => false}})
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_match_ancestor_success
|
142
|
+
t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
|
143
|
+
assert t.match(:ancestor => {:tag => "parent", :attributes => {"a" => /ll/}})
|
144
|
+
assert t.match(:ancestor => {:attributes => {"m" => "vaughn"}})
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_match_ancestor_fail
|
148
|
+
t = tag("<tag x:k='something'>", tag("<parent v=john a=kelly>", tag("<grandparent m=vaughn v=james>")))
|
149
|
+
assert !t.match(:ancestor => {:tag => /^parent/, :attributes => {"v" => /m/}})
|
150
|
+
assert !t.match(:ancestor => {:attributes => {"v" => false}})
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_match_descendant_success
|
154
|
+
tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
|
155
|
+
assert t.match(:descendant => {:tag => "child", :attributes => {"a" => /ll/}})
|
156
|
+
assert t.match(:descendant => {:attributes => {"m" => "vaughn"}})
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_match_descendant_fail
|
160
|
+
tag("<grandchild m=vaughn v=james>", tag("<child v=john a=kelly>", t = tag("<tag x:k='something'>")))
|
161
|
+
assert !t.match(:descendant => {:tag => /^child/, :attributes => {"v" => /m/}})
|
162
|
+
assert !t.match(:descendant => {:attributes => {"v" => false}})
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_match_child_count
|
166
|
+
t = tag("<tag x:k='something'>")
|
167
|
+
tag("hello", t)
|
168
|
+
tag("<child v=john a=kelly>", t)
|
169
|
+
tag("<sib m=vaughn v=james>", t)
|
170
|
+
assert t.match(:children => { :count => 2 })
|
171
|
+
assert t.match(:children => { :count => 2..4 })
|
172
|
+
assert t.match(:children => { :less_than => 4 })
|
173
|
+
assert t.match(:children => { :greater_than => 1 })
|
174
|
+
assert !t.match(:children => { :count => 3 })
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_conditions_as_strings
|
178
|
+
t = tag("<tag x:k='something'>")
|
179
|
+
assert t.match("tag" => "tag")
|
180
|
+
assert t.match("attributes" => { "x:k" => "something" })
|
181
|
+
assert !t.match("tag" => "gat")
|
182
|
+
assert !t.match("attributes" => { "x:j" => "something" })
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_attributes_as_symbols
|
186
|
+
t = tag("<child v=john a=kelly>")
|
187
|
+
assert t.match(:attributes => { :v => /oh/ })
|
188
|
+
assert t.match(:attributes => { :a => /ll/ })
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_match_sibling
|
192
|
+
t = tag("<tag x:k='something'>")
|
193
|
+
tag("hello", t)
|
194
|
+
tag("<span a=b>", t)
|
195
|
+
tag("world", t)
|
196
|
+
m = tag("<span k=r>", t)
|
197
|
+
tag("<span m=l>", t)
|
198
|
+
|
199
|
+
assert m.match(:sibling => {:tag => "span", :attributes => {:a => true}})
|
200
|
+
assert m.match(:sibling => {:tag => "span", :attributes => {:m => true}})
|
201
|
+
assert !m.match(:sibling => {:tag => "span", :attributes => {:k => true}})
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_match_sibling_before
|
205
|
+
t = tag("<tag x:k='something'>")
|
206
|
+
tag("hello", t)
|
207
|
+
tag("<span a=b>", t)
|
208
|
+
tag("world", t)
|
209
|
+
m = tag("<span k=r>", t)
|
210
|
+
tag("<span m=l>", t)
|
211
|
+
|
212
|
+
assert m.match(:before => {:tag => "span", :attributes => {:m => true}})
|
213
|
+
assert !m.match(:before => {:tag => "span", :attributes => {:a => true}})
|
214
|
+
assert !m.match(:before => {:tag => "span", :attributes => {:k => true}})
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_match_sibling_after
|
218
|
+
t = tag("<tag x:k='something'>")
|
219
|
+
tag("hello", t)
|
220
|
+
tag("<span a=b>", t)
|
221
|
+
tag("world", t)
|
222
|
+
m = tag("<span k=r>", t)
|
223
|
+
tag("<span m=l>", t)
|
224
|
+
|
225
|
+
assert m.match(:after => {:tag => "span", :attributes => {:a => true}})
|
226
|
+
assert !m.match(:after => {:tag => "span", :attributes => {:m => true}})
|
227
|
+
assert !m.match(:after => {:tag => "span", :attributes => {:k => true}})
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_tag_to_s
|
231
|
+
t = tag("<b x='foo'>")
|
232
|
+
tag("hello", t)
|
233
|
+
tag("<hr />", t)
|
234
|
+
assert_equal %(<b x="foo">hello<hr /></b>), t.to_s
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def tag(content, parent=nil)
|
240
|
+
node = HTML::Node.parse(parent,0,0,content)
|
241
|
+
parent.children << node if parent
|
242
|
+
node
|
243
|
+
end
|
244
|
+
end
|