th-bbcode 0.4.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.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/README.md +78 -0
- data/Rakefile +3 -0
- data/bbcode.gemspec +25 -0
- data/lib/bbcode/base.rb +22 -0
- data/lib/bbcode/element.rb +37 -0
- data/lib/bbcode/handler.rb +71 -0
- data/lib/bbcode/handler_element.rb +27 -0
- data/lib/bbcode/helpers.rb +7 -0
- data/lib/bbcode/html_handler.rb +24 -0
- data/lib/bbcode/node_list.rb +23 -0
- data/lib/bbcode/parser.rb +50 -0
- data/lib/bbcode/tokenizer.rb +74 -0
- data/lib/bbcode/version.rb +3 -0
- data/lib/bbcode.rb +11 -0
- data/spec/lib/bbcode/base_spec.rb +8 -0
- data/spec/lib/bbcode/handler_spec.rb +77 -0
- data/spec/lib/bbcode/helpers_spec.rb +40 -0
- data/spec/lib/bbcode/html_handler_spec.rb +77 -0
- data/spec/lib/bbcode/parser_spec.rb +82 -0
- data/spec/lib/bbcode/tokenizer_spec.rb +94 -0
- data/spec/spec_helper.rb +13 -0
- metadata +120 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
A BBcode parser designed to be used with Ruby on Rails
|
2
|
+
======================================================
|
3
|
+
A bbcode parser gem you can include in your rails app to parse bbcode-formatted
|
4
|
+
strings to HTML or any other format you like.
|
5
|
+
|
6
|
+
The bbcode gem consists of 4 parts:
|
7
|
+
|
8
|
+
- The `Tokenizer`-class, which converts the bbcode-formatted string to a stream
|
9
|
+
of tokens.
|
10
|
+
- The `Parser`-class, which attempts to pair bbcode tags to bbcode elements.
|
11
|
+
- The `Handler`-class, which converts bbcode elements anyway you like.
|
12
|
+
- The `Helpers`-module, which adds a method to String, allowing you to convert
|
13
|
+
bbcode-formatted strings with a registered handler.
|
14
|
+
|
15
|
+
Additionally, a `HtmlHandler` class is available. This class is a Handler
|
16
|
+
designed to convert bbcode elements to HTML more easily.
|
17
|
+
|
18
|
+
Installation:
|
19
|
+
-------------
|
20
|
+
Add the gem to the gemfile of your project.
|
21
|
+
(todo: add examples)
|
22
|
+
|
23
|
+
Usage:
|
24
|
+
------
|
25
|
+
Create and register a handler. In this example, I'm creating a HtmlHandler and
|
26
|
+
I'm going to register it as `:html`.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Bbcode::Base.register_handler :html, Bbcode::HtmlHandler.new(
|
30
|
+
:b => :strong,
|
31
|
+
:i => :em,
|
32
|
+
:url => [ :a, { :href => "%{0}" } ],
|
33
|
+
:txt => ->(element){ "#{element.content.source}" },
|
34
|
+
:img => ->(element){ %(<img src="#{CGI.escapeHTML(element.content.source)}">) },
|
35
|
+
:color => [ :span, { :style => "color: %{0};" } ]
|
36
|
+
)
|
37
|
+
```
|
38
|
+
|
39
|
+
That's it! You can now parse any string as bbcode and convert it to html with
|
40
|
+
the `:html`-handler like this:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
"[b]Hello, bold world![/]".as_bbcode.to :html
|
44
|
+
# => <strong>Hello, bold world!</strong>
|
45
|
+
```
|
46
|
+
|
47
|
+
If you're using this gem in a rails project, I would recommend registering your
|
48
|
+
handlers in an initializer.
|
49
|
+
|
50
|
+
See examples in `spec/` folder for detailed examples of usage.
|
51
|
+
|
52
|
+
Features:
|
53
|
+
---------
|
54
|
+
* Parsing regular bbcode tags like `[b]` and `[/b]`.
|
55
|
+
* Parsing anonymous closing bbcode tags like `[/]`.
|
56
|
+
* Parsing bbcode tags with arguments like `[a=foo, bar]`, `[a foo=1 bar:2]`,
|
57
|
+
`[a=foo, bar bar:1 foo=2]` and `[a="foo" b='bar']`.
|
58
|
+
* Parsing nested bbcode elements like `[b]bold[i]and italic[/]only bold[/]`,
|
59
|
+
which might result to `<b>bold<i>and italic</i>only bold</b>`.
|
60
|
+
* Parsing incorrectly nested bbcode elements like `[b]bold[i]and italic[/b]only
|
61
|
+
italic[/]`, which might result to `<b>bold<i>and italic</i></b><i>only
|
62
|
+
italic</i>`.
|
63
|
+
|
64
|
+
Todo:
|
65
|
+
-----
|
66
|
+
* An easier way to handle text around bbcode tags to, for example, add smileys
|
67
|
+
and wrap hyperlinks to URLs. Currently, the only way to achieve this is by
|
68
|
+
adding a `:"#text"`-handler to your handler and adding the functionality
|
69
|
+
yourself.
|
70
|
+
* An easier way to include the content, source or content-source in the
|
71
|
+
`HtmlHandler`-class.
|
72
|
+
* Review handleability of element interrupts.
|
73
|
+
* Review regular expression matching bbcode tags to allow tags having names
|
74
|
+
containing characters other than `A-Z`, `0-9`, `_` and `-`, possibly based on
|
75
|
+
the current registered tags.
|
76
|
+
* Add CDATA-like feature for bbcode tags to allow tags to be ignored within
|
77
|
+
certain elements. Useful for `[code]`-tags.
|
78
|
+
* Add a default handler with the most common bbcode tags.
|
data/Rakefile
ADDED
data/bbcode.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "bbcode/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "th-bbcode"
|
7
|
+
s.version = Bbcode::VERSION
|
8
|
+
s.authors = ["Toby Hinloopen"]
|
9
|
+
s.email = ["toby@kutcomputers.nl"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{BBCode parser}
|
12
|
+
s.description = %q{Gem for parsing bbcode-formatted strings to HTML or any other formatting you like (or don't like).}
|
13
|
+
|
14
|
+
s.rubyforge_project = "bbcode"
|
15
|
+
|
16
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
17
|
+
s.add_dependency "activesupport", "~> 3.0.9"
|
18
|
+
s.add_dependency "actionpack", "~> 3.0.9"
|
19
|
+
s.add_dependency "i18n", "~> 0.5.0"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
23
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
24
|
+
s.require_paths = ["lib"]
|
25
|
+
end
|
data/lib/bbcode/base.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Bbcode
|
2
|
+
class Base
|
3
|
+
@@handlers = {}.with_indifferent_access
|
4
|
+
|
5
|
+
def initialize( string )
|
6
|
+
@string = string
|
7
|
+
end
|
8
|
+
|
9
|
+
def to( handler )
|
10
|
+
handler = @@handlers[handler]
|
11
|
+
raise "Handler #{handler} isn't registered" if handler.blank?
|
12
|
+
Parser.new(Tokenizer.new).parse @string, handler
|
13
|
+
result = handler.get_document.content.to_s
|
14
|
+
handler.clear
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.register_handler( name, handler )
|
19
|
+
@@handlers[name] = handler
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "bbcode/node_list"
|
2
|
+
|
3
|
+
module Bbcode
|
4
|
+
class Element
|
5
|
+
def initialize( handler_element )
|
6
|
+
@handler_element = handler_element
|
7
|
+
end
|
8
|
+
|
9
|
+
def tagname
|
10
|
+
@handler_element.tagname
|
11
|
+
end
|
12
|
+
|
13
|
+
def attributes
|
14
|
+
@handler_element.attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def []( key )
|
18
|
+
@handler_element.attributes[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def source
|
22
|
+
@handler_element.source
|
23
|
+
end
|
24
|
+
|
25
|
+
def source_wraps_content( content = nil )
|
26
|
+
"#{@handler_element.start_source}#{content || self.content}#{@handler_element.end_source}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def content
|
30
|
+
NodeList.new @handler_element.handler, @handler_element.childs.map{ |child_handler_element| child_handler_element.is_a?(String) ? child_handler_element : child_handler_element.element }
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
@handler_element.handler.get_element_handler(tagname).call(self)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "bbcode/handler_element"
|
2
|
+
|
3
|
+
module Bbcode
|
4
|
+
class Handler
|
5
|
+
attr_accessor :element_handlers
|
6
|
+
|
7
|
+
def initialize( element_handlers = nil )
|
8
|
+
@element_handlers = {}.with_indifferent_access
|
9
|
+
@handler_element_stack = []
|
10
|
+
self.clear
|
11
|
+
register_element_handlers element_handlers unless element_handlers.blank?
|
12
|
+
@interruption_stack = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def register_element_handlers( element_handlers )
|
16
|
+
element_handlers.each do |k, v|
|
17
|
+
register_element_handler k, v
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def register_element_handler( name, handler )
|
22
|
+
@element_handlers[name] = handler
|
23
|
+
end
|
24
|
+
|
25
|
+
def start_element( tagname, attributes, source )
|
26
|
+
handler_element = HandlerElement.new self, tagname, attributes, source
|
27
|
+
current_handler_element.childs << handler_element
|
28
|
+
@handler_element_stack << handler_element
|
29
|
+
end
|
30
|
+
|
31
|
+
def interrupt_element( tagname )
|
32
|
+
# TODO: Add better way to handle interrupts
|
33
|
+
@interruption_stack << current_handler_element
|
34
|
+
end_element tagname, ""
|
35
|
+
end
|
36
|
+
|
37
|
+
def continue_element( tagname )
|
38
|
+
# TODO: Add better way to handle interrupts
|
39
|
+
handler_element = @interruption_stack.pop
|
40
|
+
start_element handler_element.tagname, handler_element.attributes, ""
|
41
|
+
end
|
42
|
+
|
43
|
+
def end_element( tagname, source )
|
44
|
+
raise "Unexpected end of #{tagname.inspect}, expected #{current_handler_element.tagname.inspect}" if tagname != current_handler_element.tagname
|
45
|
+
current_handler_element.end_element source
|
46
|
+
@handler_element_stack.pop
|
47
|
+
end
|
48
|
+
|
49
|
+
def text( text )
|
50
|
+
current_handler_element.childs << text
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_document
|
54
|
+
@handler_element_stack.first.element
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear
|
58
|
+
@handler_element_stack.clear
|
59
|
+
@handler_element_stack << HandlerElement.new( self, :"#document", {}, "" )
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_element_handler( name )
|
63
|
+
@element_handlers[name] || ->(element){ element.is_a?(String) ? element : element.source_wraps_content }
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
def current_handler_element
|
68
|
+
@handler_element_stack.last
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "bbcode/element"
|
2
|
+
|
3
|
+
module Bbcode
|
4
|
+
# Private data source for an Element updated by the Handler
|
5
|
+
class HandlerElement
|
6
|
+
attr_accessor :childs
|
7
|
+
attr_reader :element, :tagname, :attributes, :handler, :start_source, :end_source
|
8
|
+
|
9
|
+
def initialize( handler, tagname, attributes, start_source )
|
10
|
+
@handler = handler
|
11
|
+
@tagname = tagname
|
12
|
+
@attributes = attributes
|
13
|
+
@start_source = start_source
|
14
|
+
@end_source = nil
|
15
|
+
@childs = []
|
16
|
+
@element = Element.new(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_element( source )
|
20
|
+
@end_source = source
|
21
|
+
end
|
22
|
+
|
23
|
+
def source
|
24
|
+
"#{@start_source}#{@element.content.source}#{@end_source}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "bbcode/handler"
|
2
|
+
|
3
|
+
module Bbcode
|
4
|
+
class HtmlHandler < Handler
|
5
|
+
def initialize( element_handlers = nil )
|
6
|
+
super :"#text" => ->(text){ CGI.escapeHTML(text) }
|
7
|
+
register_element_handlers element_handlers unless element_handlers.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
def register_element_handler( name, handler )
|
11
|
+
unless handler.is_a?(Proc)
|
12
|
+
args = *handler
|
13
|
+
target_tagname = args.shift
|
14
|
+
attributes = args.first
|
15
|
+
handler = ->(element){
|
16
|
+
content_tag(target_tagname, element.content, !attributes ? {} : Hash[attributes.map{ |k, v|
|
17
|
+
[k, v.gsub(/%{[^}]+}/) { |m| CGI.escapeHTML element[m[3] == ":" ? m[3...-1].to_sym : m[2...-1].to_i] }]
|
18
|
+
}], false)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
super name, handler
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Bbcode
|
2
|
+
# An array with Elements and strings
|
3
|
+
class NodeList < Array
|
4
|
+
def initialize( handler, nodes = [] )
|
5
|
+
super nodes
|
6
|
+
@handler = handler
|
7
|
+
end
|
8
|
+
|
9
|
+
def source
|
10
|
+
self.map{ |element| element.is_a?(String) ? element : element.source }.join
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
self.map{ |element|
|
15
|
+
@handler.get_element_handler(element.is_a?(String) ? :"#text" : element.tagname).call(element)
|
16
|
+
}.join
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_handler( handler )
|
20
|
+
NodeList.new handler, self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Bbcode
|
2
|
+
# Attempts to pair a stream of tokens created by a tokenizer
|
3
|
+
class Parser
|
4
|
+
attr_accessor :tokenizer
|
5
|
+
|
6
|
+
def initialize( tokenizer = nil )
|
7
|
+
@tags_stack = []
|
8
|
+
self.tokenizer = tokenizer unless tokenizer.blank?
|
9
|
+
end
|
10
|
+
|
11
|
+
def tokenizer=( tokenizer )
|
12
|
+
raise "#{tokenizer.inspect} appears not to be a valid tokenizer for it does not respond to :tokenize" unless tokenizer.respond_to?(:tokenize)
|
13
|
+
@tokenizer = tokenizer
|
14
|
+
end
|
15
|
+
|
16
|
+
def text( text )
|
17
|
+
@handler.send :text, text
|
18
|
+
end
|
19
|
+
|
20
|
+
def start_element( tagname, attributes, source )
|
21
|
+
@tags_stack << tagname
|
22
|
+
@handler.send :start_element, tagname, attributes, source
|
23
|
+
end
|
24
|
+
|
25
|
+
def end_element( tagname, source )
|
26
|
+
return @tags_stack.last.blank? ? self.text(source) : end_element(@tags_stack.last, source) if tagname.blank?
|
27
|
+
return self.text(source) unless @tags_stack.include?(tagname)
|
28
|
+
|
29
|
+
@interruption_stack = []
|
30
|
+
while @tags_stack.last != tagname do
|
31
|
+
@interruption_stack << @tags_stack.last
|
32
|
+
@handler.send :interrupt_element, @tags_stack.pop
|
33
|
+
end
|
34
|
+
|
35
|
+
@handler.send :end_element, @tags_stack.pop, source
|
36
|
+
|
37
|
+
while !@interruption_stack.empty? do
|
38
|
+
@tags_stack << @interruption_stack.last
|
39
|
+
@handler.send :continue_element, @interruption_stack.pop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse( document, handler )
|
44
|
+
@handler = handler
|
45
|
+
@tokenizer.tokenize document do |*args|
|
46
|
+
self.send *args if [:start_element, :end_element, :text].include?(args.first)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bbcode
|
2
|
+
# Scans a string and converts it to a stream of bbcode tokens.
|
3
|
+
class Tokenizer
|
4
|
+
BBCODE_TAG_PATTERN = /\[(\/?)([a-z0-9_-]*)(\s*=?(?:(?:\s*(?:(?:[a-z0-9_-]+)|(?<=\=))\s*[:=]\s*)?(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]*(?:\\[\s\S][^'\\]*)*'|[^\]\s,]+|(?<=,)(?=\s*,))\s*,?\s*)*)\]/i
|
5
|
+
ATTRIBUTE_PATTERN = /(?:\s*(?:([a-z0-9_-]+)|^)\s*[:=]\s*)?("[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]*(?:\\[\s\S][^'\\]*)*'|[^\]\s,]+|(?<=,)(?=\s*,))\s*,?/i
|
6
|
+
UNESCAPE_PATTERN = /\\(.)/
|
7
|
+
|
8
|
+
def parse_attributes_string( attributes_string )
|
9
|
+
attrs = HashWithIndifferentAccess.new
|
10
|
+
return attrs if attributes_string.nil?
|
11
|
+
|
12
|
+
next_anonymous_key = -1
|
13
|
+
attributes_string.scan ATTRIBUTE_PATTERN do |key, value|
|
14
|
+
skip_value = key.blank? && value.blank?
|
15
|
+
key = next_anonymous_key+=1 if key.blank?
|
16
|
+
unless skip_value
|
17
|
+
value = value[1...-1].gsub UNESCAPE_PATTERN, "\\1" if value[0] == value[-1] && ["'", '"'].include?(value[0])
|
18
|
+
attrs[key] = value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
return attrs
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parses the document as BBCode-formatted text and calls block with bbcode
|
26
|
+
# events.
|
27
|
+
#
|
28
|
+
# The block is called with the following events:
|
29
|
+
# - :text, text
|
30
|
+
# A text-event with an additional parameter containing the actual text.
|
31
|
+
# - :start_element, element_name, element_arguments
|
32
|
+
# An element-event with 2 additional parameters: The element name as a
|
33
|
+
# symbol and the element attributes as a hash. This events indicate the
|
34
|
+
# start of the element.
|
35
|
+
# - :end_element, element_name
|
36
|
+
# An element-event indicating the end of an element. Optionally, the
|
37
|
+
# element_name is added as a parameter. If no parameter is present, it is
|
38
|
+
# assumed to be the last started element.
|
39
|
+
#
|
40
|
+
# Note that :start_element and :end_element are not guaranteed to be called
|
41
|
+
# evenly or in the "correct" order. You must match correct start- and end
|
42
|
+
# tags yourself to create the elements.
|
43
|
+
#
|
44
|
+
# Also note that :text events are not guaranteed to match the whole text.
|
45
|
+
# In some cases, the text might be separated to multiple :text events, even
|
46
|
+
# though there are no nodes in between.
|
47
|
+
def tokenize( document, &handler )
|
48
|
+
while !(match = BBCODE_TAG_PATTERN.match(document)).nil?
|
49
|
+
offset = match.begin(0)
|
50
|
+
elem_source = match[0]
|
51
|
+
|
52
|
+
handler.call :text, document[0...offset] unless offset == 0
|
53
|
+
|
54
|
+
elem_is_closing_tag = match[1]=='/'
|
55
|
+
elem_name = (match[2].length > 0 && match[2].to_sym) || nil
|
56
|
+
elem_attr_string = (match[3].length > 0 && match[3]) || nil
|
57
|
+
|
58
|
+
if (elem_is_closing_tag && !elem_attr_string) || (!elem_is_closing_tag && elem_name)
|
59
|
+
if !elem_is_closing_tag
|
60
|
+
handler.call :start_element, elem_name, parse_attributes_string(elem_attr_string), elem_source
|
61
|
+
else
|
62
|
+
handler.call :end_element, elem_name, elem_source
|
63
|
+
end
|
64
|
+
else
|
65
|
+
handler.call :text, elem_source
|
66
|
+
end
|
67
|
+
|
68
|
+
document = document[(offset+elem_source.length)..-1]
|
69
|
+
end
|
70
|
+
|
71
|
+
handler.call :text, document unless document.length == 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/bbcode.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
def get_handled_parser_result( string )
|
4
|
+
quote_handler = Bbcode::Handler.new()
|
5
|
+
|
6
|
+
handler = Bbcode::Handler.new({
|
7
|
+
:b => ->(element){ "<strong>#{element.content}</strong>" },
|
8
|
+
:i => ->(element){ "<em>#{element.content}</em>" },
|
9
|
+
:url => ->(element){ %(<a href="#{CGI.escapeHTML(element[0])}">#{element.content}</a>) },
|
10
|
+
:txt => ->(element){ "#{CGI.escapeHTML(element.content.source)}" },
|
11
|
+
:img => ->(element){ %(<img src="#{CGI.escapeHTML(element.content.source)}">) },
|
12
|
+
:quote => ->(element){ %(<blockquote>#{element.content.with_handler(quote_handler)}</blockquote>) },
|
13
|
+
:color => ->(element){ %(<span style="color: #{CGI.escapeHTML(element[0])};">#{element.content}</span>) },
|
14
|
+
:"#text" => ->(text){ CGI.escapeHTML(text) }
|
15
|
+
})
|
16
|
+
|
17
|
+
quote_handler.register_element_handlers handler.element_handlers.merge({
|
18
|
+
:img => ->(element){ %(<a href="#{element.content.source}">image</a>) },
|
19
|
+
:quote => ->(element){ "[...]" }
|
20
|
+
})
|
21
|
+
|
22
|
+
parser = Bbcode::Parser.new Bbcode::Tokenizer.new
|
23
|
+
parser.parse string, handler
|
24
|
+
"#{handler.get_document.content}"
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Bbcode::Handler do
|
28
|
+
it "should handle text without bbcode" do
|
29
|
+
get_handled_parser_result("Hello, World!").should eql("Hello, World!")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should handle the text handler" do
|
33
|
+
get_handled_parser_result("&").should eql("&")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should handle a bbcode tag" do
|
37
|
+
get_handled_parser_result("[b]bold[/]").should eql("<strong>bold</strong>")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should ignore unregistered elements" do
|
41
|
+
get_handled_parser_result("[foo]text[/foo]").should eql("[foo]text[/foo]")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should handle bbcode inside an ignored element" do
|
45
|
+
get_handled_parser_result("[foo][b]bold[/b][/foo]").should eql("[foo]<strong>bold</strong>[/foo]")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should handle nested bbcode tags" do
|
49
|
+
get_handled_parser_result("[b]bold and [i]italic[/][/]").should \
|
50
|
+
eql("<strong>bold and <em>italic</em></strong>")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should handle attributes in a bbcode tag" do
|
54
|
+
get_handled_parser_result("[url=http://google.com/]google[/url]").should \
|
55
|
+
eql(%(<a href="http://google.com/">google</a>))
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should handle basic tag interrupts" do
|
59
|
+
get_handled_parser_result("[b]bold[i]and italic[/b]only italic[/i]").should \
|
60
|
+
eql("<strong>bold<em>and italic</em></strong><em>only italic</em>")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should resend attributes in tag interrupts" do
|
64
|
+
get_handled_parser_result("[b]bold[color=red]and red[/b]but not bold[/]").should \
|
65
|
+
eql(%(<strong>bold<span style="color: red;">and red</span></strong><span style="color: red;">but not bold</span>))
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be able to render the source of an element's contents" do
|
69
|
+
get_handled_parser_result("[txt][b]ignored element[/b][/txt]").should \
|
70
|
+
eql("[b]ignored element[/b]")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to switch handlers" do
|
74
|
+
get_handled_parser_result("[quote][img]epic.jpg[/img][/quote][img]epic.jpg[/img]").should \
|
75
|
+
eql(%(<blockquote><a href="epic.jpg">image</a></blockquote><img src="epic.jpg">))
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
quote_handler = Bbcode::Handler.new()
|
4
|
+
|
5
|
+
handler = Bbcode::Handler.new({
|
6
|
+
:b => ->(element){ "<strong>#{element.content}</strong>" },
|
7
|
+
:i => ->(element){ "<em>#{element.content}</em>" },
|
8
|
+
:url => ->(element){ %(<a href="#{CGI.escapeHTML(element[0])}">#{element.content}</a>) },
|
9
|
+
:txt => ->(element){ "#{element.content.source}" },
|
10
|
+
:img => ->(element){ %(<img src="#{element.content.source}">) },
|
11
|
+
:quote => ->(element){ %(<blockquote>#{element.content.with_handler(quote_handler)}</blockquote>) },
|
12
|
+
:color => ->(element){ %(<span style="color: #{CGI.escapeHTML(element[0])};">#{element.content}</span>) },
|
13
|
+
:"#text" => ->(text){ CGI.escapeHTML(text) }
|
14
|
+
})
|
15
|
+
|
16
|
+
quote_handler.register_element_handlers handler.element_handlers.merge({
|
17
|
+
:img => ->(element){ %(<a href="#{element.content.source}">image</a>) },
|
18
|
+
:quote => ->(element){ "[...]" }
|
19
|
+
})
|
20
|
+
|
21
|
+
Bbcode::Base.register_handler :html, handler
|
22
|
+
Bbcode::Base.register_handler :text, Bbcode::Handler.new()
|
23
|
+
|
24
|
+
describe Bbcode::Helpers do
|
25
|
+
it "should enable a string to use a registered helper" do
|
26
|
+
"test".as_bbcode.to(:text).should eql("test")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be able to parse for a second time without being affected by the first" do
|
30
|
+
"test 2".as_bbcode.to(:text).should eql("test 2")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should enable a string to be parsed as bbcode" do
|
34
|
+
"[b]bold[/]".as_bbcode.to(:html).should eql("<strong>bold</strong>")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should be able to process non-ascii characters" do
|
38
|
+
# load UTF-8 content from a file and parse it
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
def get_handled_html_parser_result( string )
|
4
|
+
quote_handler = Bbcode::HtmlHandler.new()
|
5
|
+
|
6
|
+
handler = Bbcode::HtmlHandler.new({
|
7
|
+
:b => :strong,
|
8
|
+
:i => :em,
|
9
|
+
:url => [ :a, { :href => "%{0}" } ],
|
10
|
+
:txt => ->(element){ "#{CGI.escapeHTML(element.content.source)}" },
|
11
|
+
:img => ->(element){ %(<img src="#{CGI.escapeHTML(element.content.source)}">) },
|
12
|
+
:quote => ->(element){ %(<blockquote>#{element.content.with_handler(quote_handler)}</blockquote>) },
|
13
|
+
:color => [ :span, { :style => "color: %{0};" } ]
|
14
|
+
})
|
15
|
+
|
16
|
+
quote_handler.register_element_handlers handler.element_handlers.merge({
|
17
|
+
:img => ->(element){ %(<a href="#{CGI.escapeHTML(element.content.source)}">image</a>) },
|
18
|
+
:quote => ->(element){ "[...]" }
|
19
|
+
})
|
20
|
+
|
21
|
+
parser = Bbcode::Parser.new Bbcode::Tokenizer.new
|
22
|
+
parser.parse string, handler
|
23
|
+
"#{handler.get_document.content}"
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Bbcode::HtmlHandler do
|
27
|
+
it "should handle text without bbcode" do
|
28
|
+
get_handled_html_parser_result("Hello, World!").should eql("Hello, World!")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should escape text" do
|
32
|
+
get_handled_html_parser_result("&").should eql("&")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should handle a bbcode tag" do
|
36
|
+
get_handled_html_parser_result("[b]bold[/]").should eql("<strong>bold</strong>")
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should handle nested bbcode tags" do
|
40
|
+
get_handled_html_parser_result("[b]bold and [i]italic[/][/]").should \
|
41
|
+
eql("<strong>bold and <em>italic</em></strong>")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should handle attributes in a bbcode tag" do
|
45
|
+
get_handled_html_parser_result("[url=http://google.com/]google[/url]").should \
|
46
|
+
eql(%(<a href="http://google.com/">google</a>))
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should handle basic tag interrupts" do
|
50
|
+
get_handled_html_parser_result("[b]bold[i]and italic[/b]only italic[/i]").should \
|
51
|
+
eql("<strong>bold<em>and italic</em></strong><em>only italic</em>")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should resend attributes in tag interrupts" do
|
55
|
+
get_handled_html_parser_result("[b]bold[color=red]and red[/b]but not bold[/]").should \
|
56
|
+
eql(%(<strong>bold<span style="color: red;">and red</span></strong><span style="color: red;">but not bold</span>))
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be able to render the source of an element's contents" do
|
60
|
+
get_handled_html_parser_result("[txt][b]ignored element[/b][/txt]").should \
|
61
|
+
eql("[b]ignored element[/b]")
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should be able to switch handlers" do
|
65
|
+
get_handled_html_parser_result("[quote][img]epic.jpg[/img][/quote][img]epic.jpg[/img]").should \
|
66
|
+
eql(%(<blockquote><a href="epic.jpg">image</a></blockquote><img src="epic.jpg">))
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should be able to escape content within tags" do
|
70
|
+
get_handled_html_parser_result("[i]&[/i]").should eql("<em>&</em>")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should be able to escape content within attributes" do
|
74
|
+
get_handled_html_parser_result("[url=http://youtube.com/watch?v=FErzTCzR5N4&feature=]epic tune[/]").should \
|
75
|
+
eql(%(<a href="http://youtube.com/watch?v=FErzTCzR5N4&feature=">epic tune</a>))
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
class BlockHandler
|
4
|
+
def initialize( block )
|
5
|
+
@block = block
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(*args)
|
9
|
+
@block.call *args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_parser_results(string, strip_source = true)
|
14
|
+
parser = Bbcode::Parser.new Bbcode::Tokenizer.new
|
15
|
+
results = []
|
16
|
+
parser.parse string, BlockHandler.new(->(*args) {
|
17
|
+
args.pop if strip_source && [:end_element, :start_element].include?(args.first) # pop the source
|
18
|
+
results.push args
|
19
|
+
})
|
20
|
+
results
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Bbcode::Parser do
|
24
|
+
it "should parse text without bbcode" do
|
25
|
+
get_parser_results("Hello, world!")[0].should eql([:text, "Hello, world!"])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should parse a simple bbcode element" do
|
29
|
+
get_parser_results("[b]text[/b]").should \
|
30
|
+
eql([ [ :start_element, :b, {} ],
|
31
|
+
[ :text, "text" ],
|
32
|
+
[ :end_element, :b ] ])
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should parse a simple bbcode element with shorthand closing tag" do
|
36
|
+
get_parser_results("[b]text[/]").should \
|
37
|
+
eql([ [ :start_element, :b, {} ],
|
38
|
+
[ :text, "text" ],
|
39
|
+
[ :end_element, :b ] ])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should provide the actual sourcecode of the elements" do
|
43
|
+
get_parser_results("[b a = 1, b:2, c='1'][/][url=http://www.google.com/][/url]", false).should \
|
44
|
+
eql([ [ :start_element, :b, { :a => "1", :b => "2", :c => "1" }.with_indifferent_access, "[b a = 1, b:2, c='1']"],
|
45
|
+
[ :end_element, :b, "[/]" ],
|
46
|
+
[ :start_element, :url, { 0 => "http://www.google.com/" }, "[url=http://www.google.com/]" ],
|
47
|
+
[ :end_element, :url, "[/url]" ] ])
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should fire an interrupt for incorrect nested elements" do
|
51
|
+
get_parser_results("[b]bold[i]and italic[/b]but not bold[/i]nor italic").should \
|
52
|
+
eql([ [ :start_element, :b, {} ],
|
53
|
+
[ :text, "bold" ],
|
54
|
+
[ :start_element, :i, {} ],
|
55
|
+
[ :text, "and italic" ],
|
56
|
+
[ :interrupt_element, :i ],
|
57
|
+
[ :end_element, :b ],
|
58
|
+
[ :continue_element, :i ],
|
59
|
+
[ :text, "but not bold" ],
|
60
|
+
[ :end_element, :i ],
|
61
|
+
[ :text, "nor italic" ] ])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should fire multiple interrupts for multiple incorrect nested elements" do
|
65
|
+
get_parser_results("[u]a[b]b[i]c[/u]d[/i]e[/b]").should \
|
66
|
+
eql([ [ :start_element, :u, {} ],
|
67
|
+
[ :text, "a" ],
|
68
|
+
[ :start_element, :b, {} ],
|
69
|
+
[ :text, "b" ],
|
70
|
+
[ :start_element, :i, {} ],
|
71
|
+
[ :text, "c" ],
|
72
|
+
[ :interrupt_element, :i ],
|
73
|
+
[ :interrupt_element, :b ],
|
74
|
+
[ :end_element, :u ],
|
75
|
+
[ :continue_element, :b ],
|
76
|
+
[ :continue_element, :i ],
|
77
|
+
[ :text, "d" ],
|
78
|
+
[ :end_element, :i ],
|
79
|
+
[ :text, "e" ],
|
80
|
+
[ :end_element, :b ]])
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
def get_tokenizer_results(string, strip_source = true)
|
4
|
+
tokenizer = Bbcode::Tokenizer.new
|
5
|
+
results = []
|
6
|
+
tokenizer.tokenize string do |*args|
|
7
|
+
args.pop if strip_source && [:end_element, :start_element].include?(args.first) # pop the source
|
8
|
+
results.push args
|
9
|
+
end
|
10
|
+
results
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Bbcode::Tokenizer do
|
14
|
+
it "should parse text without bbcode" do
|
15
|
+
get_tokenizer_results("Hello, world!")[0].should eql([:text, "Hello, world!"])
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should parse a simple bbcode tag" do
|
19
|
+
get_tokenizer_results("[b]")[0].should eql([:start_element, :b, {}])
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should provide the actual source of the bbcode tag" do
|
23
|
+
get_tokenizer_results("[b a = 1, b:2, c='1'][/][url=http://www.google.com/][/url]", false).should \
|
24
|
+
eql([ [ :start_element, :b, { :a => "1", :b => "2", :c => "1" }.with_indifferent_access, "[b a = 1, b:2, c='1']"],
|
25
|
+
[ :end_element, nil, "[/]" ],
|
26
|
+
[ :start_element, :url, { 0 => "http://www.google.com/" }, "[url=http://www.google.com/]" ],
|
27
|
+
[ :end_element, :url, "[/url]" ] ])
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should parse 4 simple bbcode tags with text" do
|
31
|
+
get_tokenizer_results("[b]bold[i]and italic[/b]but not bold anymore[/i]nor italic")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should process text before the first bbcode tag" do
|
35
|
+
get_tokenizer_results("text[b]")[0].should eql([:text, "text"])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should process text after the last bbcode tag" do
|
39
|
+
get_tokenizer_results("[b]text")[1].should eql([:text, "text"])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should parse an anonymous closing tag" do
|
43
|
+
get_tokenizer_results("[/]")[0].should eql([:end_element, nil])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should parse a single, regular unnamed argument" do
|
47
|
+
get_tokenizer_results("[url=http://www.google.com/]")[0].should \
|
48
|
+
eql([:start_element, :url, { 0 => "http://www.google.com/" }])
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should parse a quoted, unnamed argument without the equals-to sign" do
|
52
|
+
get_tokenizer_results("[url'http://www.google.nl/']")[0].should \
|
53
|
+
eql([:start_element, :url, { 0 => "http://www.google.nl/" }])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should parse multiple unnamed arguments" do
|
57
|
+
get_tokenizer_results("[video=640, 480,,1]")[0].should \
|
58
|
+
eql([:start_element, :video, { 0 => "640", 1 => "480", 3 => "1" }]);
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should parse quoted unnamed arguments with escaped characters" do
|
62
|
+
get_tokenizer_results(%([abbr='It\\'s a test', "...a \\"test\\"!"]))[0].should \
|
63
|
+
eql([:start_element, :abbr, { 0 => "It's a test", 1 => '...a "test"!' }])
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should parse quoted named arguments with escaped characters" do
|
67
|
+
get_tokenizer_results(%([abbr a='It\\'s a test', b: "...a \\"test\\"!"]))[0].should \
|
68
|
+
eql([:start_element, :abbr, { :a => "It's a test", :b => '...a "test"!' }.with_indifferent_access])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should parse mixed unnamed and named arguments" do
|
72
|
+
get_tokenizer_results(%([a=foo, bar foo=1 bar:2]))[0].should \
|
73
|
+
eql([:start_element, :a, { 0 => "foo", 1 => "bar", :foo => "1", :bar => "2" }.with_indifferent_access])
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should ignore the quotes of an attribute value if the quote-pair is incomplete or incorrect" do
|
77
|
+
get_tokenizer_results(%([a "test]))[0].should eql([:start_element, :a, { 0 => "\"test" }])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should parse various key=value attribute pairs" do
|
81
|
+
get_tokenizer_results(%([table width = 600 height=300 background-color= \"black\" background-image =url('image.jpg')]))[0].should \
|
82
|
+
eql([:start_element, :table, { :width => "600", :height => "300", :"background-color" => "black", :"background-image" => "url('image.jpg')" }.with_indifferent_access])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should parse key:value attribute pairs separated with optional comma" do
|
86
|
+
get_tokenizer_results(%([alt ding: a, banana: b]))[0].should \
|
87
|
+
eql([:start_element, :alt, { :ding => "a", :banana => "b" }.with_indifferent_access])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should ignore the ] in the attribute value" do
|
91
|
+
get_tokenizer_results(%([testing "with a ] in my attribute!"])).should \
|
92
|
+
eql([[:start_element, :testing, { 0 => "with a ] in my attribute!" }]])
|
93
|
+
end
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'action_view/helpers/capture_helper'
|
5
|
+
require 'action_view/helpers/tag_helper'
|
6
|
+
|
7
|
+
include ActionView::Helpers::TagHelper
|
8
|
+
|
9
|
+
require 'bbcode'
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
# some (optional) config here
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: th-bbcode
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Toby Hinloopen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70176012816200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.6'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70176012816200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70176012815280 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.0.9
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70176012815280
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: actionpack
|
38
|
+
requirement: &70176012814380 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 3.0.9
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70176012814380
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: i18n
|
49
|
+
requirement: &70176012813460 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.5.0
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70176012813460
|
58
|
+
description: Gem for parsing bbcode-formatted strings to HTML or any other formatting
|
59
|
+
you like (or don't like).
|
60
|
+
email:
|
61
|
+
- toby@kutcomputers.nl
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bbcode.gemspec
|
71
|
+
- lib/bbcode.rb
|
72
|
+
- lib/bbcode/base.rb
|
73
|
+
- lib/bbcode/element.rb
|
74
|
+
- lib/bbcode/handler.rb
|
75
|
+
- lib/bbcode/handler_element.rb
|
76
|
+
- lib/bbcode/helpers.rb
|
77
|
+
- lib/bbcode/html_handler.rb
|
78
|
+
- lib/bbcode/node_list.rb
|
79
|
+
- lib/bbcode/parser.rb
|
80
|
+
- lib/bbcode/tokenizer.rb
|
81
|
+
- lib/bbcode/version.rb
|
82
|
+
- spec/lib/bbcode/base_spec.rb
|
83
|
+
- spec/lib/bbcode/handler_spec.rb
|
84
|
+
- spec/lib/bbcode/helpers_spec.rb
|
85
|
+
- spec/lib/bbcode/html_handler_spec.rb
|
86
|
+
- spec/lib/bbcode/parser_spec.rb
|
87
|
+
- spec/lib/bbcode/tokenizer_spec.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
homepage: ''
|
90
|
+
licenses: []
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project: bbcode
|
109
|
+
rubygems_version: 1.8.11
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: BBCode parser
|
113
|
+
test_files:
|
114
|
+
- spec/lib/bbcode/base_spec.rb
|
115
|
+
- spec/lib/bbcode/handler_spec.rb
|
116
|
+
- spec/lib/bbcode/helpers_spec.rb
|
117
|
+
- spec/lib/bbcode/html_handler_spec.rb
|
118
|
+
- spec/lib/bbcode/parser_spec.rb
|
119
|
+
- spec/lib/bbcode/tokenizer_spec.rb
|
120
|
+
- spec/spec_helper.rb
|