ruby-bbcode-to-md 0.0.1

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.
@@ -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