Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add color and size tag support #27

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ RbbCode supports the following BBCode features:
* [i]
* [u]
* [s]
* [size]
* [color]
* [url]
* [img]
* [quote]
Expand Down
7 changes: 4 additions & 3 deletions lib/rbbcode.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ def self.parser_class
end
RbbCodeGrammarParser
end

def initialize(options = {})
@options = {
:output_format => :html,
:emoticons => false,
:sanitize => true,
:unsupported_features => :remove,
:sanitize_config => RbbCode::DEFAULT_SANITIZE_CONFIG
}.merge(options)
end

def convert(bb_code, options = {})
# Options passed to #convert will override any options passed to .new.
options = @options.merge(options)
Expand Down Expand Up @@ -69,4 +70,4 @@ def convert_emoticons(output)
def output_format
@options[:output_format]
end
end
end
104 changes: 80 additions & 24 deletions lib/rbbcode/node_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def strip_quotes(str)
str.sub(/^"+/, '').sub(/"+$/, '')
end
end

module RecursiveConversion
def recursively_convert(node, output_method, options, depth = 0)
if node.terminal?
Expand Down Expand Up @@ -34,7 +34,7 @@ def recursively_convert(node, output_method, options, depth = 0)
end
end
end

module DocumentNode
def to_html(options)
contents.elements.collect { |p| p.to_html(options) }.join
Expand All @@ -44,10 +44,10 @@ def to_markdown(options)
contents.elements.collect { |p| p.to_markdown(options) }.join
end
end

module ParagraphNode
include RecursiveConversion

def to_html(options)
# Convert all child nodes, concatenate the results,
# and wrap the concatenated HTML in <p> tags.
Expand All @@ -66,7 +66,7 @@ def to_markdown(options)
markdown + "\n\n"
end
end

module BlockquoteNode
include RecursiveConversion

Expand Down Expand Up @@ -102,7 +102,7 @@ def to_markdown(options)
end + "\n\n"
end
end

module BlockquoteLineNode
# Returns the number of line breaks after this line. May be zero for the final
# line, since there doesn't have to be a break before [/quote].
Expand All @@ -113,7 +113,7 @@ def post_breaks

module ListNode
include RecursiveConversion

def to_html(options)
# Convert the :contents child node (defined in the .treetop file)
# and wrap the result in <ul> tags.
Expand All @@ -127,10 +127,10 @@ def to_markdown(options)
recursively_convert(items, :to_markdown, options) + "\n"
end
end

module ListItemNode
include RecursiveConversion

def to_html(options)
# Convert the :contents child node (defined in the .treetop file)
# and wrap the result in <li> tags.
Expand All @@ -143,13 +143,13 @@ def to_markdown(options)
"* " + recursively_convert(contents, :to_html, options) + "\n"
end
end

# You won't find this module in the .treetop file. Instead, it's effectively a specialization
# of TagNode, which calls to ImgTagNode when processing an img tag. (However, one of the
# child nodes used here, :url, is indeed defined in the .treetop file.)
module URLTagNode
include Attributes

def url_to_html(options)
# The :url child node (defined in the .treetop file) may or may not exist,
# depending on how the link is formatted in the BBCode source.
Expand All @@ -172,7 +172,7 @@ def url_to_markdown(options)
end
end
end

# You won't find this module in the .treetop file. Instead, it's effectively a specialization
# of TagNode, which calls to ImgTagNode when processing an img tag.
module ImgTagNode
Expand All @@ -185,37 +185,93 @@ def img_to_markdown(options)
end
end

# You won't find this module in the .treetop file. Instead, it's effectively a specialization
# of TagNode, which calls to ColorTagNode when processing a color tag.
module ColorTagNode
def color_to_html(options)
unsupported_tag(options)
end

def color_to_markdown(options)
unsupported_tag(options)
end

def unsupported_tag(options)
if options.fetch(:unsupported_features) == :remove
text.text_value
elsif options.fetch(:unsupported_features) == :span
'<span class="' + color_name.text_value + '">' + text.text_value + '</span>'
end
end
end

# You won't find this module in the .treetop file. Instead, it's effectively a specialization
# of TagNode, which calls to SizeTagNode when processing a size tag.
module SizeTagNode
def size_to_html(options)
unsupported_tag(options)
end

def size_to_markdown(options)
unsupported_tag(options)
end

