rails-deprecated_sanitizer-no-registration 1.0.4
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 +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE +22 -0
- data/README.md +17 -0
- data/lib/rails-deprecated_sanitizer.rb +1 -0
- data/lib/rails/deprecated_sanitizer.rb +25 -0
- data/lib/rails/deprecated_sanitizer/html-scanner.rb +21 -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/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 +13 -0
- data/test/text_node_test.rb +51 -0
- data/test/tokenizer_test.rb +132 -0
- metadata +112 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module HTMLDeprecated #: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 = HTMLDeprecated::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,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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 { HTMLDeprecated::Document.new("<tags><tag/></tags>", true, true) }
|
64
|
+
assert_nothing_raised { HTMLDeprecated::Document.new("<outer><link>something</link></outer>", true, true) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_parse_document
|
68
|
+
doc = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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
|
+
HTMLDeprecated::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
|
+
HTMLDeprecated::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 = HTMLDeprecated::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) { HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::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 = HTMLDeprecated::Node.parse(nil, 0, 0, s, false) }
|
74
|
+
assert_kind_of HTMLDeprecated::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) { HTMLDeprecated::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 = HTMLDeprecated::Node.parse(nil, 0, 0, s, false) }
|
87
|
+
assert_kind_of HTMLDeprecated::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 = HTMLDeprecated::Node.parse(parent, 0, 0, content)
|
241
|
+
parent.children << node if parent
|
242
|
+
node
|
243
|
+
end
|
244
|
+
end
|