rbbcode 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,46 @@
1
+ RbbCode is a customizable Ruby library for parsing BB Code.
2
+
3
+ RbbCode validates and cleans input. It supports customizable schemas so you can set rules about what tags are allowed where. The default rules are designed to ensure valid HTML output.
4
+
5
+ Example usage:
6
+
7
+ require 'rubygems'
8
+ require 'rbbcode'
9
+
10
+ bb_code = 'This is [b]bold[/b] text'
11
+ parser = RbbCode::Parser.new
12
+ html = parser.parse(bb_code)
13
+ # => '<p>This is <strong>bold</strong> text</p>'
14
+
15
+ Customizing
16
+ ===========
17
+
18
+ You can customize RbbCode by subclassing HtmlMaker and/or by passing configuration directives to a Schema object.
19
+
20
+ HtmlMaker can be extended by adding methods like this:
21
+
22
+ class MyHtmlMaker < RbbCode::HtmlMaker
23
+ def html_from_TAGNAME_tag(node)
24
+ # ...
25
+ end
26
+ end
27
+
28
+ ...where TAGNAME should be replaced with the name of the tag. The method should accept an RbbCode::TagNode and return HTML as a string. (See tree_maker.rb for the definition of RbbCode::TagNode.) Anytime the parser encounters the specified tag, it will call your method and insert the returned HTML into the output.
29
+
30
+ Now you just have to tell the Parser object to use an instance of your custom subclass instead of the default HtmlMaker:
31
+
32
+ my_html_maker = MyHtmlMaker.new
33
+ parser = RbbCode::Parser.new(:html_maker => my_html_maker)
34
+
35
+ RbbCode removes invalid markup by comparing the input against a Schema object. The Schema is much like a DTD in XML. You can set your own rules and change the default ones by calling configuration methods on a Schema instance. Look at Schema#use_defaults in schema.rb for examples.
36
+
37
+ Normally, RbbCode instantiates Schema behind the scenes, but if you want to customize it, you'll have to instantiate it yourself and pass the instance to the Parser object:
38
+
39
+ schema = RbbCode::Schema.new
40
+ schema.tag('quote').may_not_be_nested # Or whatever other configuration methods you want to call
41
+ parser = RbbCode::Parser.new(:schema => schema)
42
+
43
+ Unicode Support
44
+ ===============
45
+
46
+ UTF-8 compatibility is a high priority for this project. RbbCode aims to be fully compatible with UTF-8, but not with other multibyte encodings. As of the most recent release, UTF-8 support has been tested to a limited extent. It is possible that there are some hidden gotchas. Please report any bugs you may find.
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/node_spec_helper')
3
+
4
+ describe RbbCode::HtmlMaker do
5
+ context '#make_html' do
6
+ def expect_html(expected_html, &block)
7
+ @html_maker.make_html(NodeBuilder.build(&block)).should == expected_html
8
+ end
9
+
10
+ before :each do
11
+ @html_maker = RbbCode::HtmlMaker.new
12
+ end
13
+
14
+ it 'should replace simple BB code tags with HTML tags' do
15
+ expect_html('<p>This is <strong>bold</strong> text</p>') do
16
+ tag('p') do
17
+ text 'This is '
18
+ tag('b') { text 'bold' }
19
+ text ' text'
20
+ end
21
+ end
22
+ end
23
+
24
+ it 'should work for nested tags' do
25
+ expect_html('<p>This is <strong>bold and <u>underlined</u></strong> text</p>') do
26
+ tag('p') do
27
+ text 'This is '
28
+ tag('b') do
29
+ text 'bold and '
30
+ tag('u') { text 'underlined' }
31
+ end
32
+ text ' text'
33
+ end
34
+ end
35
+ end
36
+
37
+ it 'should not allow JavaScript in URLs' do
38
+ urls = {
39
+ 'javascript:alert("foo");' => 'http://javascript%3Aalert("foo");',
40
+ 'j a v a script:alert("foo");' => 'http://j+a+v+a+script%3Aalert("foo");',
41
+ ' javascript:alert("foo");' => 'http://+javascript%3Aalert("foo");',
42
+ 'JavaScript:alert("foo");' => 'http://JavaScript%3Aalert("foo");' ,
43
+ "java\nscript:alert(\"foo\");" => 'http://java%0Ascript%3Aalert("foo");',
44
+ "java\rscript:alert(\"foo\");" => 'http://java%0Dscript%3Aalert("foo");'
45
+ }
46
+
47
+ # url tag
48
+ urls.each do |evil_url, clean_url|
49
+ expect_html("<p><a href=\"#{clean_url}\">foo</a></p>") do
50
+ tag('p') do
51
+ tag('url', evil_url) do
52
+ text 'foo'
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # img tag
59
+ urls.each do |evil_url, clean_url|
60
+ expect_html("<p><img src=\"#{clean_url}\" alt=\"\"/></p>") do
61
+ tag('p') do
62
+ tag('img') do
63
+ text evil_url
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,114 @@
1
+ module RbbCode
2
+ class RootNode
3
+ def == (other_node)
4
+ self.class == other_node.class and self.children == other_node.children
5
+ end
6
+
7
+ def print_tree(indent = 0)
8
+ output = ''
9
+ indent.times { output << " " }
10
+ output << 'ROOT'
11
+ children.each do |child|
12
+ output << "\n" << child.print_tree(indent + 1)
13
+ end
14
+ output << "\n/ROOT"
15
+ output
16
+ end
17
+ end
18
+
19
+ class TagNode
20
+ def == (other_node)
21
+ self.class == other_node.class and self.tag_name == other_node.tag_name and self.value == other_node.value and self.children == other_node.children
22
+ end
23
+
24
+ def print_tree(indent = 0)
25
+ output = ''
26
+ indent.times { output << " " }
27
+ if value.nil?
28
+ output << "[#{tag_name}]"
29
+ else
30
+ output << "[#{tag_name}=#{value}]"
31
+ end
32
+ children.each do |child|
33
+ output << "\n" << child.print_tree(indent + 1)
34
+ end
35
+ output << "\n"
36
+ indent.times { output << " " }
37
+ output << "[/#{tag_name}]"
38
+ output
39
+ end
40
+ end
41
+
42
+ class TextNode
43
+ def == (other_node)
44
+ self.class == other_node.class and self.text == other_node.text
45
+ end
46
+
47
+ def print_tree(indent = 0)
48
+ output = ''
49
+ indent.times { output << " " }
50
+ output << '"' << text << '"'
51
+ end
52
+ end
53
+ end
54
+
55
+ class NodeBuilder
56
+ include RbbCode
57
+
58
+ def self.build(&block)
59
+ builder = new
60
+ builder.instance_eval(&block)
61
+ builder.root
62
+ end
63
+
64
+ attr_reader :root
65
+
66
+ protected
67
+
68
+ def << (node)
69
+ @current_parent.children << node
70
+ end
71
+
72
+ def initialize
73
+ @root = RootNode.new
74
+ @current_parent = @root
75
+ end
76
+
77
+ def text(contents, &block)
78
+ self << TextNode.new(@current_parent, contents)
79
+ end
80
+
81
+ def tag(tag_name, value = nil, &block)
82
+ tag_node = TagNode.new(@current_parent, tag_name, value)
83
+ self << tag_node
84
+ original_parent = @current_parent
85
+ @current_parent = tag_node
86
+ instance_eval(&block)
87
+ @current_parent = original_parent
88
+ end
89
+ end
90
+
91
+ module NodeMatchers
92
+ class MatchNode
93
+ def initialize(expected_tree)
94
+ @expected_tree = expected_tree
95
+ end
96
+
97
+ def matches?(target)
98
+ @target = target
99
+ @target == @expected_tree
100
+ end
101
+
102
+ def failure_message
103
+ "Expected:\n\n#{@expected_tree.print_tree}\n\nbut got:\n\n#{@target.print_tree}"
104
+ end
105
+
106
+ def negative_failure_message
107
+ "Expected anything other than:\n\n#{@expected_tree.print_tree}"
108
+ end
109
+ end
110
+
111
+ def match_node(expected_node)
112
+ MatchNode.new(expected_node)
113
+ end
114
+ end
@@ -0,0 +1,99 @@
1
+ $KCODE = 'u'
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
4
+
5
+ describe RbbCode::Parser do
6
+ context '#parse' do
7
+ before :each do
8
+ @parser = RbbCode::Parser.new
9
+ end
10
+
11
+ it 'should create paragraphs and line breaks' do
12
+ bb_code = "This is one paragraph.\n\nThis is another paragraph."
13
+ @parser.parse(bb_code).should == '<p>This is one paragraph.</p><p>This is another paragraph.</p>'
14
+ bb_code = "This is one line.\nThis is another line."
15
+ @parser.parse(bb_code).should == '<p>This is one line.<br/>This is another line.</p>'
16
+ end
17
+
18
+ it 'should turn [b] to <strong>' do
19
+ @parser.parse('This is [b]bold[/b] text').should == '<p>This is <strong>bold</strong> text</p>'
20
+ end
21
+
22
+ it 'should turn [i] to <em> by default' do
23
+ @parser.parse('This is [i]italic[/i] text').should == '<p>This is <em>italic</em> text</p>'
24
+ end
25
+
26
+ it 'should turn [u] to <u>' do
27
+ @parser.parse('This is [u]underlined[/u] text').should == '<p>This is <u>underlined</u> text</p>'
28
+ end
29
+
30
+ it 'should turn [url]http://google.com[/url] to a link' do
31
+ @parser.parse('Visit [url]http://google.com[/url] now').should == '<p>Visit <a href="http://google.com">http://google.com</a> now</p>'
32
+ end
33
+
34
+ it 'should turn [url=http://google.com]Google[/url] to a link' do
35
+ @parser.parse('Visit [url=http://google.com]Google[/url] now').should == '<p>Visit <a href="http://google.com">Google</a> now</p>'
36
+ end
37
+
38
+ it 'should turn [img] to <img>' do
39
+ @parser.parse('[img]http://example.com/image.jpg[/img]').should == '<p><img src="http://example.com/image.jpg" alt=""/></p>'
40
+ end
41
+
42
+ it 'should turn [code] to <code>' do
43
+ @parser.parse('Too bad [code]method_missing[/code] is rarely useful').should == '<p>Too bad <code>method_missing</code> is rarely useful</p>'
44
+ end
45
+
46
+ it 'should parse nested tags' do
47
+ @parser.parse('[b][i]This is bold-italic[/i][/b]').should == '<p><strong><em>This is bold-italic</em></strong></p>'
48
+ end
49
+
50
+ it 'should not put <p> tags around <ul> tags' do
51
+ @parser.parse("Text.\n\n[list]\n[*]Foo[/*]\n[*]Bar[/*]\n[/list]\n\nMore text.").should == '<p>Text.</p><ul><li>Foo</li><li>Bar</li></ul><p>More text.</p>'
52
+ end
53
+
54
+ it 'should ignore forbidden or unrecognized tags' do
55
+ @parser.parse('There is [foo]no such thing[/foo] as a foo tag').should == '<p>There is no such thing as a foo tag</p>'
56
+ end
57
+
58
+ it 'should recover gracefully from malformed or improperly matched tags' do
59
+ @parser.parse('This [i/]tag[/i] is malformed').should == '<p>This [i/]tag is malformed</p>'
60
+ @parser.parse('This [i]]tag[/i] is malformed').should == '<p>This <em>]tag</em> is malformed</p>'
61
+ @parser.parse('This [i]tag[[/i] is malformed').should == '<p>This <em>tag[</em> is malformed</p>'
62
+ @parser.parse('This [i]tag[//i] is malformed').should == '<p>This <em>tag[//i] is malformed</em></p>'
63
+ @parser.parse('This [[i]tag[/i] is malformed').should == '<p>This [<em>tag</em> is malformed</p>'
64
+ @parser.parse('This [i]tag[/i]] is malformed').should == '<p>This <em>tag</em>] is malformed</p>'
65
+ @parser.parse('This [i]i tag[i] is not properly matched').should == '<p>This <em>i tag is not properly matched</em></p>'
66
+ @parser.parse('This i tag[/i] is not properly matched').should == '<p>This i tag is not properly matched</p>'
67
+ end
68
+
69
+ it 'should escape < and >' do
70
+ @parser.parse('This is [i]italic[/i], but this it not <i>italic</i>.').should == '<p>This is <em>italic</em>, but this it not &lt;i&gt;italic&lt;/i&gt;.</p>'
71
+ end
72
+
73
+ it 'should work when the string begins with a tag' do
74
+ @parser.parse('[b]This is bold[/b]').should == '<p><strong>This is bold</strong></p>'
75
+ end
76
+
77
+ it 'should handle UTF8' do
78
+ @parser.parse("Here's some UTF-8: [i]א[/i]. And here's some ASCII text.").should == "<p>Here's some UTF-8: <em>א</em>. And here's some ASCII text.</p>"
79
+ end
80
+
81
+ # Bugs reported and fixed:
82
+
83
+ it 'should not leave an open <em> tag when parsing "foo [i][/i] bar"' do
84
+ # Thanks to Vizakenjack for finding this. It creates an empty <em> tag. Browsers don't like this, so we need to replace it.
85
+ @parser.parse('foo [i][/i] bar').should match(/<p>foo +bar<\/p>/)
86
+ end
87
+
88
+ it 'should not raise when parsing "Are you a real phan yet?\r\n\r\n[ ] Yes\r\n[X] No"' do
89
+ # Thanks to sblackstone for finding this.
90
+ @parser.parse("Are you a real phan yet?\r\n\r\n[ ] Yes\r\n[X] No")
91
+ end
92
+
93
+ it 'should support images inside links' do
94
+ # Thanks to Vizakenjack for finding this.
95
+ @parser.parse('[url=http://www.google.com][img]http://www.123.com/123.png[/img][/url]').should ==
96
+ '<p><a href="http://www.google.com"><img src="http://www.123.com/123.png" alt=""/></a></p>'
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,98 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe RbbCode::Schema do
4
+ before :each do
5
+ @schema = RbbCode::Schema.new
6
+ @schema.clear
7
+ @schema.allow_tags(*RbbCode::DEFAULT_ALLOWED_TAGS)
8
+ end
9
+
10
+ it 'should allow the default tags at the top level' do
11
+ schema = RbbCode::Schema.new
12
+ [
13
+ 'b',
14
+ 'i',
15
+ 'u',
16
+ 'url',
17
+ 'img',
18
+ 'code',
19
+ 'quote',
20
+ 'list'
21
+ ].each do |tag|
22
+ schema.tag(tag).valid_in_context?().should == true
23
+ end
24
+ end
25
+
26
+ it 'should not allow unknown tags' do
27
+ @schema.tag('foo').valid_in_context?().should == false
28
+ end
29
+
30
+ it 'should return a new SchemaTag object when tag is called' do
31
+ @schema.tag('b').should be_a(RbbCode::SchemaTag)
32
+ end
33
+
34
+ it 'should not allow nesting a tag when may_not_be_nested is called on it' do
35
+ @schema.tag('b').may_not_be_nested
36
+ @schema.tag('b').valid_in_context?('b').should == false
37
+ end
38
+
39
+ it 'should allow nesting a tag when may_be_nested is called on it' do
40
+ @schema.tag('b').may_not_be_nested
41
+ @schema.tag('b').may_be_nested
42
+ @schema.tag('b').valid_in_context?('b').should == true
43
+ end
44
+
45
+ it 'should not allow a tag to descend from another when forbidden by may_not_descend_from' do
46
+ @schema.tag('b').may_not_descend_from('u')
47
+ @schema.tag('b').valid_in_context?('u').should == false
48
+ end
49
+
50
+ it 'should allow a tag to descend from another when permitted by may_descend_from' do
51
+ @schema.tag('b').may_not_descend_from('u')
52
+ @schema.tag('b').may_descend_from('u')
53
+ @schema.tag('b').valid_in_context?('u').should == true
54
+ end
55
+
56
+ it 'should not allow a tag to descend from anything other than the tags specified in must_be_child_of' do
57
+ @schema.tag('b').must_be_child_of('u', 'quote')
58
+ @schema.tag('b').valid_in_context?('i').should == false
59
+ @schema.tag('b').valid_in_context?('u').should == true
60
+ @schema.tag('b').valid_in_context?('quote').should == true
61
+ end
62
+
63
+ it 'should allow a tag to descend from the one specified in must_be_child_of' do
64
+ @schema.tag('b').may_not_descend_from('u')
65
+ @schema.tag('b').must_be_child_of('u')
66
+ @schema.tag('b').valid_in_context?('u').should == true
67
+ end
68
+
69
+ it 'should not require a tag to be a child of another when need_not_be_child_of is called' do
70
+ @schema.tag('b').must_be_child_of('u')
71
+ @schema.tag('b').need_not_be_child_of('u')
72
+ @schema.tag('b').valid_in_context?('i').should == true
73
+ end
74
+
75
+ it 'should allow only the specified tag as a child when may_only_be_parent_of is called' do
76
+ @schema.tag('list').may_only_be_parent_of('*')
77
+ @schema.tag('*').valid_in_context?('list').should == true
78
+ @schema.tag('u').valid_in_context?('list').should == false
79
+ @schema.tag('u').valid_in_context?('*', 'list').should == true
80
+ end
81
+
82
+ it 'should not allow text inside a tag when may_not_contain_text is called' do
83
+ @schema.tag('list').may_not_contain_text
84
+ @schema.text.valid_in_context?('list').should == false
85
+ end
86
+
87
+ it 'should allow text inside a tag when may_contain_text is called' do
88
+ @schema.tag('list').may_not_contain_text
89
+ @schema.tag('list').may_contain_text
90
+ @schema.text.valid_in_context?('list').should == true
91
+ end
92
+
93
+ it 'should not allow text or children when must_be_empty is called' do
94
+ @schema.tag('br').must_be_empty
95
+ @schema.text.valid_in_context?('br').should == false
96
+ @schema.tag('b').valid_in_context?('br').should == false
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'spec'
4
+
5
+ def puts(foo)
6
+ raise 'puts called'
7
+ end
8
+
9
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/rbbcode')
@@ -0,0 +1,107 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/node_spec_helper')
3
+ require 'pp'
4
+
5
+ describe RbbCode::TreeMaker do
6
+ include NodeMatchers
7
+
8
+ context '#make_tree' do
9
+ def expect_tree(str, &block)
10
+ expected = NodeBuilder.build(&block)
11
+ @tree_maker.make_tree(str).should match_node(expected)
12
+ end
13
+
14
+ before :each do
15
+ @schema = RbbCode::Schema.new
16
+ @tree_maker = RbbCode::TreeMaker.new(@schema)
17
+ end
18
+
19
+ it 'should make a tree from a string with one tag' do
20
+ str = 'This is [b]bold[/b] text'
21
+
22
+ expect_tree(str) do
23
+ tag('p') do
24
+ text 'This is '
25
+ tag('b') { text 'bold' }
26
+ text ' text'
27
+ end
28
+ end
29
+ end
30
+
31
+ it 'should ignore tags that are invalid in their context' do
32
+ @schema.tag('u').may_not_descend_from('b')
33
+
34
+ str = 'This is [b]bold and [u]underlined[/u][/b] text'
35
+
36
+ expect_tree(str) do
37
+ tag('p') do
38
+ text 'This is '
39
+ tag('b') do
40
+ text 'bold and '
41
+ text 'underlined'
42
+ end
43
+ text ' text'
44
+ end
45
+ end
46
+ end
47
+
48
+ it 'should create paragraph tags' do
49
+ str = "This is a paragraph.\n\nThis is another."
50
+
51
+ expect_tree(str) do
52
+ tag('p') do
53
+ text 'This is a paragraph.'
54
+ end
55
+ tag('p') do
56
+ text 'This is another.'
57
+ end
58
+ end
59
+ end
60
+
61
+ it 'should not put block-level elements inside paragraph tags' do
62
+ str = "This is a list:\n\n[list]\n\n[*]Foo[/i]\n\n[/list]\n\nwith some text after it"
63
+
64
+ expect_tree(str) do
65
+ tag('p') do
66
+ text 'This is a list:'
67
+ end
68
+ tag('list') do
69
+ tag('*') { text 'Foo' }
70
+ end
71
+ tag('p') do
72
+ text 'with some text after it'
73
+ end
74
+ end
75
+ end
76
+
77
+ it 'should not insert br tags in the midst of block-level elements' do
78
+ str = "List:\n[list]\n[*]Foo[/*]\n[*]Bar[/*]\n[/list]\nText after list"
79
+
80
+ expect_tree(str) do
81
+ tag('p') do
82
+ text 'List:'
83
+ end
84
+ tag('list') do
85
+ tag('*') { text 'Foo' }
86
+ tag('*') { text 'Bar' }
87
+ end
88
+ tag('p') do
89
+ text 'Text after list'
90
+ end
91
+ end
92
+ end
93
+
94
+ it 'should store tag values' do
95
+ str = 'This is a [url=http://google.com]link[/url]'
96
+
97
+ expect_tree(str) do
98
+ tag('p') do
99
+ text 'This is a '
100
+ tag('url', 'http://google.com') do
101
+ text 'link'
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbbcode
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Jarrett Colby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-09 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: RbbCode is a customizable Ruby library for parsing BB Code. RbbCode validates and cleans input. It supports customizable schemas so you can set rules about what tags are allowed where. The default rules are designed to ensure valid HTML output.
17
+ email: jarrett@jarrettcolby.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - README
26
+ has_rdoc: true
27
+ homepage: http://github.com/jarrett/rbbcode
28
+ licenses: []
29
+
30
+ post_install_message:
31
+ rdoc_options:
32
+ - --charset=UTF-8
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.3.5
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: Ruby BB Code parser
54
+ test_files:
55
+ - spec/html_maker_spec.rb
56
+ - spec/node_spec_helper.rb
57
+ - spec/parser_spec.rb
58
+ - spec/schema_spec.rb
59
+ - spec/spec_helper.rb
60
+ - spec/tree_maker_spec.rb