th-bbcode 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|