ruby-bbcode-to-md 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # Ruby-BBcode-to-MD
2
+ This gem converts BBcode to Markdown. BBcode is parsed before conversion, checking whether the BBcode is valid.
3
+
4
+ The parser recognizes most "official tags":http://www.bbcode.org/reference.php and allows to easily extend this set with custom tags by editing tags.rb.
5
+
6
+ ## Example
7
+ ```ruby
8
+ "This is [b]bold[/b] and this is [i]italic[/i].".bbcode_to_html
9
+ ```
10
+ =>
11
+ ```markdown
12
+ This is **bold** and this is *italic*.
13
+ ```
14
+
15
+ ## Installation
16
+ Add this to your Rails app's Gemfile
17
+ ```
18
+ gem 'ruby-bbcode-to-md'
19
+ ```
20
+ or use the repo link
21
+ ```
22
+ gem 'ruby-bbcode-to-md', :git => git://github.com/rikkit/ruby-bbcode-to-md.git
23
+ ```
24
+ Then run
25
+ ```
26
+ bundle install
27
+ ```
28
+
29
+ And the gem is available in you application
30
+
31
+ _Note: Do not forget to restart your server!_
32
+
33
+ ## Acknowledgements
34
+
35
+ Code is based on BBcode to HTML gem by Maarten Bezemer: http://github.com/veger/ruby-bbcode.git
36
+
37
+ Some of the ideas and the tests came from "bb-ruby":http://github.com/cpjolicoeur/bb-ruby of Craig P Jolicoeur
38
+
39
+ ## Licence
40
+
41
+ MIT Licence. See the included MIT-LICENCE file.
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ require 'rdoc/task'
13
+ RDoc::Task = Rake::RDocTask
14
+ end
15
+
16
+ RDoc::Task.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'RubyBbcode'
19
+ rdoc.options << '--line-numbers'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = true
35
+ end
36
+
37
+ Rake::TestTask.new(:current) do |t|
38
+ t.libs << 'lib'
39
+ t.libs << 'test'
40
+ t.pattern = 'test/**/current_test.rb'
41
+ t.verbose = true
42
+ end
43
+
44
+ task :default => :test
@@ -0,0 +1,100 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
3
+ require 'tags/tags'
4
+ require 'ruby-bbcode/debugging'
5
+ require 'ruby-bbcode/tag_info'
6
+ require 'ruby-bbcode/tag_sifter'
7
+ require 'ruby-bbcode/tag_node'
8
+ require 'ruby-bbcode/tag_collection'
9
+ require 'ruby-bbcode/bbtree'
10
+
11
+
12
+ module RubyBBCode
13
+ include ::RubyBBCode::Tags
14
+
15
+ # This method converts the given text (with BBCode tags) into a HTML representation
16
+ # The escape_html parameter (default: true) escapes HTML tags that were present in the given text and therefore blocking (mallicious) HTML in the original text
17
+ # The additional_tags parameter is used to add additional BBCode tags that should be accepted
18
+ # The method paramter determines whether the tags parameter needs to be used to blacklist (when set to :disable) or whitelist (when not set to :disable) the list of BBCode tags
19
+ def self.to_html(text, escape_html = true, additional_tags = {}, method = :disable, *tags)
20
+ text = text.clone
21
+
22
+ use_tags = determine_applicable_tags(additional_tags, method, *tags)
23
+
24
+ @tag_sifter = TagSifter.new(text, use_tags, escape_html)
25
+
26
+ @tag_sifter.process_text
27
+
28
+ if @tag_sifter.invalid?
29
+ raise @tag_sifter.errors.join(', ') # We cannot convert to HTML if the BBCode is not valid!
30
+ else
31
+ @tag_sifter.bbtree.to_html(use_tags)
32
+ end
33
+
34
+ end
35
+
36
+ # Returns true when valid, else returns array with error(s)
37
+ def self.validity_check(text, additional_tags = {})
38
+ @tag_sifter = TagSifter.new(text, @@tags.merge(additional_tags))
39
+
40
+ @tag_sifter.process_text
41
+ return @tag_sifter.errors if @tag_sifter.invalid?
42
+ true
43
+ end
44
+
45
+
46
+ protected
47
+
48
+ # This method provides the final set of bbcode tags, it merges the default tags with the given additional_tags
49
+ # and blacklists(method = :disable) or whitelists the list of tags with the given tags parameter.
50
+ def self.determine_applicable_tags(additional_tags, method, *tags)
51
+ use_tags = @@tags.merge(additional_tags)
52
+ if method == :disable then # if method is set to :disable
53
+ tags.each { |t| use_tags.delete(t) } # blacklist (remove) the supplied tags
54
+ else # method is not :disable, but has any other value
55
+ # Only use the supplied tags (whitelist)
56
+ new_use_tags = {}
57
+ tags.each { |t| new_use_tags[t] = use_tags[t] if use_tags.key?(t) }
58
+ use_tags = new_use_tags
59
+ end
60
+ use_tags
61
+ end
62
+
63
+ def self.parse(text, tags)
64
+ @tag_sifter = TagSifter.new(text, tags)
65
+
66
+ @tag_sifter.process_text
67
+
68
+ if @tag_sifter.invalid?
69
+ @tag_sifter.errors
70
+ else
71
+ true
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ String.class_eval do
79
+ # Convert a string with BBCode markup into its corresponding HTML markup
80
+ def bbcode_to_md(escape_html = true, additional_tags = {}, method = :disable, *tags)
81
+ RubyBBCode.to_html(self, escape_html, additional_tags, method, *tags)
82
+ end
83
+
84
+ # Replace the BBCode content of a string with its corresponding HTML markup
85
+ def bbcode_to_md!(escape_html = true, additional_tags = {}, method = :disable, *tags)
86
+ self.replace(RubyBBCode.to_html(self, escape_html, additional_tags, method, *tags))
87
+ end
88
+
89
+ # Deprecated! Please use check_bbcode_validity (will be removed in version 0.1.0)
90
+ # Check if string contains valid BBCode. Returns true when valid, else returns array with error(s)
91
+ def is_valid_bbcode?
92
+ # TODO: add a puts "Warning: This method has been deprecated, please use check_bbcode_validity which does the same thing but is more syntactical." or something
93
+ check_bbcode_validity
94
+ end
95
+
96
+ # Check if string contains valid BBCode. Returns true when valid, else returns array with error(s)
97
+ def check_bbcode_validity
98
+ RubyBBCode.validity_check(self)
99
+ end
100
+ end
@@ -0,0 +1,94 @@
1
+ module RubyBBCode
2
+ # As you parse a string of text, say:
3
+ # "[b]I'm bold and the next word is [i]ITALLICS[/i][b]"
4
+ # ...you build up a tree of nodes (@bbtree). The above string converts to 4 nodes when the parse has completed.
5
+ # Node 1) An opening tag node representing "[b]"
6
+ # Node 2) A text node representing "I'm bold and the next word is "
7
+ # Node 3) An opening tag node representing "[i]"
8
+ # Node 4) A text node representing "ITALLICS"
9
+ #
10
+ # The closing of the nodes seems to be implied which is fine by me --less to keep track of.
11
+ #
12
+ class BBTree
13
+ include ::RubyBBCode::DebugBBTree
14
+ attr_accessor :current_node, :tags_list
15
+
16
+ def initialize(hash = { :nodes => TagCollection.new }, dictionary)
17
+ @bbtree = hash
18
+ @current_node = TagNode.new(@bbtree)
19
+ @tags_list = []
20
+ @dictionary = dictionary
21
+ end
22
+
23
+ def [](key)
24
+ @bbtree[key]
25
+ end
26
+
27
+ def []=(key, value)
28
+ @bbtree[key] = value
29
+ end
30
+
31
+ def nodes
32
+ @bbtree[:nodes]
33
+ end
34
+ alias :children :nodes # needed due to the similarities between BBTree[:nodes] and TagNode[:nodes]... they're walked through in debugging.rb right now
35
+
36
+ def type
37
+ :bbtree
38
+ end
39
+
40
+ def within_open_tag?
41
+ @tags_list.length > 0
42
+ end
43
+ alias :expecting_a_closing_tag? :within_open_tag? # just giving this method multiple names for semantical purposes
44
+
45
+ def parent_tag
46
+ return nil if !within_open_tag?
47
+ @tags_list.last.to_sym
48
+ end
49
+
50
+ def parent_has_constraints_on_children?
51
+ @dictionary[parent_tag][:only_allow] != nil
52
+ end
53
+
54
+ # Advance to next level (the node we just added)
55
+ def escalate_bbtree(element)
56
+ element[:parent_tag] = parent_tag
57
+ @tags_list.push element[:tag]
58
+ @current_node = TagNode.new(element)
59
+ end
60
+
61
+ # Step down the bbtree a notch because we've reached a closing tag
62
+ def retrogress_bbtree
63
+ @tags_list.pop # remove latest tag in tags_list since it's closed now...
64
+ # The parsed data manifests in @bbtree.current_node.children << TagNode.new(element) which I think is more confusing than needed
65
+
66
+ if within_open_tag?
67
+ # Set the current node to be the node we've just parsed over which is infact within another node??...
68
+ @current_node = TagNode.new(self.nodes.last)
69
+ else # If we're still at the root of the BBTree or have returned back to the root via encountring closing tags...
70
+ @current_node = TagNode.new({:nodes => self.nodes})
71
+ end
72
+
73
+ # OKOKOK!
74
+ # Since @bbtree = @current_node, if we ever set @current_node to something, we're actually changing @bbtree...
75
+ # therefore... my brain is now numb
76
+ end
77
+
78
+ def redefine_parent_tag_as_text
79
+ @tags_list.pop
80
+ @current_node[:is_tag] = false
81
+ @current_node[:closing_tag] = false
82
+ @current_node.element[:text] = "[#{@current_node[:tag].to_s}]"
83
+ end
84
+
85
+ def build_up_new_tag(element)
86
+ @current_node.children << TagNode.new(element)
87
+ end
88
+
89
+ def to_html(tags = {})
90
+ self.nodes.to_html(tags)
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,93 @@
1
+ module RubyBBCode
2
+ def self.log(string, clear_file = true)
3
+ clear_log_file_at_beginning_of_execution clear_file
4
+
5
+ File.open('/tmp/ruby-bbcode.log', 'a') do |f|
6
+ f.puts string
7
+ end
8
+ end
9
+
10
+ def self.clear_log_file_at_beginning_of_execution(clear_file)
11
+ return if !clear_file
12
+ if defined?(@@cleared_file).nil?
13
+ @@cleared_file = true
14
+ File.open('/tmp/ruby-bbcode.log', 'w+') do |f|
15
+ puts ''
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ # This module can be included in the BBTree and TagNode to give them debugging features
22
+ module DebugBBTree
23
+ # For Debugging/ visualization purposes.
24
+ # This can be used to render the #nodes array in a pretty manor, showing the hirarchy.
25
+ def to_v
26
+ tree = self
27
+ visual_string = ''
28
+
29
+ walk_tree(tree) do |node, depth|
30
+ indentation = ' ' * depth
31
+ case node[:is_tag]
32
+ when true
33
+ visual_string += "#{indentation}" + node[:tag].to_s + "\n"
34
+ when false
35
+ visual_string += "#{indentation}\"#{node[:text]}\"\n"
36
+ end
37
+ end
38
+
39
+ visual_string
40
+ end
41
+
42
+
43
+ # this blocky method counts how many children are
44
+ # in the TagNode.children, recursively walking the tree
45
+ def count_child_nodes(hash = self)
46
+ count = 0
47
+ walk_tree(hash) do
48
+ count += 1
49
+ end
50
+ count
51
+ end
52
+
53
+ # For BBTree, teh to_s method shows the count of the children plus a graphical
54
+ # depiction of the tree, and the relation of the nodes.
55
+ # For TagNodes, the root-most tag is displayed, and the children are counted.
56
+ def to_s
57
+ object_identifier = "#<#{self.class.to_s}:0x#{'%x' % (self.object_id << 1)}\n"
58
+ close_object = ">\n"
59
+
60
+ case self
61
+ when RubyBBCode::BBTree
62
+ object_identifier + "Children: #{count_child_nodes}\n" + self.to_v + close_object
63
+ when RubyBBCode::TagNode # when inspecting TagNodes, it's better not to show the tree display
64
+ if self[:is_tag]
65
+ object_identifier + "Tag: #{self[:tag].to_s}, Children: #{count_child_nodes}\n" + close_object
66
+ else
67
+ object_identifier + '"' + self[:text].to_s + "\"\n" + close_object
68
+ end
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ # This function is used by to_v and anything else that needs to iterate through the
75
+ # @bbtree
76
+ def walk_tree(tree, depth = -1, &blk)
77
+ return enum_for(:walk_tree) unless blk # ignore me for now, I'm a convention for being versatile
78
+
79
+ # Perform the block action specified at top level!!!
80
+ yield tree, depth unless depth == -1
81
+
82
+ # next if we're a text node
83
+ return if tree.type == :text
84
+
85
+ # Enter into recursion (including block action) for each child node in this node
86
+ tree.children.each do |node|
87
+ walk_tree(node, depth + 1, &blk)
88
+ end
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,116 @@
1
+ module RubyBBCode
2
+ # This class holds TagNodes and helps build them into html when the time comes. It's really just a simple array, with the addition of the #to_html method
3
+ class TagCollection < Array
4
+
5
+ def to_html(tags)
6
+ html_string = ""
7
+ self.each do |node|
8
+ if node.type == :tag
9
+ t = HtmlTemplate.new node
10
+
11
+ t.inlay_between_text!
12
+
13
+ if node.allow_tag_param? and node.param_set?
14
+ t.inlay_inline_params!
15
+ elsif node.allow_tag_param? and node.param_not_set?
16
+ t.remove_unused_tokens!
17
+ end
18
+
19
+ html_string += t.opening_html
20
+
21
+ # invoke "recursive" call if this node contains child nodes
22
+ html_string += node.children.to_html(tags) if node.has_children?
23
+
24
+ t.inlay_closing_html!
25
+
26
+ html_string += t.closing_html
27
+ elsif node.type == :text
28
+ html_string += node[:text] unless node[:text].nil?
29
+ end
30
+ end
31
+
32
+ html_string
33
+ end
34
+
35
+
36
+
37
+ # This class is designed to help us build up the HTML data. It starts out as a template such as...
38
+ # @opening_html = '<a href="%url%">%between%'
39
+ # @closing_html = '</a>'
40
+ # and then slowly turns into...
41
+ # @opening_html = '<a href="http://www.blah.com">cool beans'
42
+ # @closing_html = '</a>'
43
+ # TODO: Think about creating a separate file for this or something... maybe look into folder structures cause this project
44
+ # got huge when I showed up.
45
+ class HtmlTemplate
46
+ attr_accessor :opening_html, :closing_html
47
+
48
+ def initialize(node)
49
+ @node = node
50
+ @tag_definition = node.definition # tag_definition
51
+
52
+ @opening_html = ""
53
+ @closing_html = ""
54
+
55
+ # if this is a nested tag, then don't prefix first_html_open
56
+ if !node.definition[:first_html_open].nil? && node.type != node.parent_type then
57
+ @opening_html << node.definition[:first_html_open]
58
+ end
59
+
60
+ if node.definition[:html_open].is_a?(Hash) then
61
+ @opening_html << node.definition[:html_open][node.parent_type].dup
62
+ else
63
+ @opening_html << node.definition[:html_open].dup
64
+ end
65
+
66
+ if node.definition[:html_close].is_a?(Hash) then
67
+ @closing_html << node.definition[:html_close][node.parent_type].dup
68
+ else
69
+ @closing_html << node.definition[:html_close].dup
70
+ end
71
+
72
+ if !node.definition[:last_html_close].nil? && node.type != node.parent_type then
73
+ @closing_html << node.definition[:last_html_close]
74
+ end
75
+ end
76
+
77
+ def inlay_between_text!
78
+ @opening_html.gsub!('%between%',@node[:between]) if between_text_goes_into_html_output_as_param? # set the between text to where it goes if required to do so...
79
+ end
80
+
81
+ def inlay_inline_params!
82
+ # Get list of paramaters to feed
83
+ match_array = @node[:params][:tag_param].scan(@tag_definition[:tag_param])[0]
84
+
85
+ # for each parameter to feed
86
+ match_array.each.with_index do |match, i|
87
+ if i < @tag_definition[:tag_param_tokens].length
88
+
89
+ # Substitute the %param% keyword for the appropriate data specified
90
+ @opening_html.gsub!("%#{@tag_definition[:tag_param_tokens][i][:token].to_s}%",
91
+ @tag_definition[:tag_param_tokens][i][:prefix].to_s +
92
+ match +
93
+ @tag_definition[:tag_param_tokens][i][:postfix].to_s)
94
+ end
95
+ end
96
+ end
97
+
98
+ def inlay_closing_html!
99
+ @closing_html.gsub!('%between%',@node[:between]) if @tag_definition[:require_between]
100
+ end
101
+
102
+ def remove_unused_tokens!
103
+ @tag_definition[:tag_param_tokens].each do |token|
104
+ @opening_html.gsub!("%#{token[:token]}%", '')
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def between_text_goes_into_html_output_as_param?
111
+ @tag_definition[:require_between]
112
+ end
113
+ end
114
+
115
+ end
116
+ end