def unsupported_tag(options)
if options.fetch(:unsupported_features) == :remove
text.text_value
elsif options.fetch(:unsupported_features) == :span
'<span class="size' + size_value.text_value + '">' + text.text_value + '</span>'
end
end
end

module UTagNode
def u_to_markdown(options)
# Underlining is unsupported in Markdown. So we just ignore [u] tags.
inner_bbcode
end
end

module TagNode
include RecursiveConversion

# For each tag name, we can either: (a) map to a simple HTML tag or Markdown character, or
# (b) invoke a separate Ruby module for more advanced logic.
TAG_MAPPINGS = {
html: {'b' => 'strong', 'i' => 'em', 'u' => 'u', 'url' => URLTagNode, 'img' => ImgTagNode},
markdown: {'b' => '**', 'i' => '*', 'u' => UTagNode, 'url' => URLTagNode, 'img' => ImgTagNode}
html: {
'b' => 'strong',
'i' => 'em',
'u' => 'u',
'url' => URLTagNode,
'img' => ImgTagNode,
'color' => ColorTagNode,
'size' => SizeTagNode
},
markdown: {
'b' => '**',
'i' => '*',
'u' => UTagNode,
'url' => URLTagNode,
'img' => ImgTagNode,
'color' => ColorTagNode,
'size' => SizeTagNode
}
}

def contents
# The first element is the opening tag, the second is everything inside,
# and the third is the closing tag.
elements[1]
elements[1]
end

def tag_name
elements.first.text_value.slice(1..-2).downcase
end

def inner_bbcode
contents.elements.collect { |e| e.text_value }.join
end

def inner_html(options)
contents.elements.collect do |node|
recursively_convert(node, :to_html, options)
Expand Down Expand Up @@ -254,7 +310,7 @@ def convert(output_format, options)
send("wrap_#{output_format}", t, options)
end
end

def to_html(options)
convert :html, options
end
Expand All @@ -263,7 +319,7 @@ def to_markdown(options)
convert :markdown, options
end
end

module SingleBreakNode
def to_html(options)
'<br/>'
Expand All @@ -273,7 +329,7 @@ def to_markdown(options)
"\n"
end
end

module LiteralTextNode
def to_html(options)
text_value
Expand Down
16 changes: 14 additions & 2 deletions lib/rbbcode/rbbcode_grammar.treetop
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ grammar RbbCodeGrammar

rule tag
# Make sure that anytime you call def_tag, you add it to this list:
(bold / italic / underline / simple_url / complex_url / img)
(bold / italic / underline / simple_url / complex_url / img / color / size)
<RbbCode::TagNode>
end

Expand All @@ -101,7 +101,19 @@ grammar RbbCodeGrammar
<%= def_tag 'underline', 'u' %>
<%= def_tag 'simple_url', 'url' %>
<%= def_tag 'img', 'img' %>


rule size
'[size=' size_value:[^\]]+ ']'
text:(!'[/size]' .)+
'[/size]'
end

rule color
'[color=' color_name:[^\]]+ ']'
text:(!'[/color]' .)+
'[/color]'
end

rule complex_url
'[url=' url:[^\]]+ ']'
text:(!'[/url]' .)+
Expand Down
38 changes: 38 additions & 0 deletions test/unsupported_features_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper.rb')

class TestUnsupportedFeatures < Minitest::Test
include RbbCode::OutputAssertions

def test_remove_color_and_size_tags_to_html
assert_converts_to(
'<p>Not colored or resized but <strong>bold.</strong></p>',
'Not [color=red]colored[/color] or [size=3]resized[/size] but [b]bold.[/b]',
{}
)
end

def test_remove_color_and_size_tags_to_markdown
assert_converts_to(
"Not colored or resized but **bold.**\n\n",
'Not [color=red]colored[/color] or [size=3]resized[/size] but [b]bold.[/b]',
{ output_format: :markdown }
)
end

def test_color_and_size_with_span_tags_to_html
assert_converts_to(
'<p>Not colored or resized but <strong>bold.</strong></p>',
'Not [color=red]colored[/color] or [size=3]resized[/size] but [b]bold.[/b]',
{ :unsupported_features => :span }
)
end

def test_color_and_size_with_tags_to_markdown
assert_converts_to(
"Not <span class=\"red\">colored</span> or <span class=\"size3\">resized</span> but **bold.**\n\n",
'Not [color=red]colored[/color] or [size=3]resized[/size] but [b]bold.[/b]',
{ :unsupported_features => :span, output_format: :markdown }
)
end

end