ruby-bbcode 1.0.1 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.textile +22 -3
- data/lib/ruby-bbcode.rb +48 -27
- data/lib/ruby-bbcode/bbtree.rb +24 -45
- data/lib/ruby-bbcode/tag_collection.rb +32 -82
- data/lib/ruby-bbcode/tag_info.rb +76 -79
- data/lib/ruby-bbcode/tag_node.rb +25 -31
- data/lib/ruby-bbcode/tag_sifter.rb +214 -184
- data/lib/ruby-bbcode/templates/bbcode_errors_template.rb +68 -0
- data/lib/ruby-bbcode/templates/html_template.rb +55 -0
- data/lib/ruby-bbcode/version.rb +1 -1
- data/lib/tags/tags.rb +33 -23
- data/test/ruby_bbcode_bbcode_test.rb +189 -0
- data/test/{ruby_bbcode_test.rb → ruby_bbcode_html_test.rb} +98 -120
- data/test/ruby_bbcode_validity_test.rb +119 -0
- data/test/test_helper.rb +5 -2
- data/test/unit/tag_sifter_test.rb +22 -32
- data/test/unit/tags_test.rb +17 -0
- metadata +42 -12
- data/test/current_test.rb +0 -8
- data/test/debugging.rb +0 -93
- data/test/unit/debugging_test.rb +0 -48
data/lib/ruby-bbcode/tag_info.rb
CHANGED
@@ -1,125 +1,123 @@
|
|
1
1
|
module RubyBBCode
|
2
|
-
# TagInfo is basically what the regex scan get's converted into
|
3
|
-
#
|
4
|
-
# This class was made mostly just to keep track of all of the confusing
|
5
|
-
# the logic conditions that are checked.
|
2
|
+
# TagInfo is basically what the regex scan get's converted into during the TagSifter#process_text method.
|
3
|
+
# This class was made mostly just to keep track of all of the confusing the logic conditions that are checked.
|
6
4
|
#
|
7
5
|
class TagInfo
|
8
6
|
def initialize(tag_info, dictionary)
|
9
|
-
@tag_data = find_tag_info(tag_info)
|
10
|
-
@dictionary = dictionary
|
11
|
-
@definition = @dictionary[@tag_data[:tag].to_sym] unless @tag_data[:tag].nil?
|
7
|
+
@tag_data = find_tag_info(tag_info, dictionary)
|
12
8
|
end
|
13
|
-
|
9
|
+
|
14
10
|
def [](key)
|
15
11
|
@tag_data[key]
|
16
12
|
end
|
17
|
-
|
13
|
+
|
18
14
|
def []=(key, value)
|
19
15
|
@tag_data[key] = value
|
20
16
|
end
|
21
|
-
|
22
|
-
|
23
|
-
@tag_data
|
24
|
-
end
|
25
|
-
|
17
|
+
|
18
|
+
# Returns the definition of this instance (when it represents a tag element)
|
26
19
|
def definition
|
27
20
|
@definition
|
28
21
|
end
|
29
|
-
|
30
|
-
|
31
|
-
@definition = val
|
32
|
-
end
|
33
|
-
|
34
|
-
def dictionary # need this for reasigning multi_tag elements
|
35
|
-
@dictionary
|
36
|
-
end
|
37
|
-
|
38
|
-
# This represents the text value of the element (if it's not a tag element)
|
39
|
-
# Newlines are converted to html <br /> syntax before being returned.
|
22
|
+
|
23
|
+
# Returns the text (when this instance represents a text element)
|
40
24
|
def text
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
text
|
46
|
-
end
|
47
|
-
|
48
|
-
# allows for a very snazy case/ when conditional
|
25
|
+
@tag_data[:text]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the type of the cuvvrent tag/node, which is either :opening_tag, :closing_tag, or :text
|
49
29
|
def type
|
50
30
|
return :opening_tag if element_is_opening_tag?
|
51
31
|
return :text if element_is_text?
|
52
32
|
return :closing_tag if element_is_closing_tag?
|
53
33
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
self[:text] = self[:complete_match]
|
61
|
-
end
|
34
|
+
|
35
|
+
# Converts this instance (from a tag) into a text element
|
36
|
+
def handle_tag_as_text
|
37
|
+
self[:is_tag] = false
|
38
|
+
self[:closing_tag] = false
|
39
|
+
self[:text] = self[:complete_match]
|
62
40
|
end
|
63
|
-
|
41
|
+
|
42
|
+
# Returns true if this instance represents a tag element
|
64
43
|
def element_is_tag?
|
65
44
|
self[:is_tag]
|
66
45
|
end
|
67
|
-
|
46
|
+
|
47
|
+
# Returns true if this instance represents a text element
|
48
|
+
def element_is_text?
|
49
|
+
!self[:is_tag]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns true if this instance represents an opening tag element
|
68
53
|
def element_is_opening_tag?
|
69
54
|
self[:is_tag] and !self[:closing_tag]
|
70
55
|
end
|
71
|
-
|
56
|
+
|
57
|
+
# Returns true if this instance represents a closing tag element
|
72
58
|
def element_is_closing_tag?
|
73
|
-
self[:closing_tag]
|
74
|
-
end
|
75
|
-
|
76
|
-
def element_is_text?
|
77
|
-
!self[:text].nil?
|
78
|
-
end
|
79
|
-
|
80
|
-
def has_params?
|
81
|
-
self[:params][:tag_param] != nil
|
82
|
-
end
|
83
|
-
|
84
|
-
def tag_missing_from_tag_dictionary?
|
85
|
-
!@dictionary.include?(self[:tag].to_sym)
|
59
|
+
self[:is_tag] and self[:closing_tag]
|
86
60
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
61
|
+
|
62
|
+
# Returns true if this tag element is included in the set of available tags
|
63
|
+
def tag_in_dictionary?
|
64
|
+
!@definition.nil?
|
90
65
|
end
|
91
|
-
|
92
|
-
|
66
|
+
|
67
|
+
# Returns true if the tag that is represented by this instance is restricted on where it is allowed, i.e. if it is restricted by certain parent tags.
|
68
|
+
def only_allowed_in_parent_tags?
|
93
69
|
!@definition[:only_in].nil?
|
94
70
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
71
|
+
|
72
|
+
# Returns true if the tag element is allowed in the provided parent_tag
|
73
|
+
def allowed_in?(parent_tag)
|
74
|
+
!only_allowed_in_parent_tags? or @definition[:only_in].include?(parent_tag)
|
98
75
|
end
|
99
|
-
|
100
|
-
|
101
|
-
|
76
|
+
|
77
|
+
# Returns true if this tag has quick parameter support
|
78
|
+
def can_have_quick_param?
|
79
|
+
@definition[:allow_quick_param]
|
102
80
|
end
|
103
81
|
|
104
|
-
#
|
105
|
-
def
|
106
|
-
|
82
|
+
# Returns true if the tag param matches the regex pattern defined in tags.rb
|
83
|
+
def invalid_quick_param?
|
84
|
+
@tag_data.key? :invalid_quick_param
|
107
85
|
end
|
108
|
-
|
86
|
+
|
109
87
|
protected
|
110
|
-
|
111
|
-
|
88
|
+
|
89
|
+
# Convert the result of the TagSifter#process_text regex into a more usable hash, that is used by the rest of the parser.
|
90
|
+
# tag_info should a result of the regex of TagSifter#process_text
|
91
|
+
# Returns the tag hash
|
92
|
+
def find_tag_info(tag_info, dictionary)
|
112
93
|
ti = {}
|
94
|
+
ti[:errors] = []
|
113
95
|
ti[:complete_match] = tag_info[0]
|
114
96
|
ti[:is_tag] = (tag_info[0].start_with? '[')
|
115
97
|
if ti[:is_tag]
|
116
98
|
ti[:closing_tag] = (tag_info[2] == '/')
|
117
|
-
ti[:tag] = tag_info[3]
|
99
|
+
ti[:tag] = tag_info[3].to_sym.downcase
|
118
100
|
ti[:params] = {}
|
119
|
-
|
120
|
-
|
101
|
+
@definition = dictionary[ti[:tag]]
|
102
|
+
if tag_info[5][0] == ?= and can_have_quick_param?
|
103
|
+
quick_param = tag_info[5][1..-1]
|
104
|
+
# Get list of parameter values and add them as (regular) parameters
|
105
|
+
value_array = quick_param.scan(@definition[:quick_param_format])[0]
|
106
|
+
if value_array.nil?
|
107
|
+
ti[:invalid_quick_param] = quick_param
|
108
|
+
else
|
109
|
+
param_tokens = @definition[:param_tokens]
|
110
|
+
value_array[0..param_tokens.length - 1].each.with_index do |value, i|
|
111
|
+
ti[:params][param_tokens[i][:token]] = value
|
112
|
+
end
|
113
|
+
end
|
121
114
|
elsif tag_info[5][0] == ?\s
|
122
|
-
|
115
|
+
regex_string = '((\w+)=([\w#]+)) | ((\w+)="([^"]+)") | ((\w+)=\'([^\']+)\')'
|
116
|
+
tag_info[5].scan(/#{regex_string}/ix) do |param_info|
|
117
|
+
param = param_info[1] || param_info[4] || param_info[7]
|
118
|
+
value = param_info[2] || param_info[5] || param_info[8]
|
119
|
+
ti[:params][param.to_sym] = value
|
120
|
+
end
|
123
121
|
end
|
124
122
|
else
|
125
123
|
# Plain text
|
@@ -127,6 +125,5 @@ module RubyBBCode
|
|
127
125
|
end
|
128
126
|
ti
|
129
127
|
end
|
130
|
-
|
131
128
|
end
|
132
129
|
end
|
data/lib/ruby-bbcode/tag_node.rb
CHANGED
@@ -6,7 +6,7 @@ module RubyBBCode
|
|
6
6
|
class TagNode
|
7
7
|
# Tag or text element that is stored in this node
|
8
8
|
attr_accessor :element
|
9
|
-
|
9
|
+
|
10
10
|
# ==== Attributes
|
11
11
|
#
|
12
12
|
# * +element+ - contains the information of TagInfo#tag_data.
|
@@ -18,54 +18,48 @@ module RubyBBCode
|
|
18
18
|
def initialize(element, nodes = [])
|
19
19
|
@element = element
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def [](key)
|
23
23
|
@element[key]
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def []=(key, value)
|
27
27
|
@element[key] = value
|
28
28
|
end
|
29
|
-
|
30
|
-
#
|
29
|
+
|
30
|
+
# Returns :tag is the node is a tag node, and :text if the node is a text node
|
31
31
|
def type
|
32
|
-
|
33
|
-
return :text if !@element[:is_tag]
|
32
|
+
@element[:is_tag] ? :tag : :text
|
34
33
|
end
|
35
|
-
|
36
|
-
#
|
37
|
-
def
|
38
|
-
|
34
|
+
|
35
|
+
# Returns true if the tag is allowed to have parameters
|
36
|
+
def allow_params?
|
37
|
+
definition[:param_tokens]
|
39
38
|
end
|
40
|
-
|
41
|
-
#
|
42
|
-
def
|
43
|
-
|
39
|
+
|
40
|
+
# Returns true if the tag does not have any parameters set.
|
41
|
+
def params_not_set?
|
42
|
+
@element[:params].length == 0
|
44
43
|
end
|
45
|
-
|
44
|
+
|
45
|
+
# Returns true id the node that child nodes
|
46
46
|
def has_children?
|
47
|
-
|
48
|
-
return true if children.length > 0
|
47
|
+
type == :tag and children.length > 0
|
49
48
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
|
50
|
+
# Returns true when the quick parameter was invalid (i.e. it did not match the required format)
|
51
|
+
def invalid_quick_param?
|
52
|
+
@element.key? :invalid_quick_param
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
# shows the tag definition for this TagNode as defined in tags.rb
|
56
56
|
def definition
|
57
57
|
@element[:definition]
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
|
+
# Return an list containing the child nodes of this node.
|
60
61
|
def children
|
61
62
|
@element[:nodes]
|
62
63
|
end
|
63
|
-
|
64
|
-
# Easy way to set the tag_param value of the hash, which represents
|
65
|
-
# the parameter supplied
|
66
|
-
def tag_param=(param)
|
67
|
-
@element[:params] = {:tag_param => param}
|
68
|
-
end
|
69
|
-
|
70
64
|
end
|
71
|
-
end
|
65
|
+
end
|
@@ -6,135 +6,144 @@ module RubyBBCode
|
|
6
6
|
# "[b]I'm bold and the next word is [i]ITALIC[/i][b]"
|
7
7
|
class TagSifter
|
8
8
|
attr_reader :bbtree, :errors
|
9
|
-
|
9
|
+
|
10
10
|
def initialize(text_to_parse, dictionary, escape_html = true)
|
11
11
|
@text = escape_html ? text_to_parse.gsub('<', '<').gsub('>', '>').gsub('"', """) : text_to_parse
|
12
|
-
|
13
|
-
@dictionary = dictionary #
|
14
|
-
@bbtree = BBTree.new({:nodes => TagCollection.new}
|
12
|
+
|
13
|
+
@dictionary = dictionary # dictionary containing all allowed/defined tags
|
14
|
+
@bbtree = BBTree.new({:nodes => TagCollection.new})
|
15
15
|
@ti = nil
|
16
|
-
@errors =
|
16
|
+
@errors = []
|
17
17
|
end
|
18
|
-
|
19
|
-
def
|
20
|
-
@errors
|
18
|
+
|
19
|
+
def valid?
|
20
|
+
@errors.empty?
|
21
21
|
end
|
22
|
-
|
23
|
-
# BBTree#process_text is responsible for parsing the actual BBCode text and converting it
|
22
|
+
|
23
|
+
# BBTree#process_text is responsible for parsing the actual BBCode text and converting it
|
24
24
|
# into a 'syntax tree' of nodes, each node represeting either a tag type or content for a tag
|
25
|
-
# once this tree is built, the to_html method can be invoked where the tree is finally
|
26
|
-
# converted into HTML syntax.
|
25
|
+
# once this tree is built, the to_html method can be invoked where the tree is finally
|
26
|
+
# converted into HTML syntax.
|
27
27
|
def process_text
|
28
28
|
regex_string = '((\[ (\/)? ( \* | (\w+)) ((=[^\[\]]+) | (\s\w+=\w+)* | ([^\]]*))? \]) | ([^\[]+))'
|
29
29
|
@text.scan(/#{regex_string}/ix) do |tag_info|
|
30
30
|
@ti = TagInfo.new(tag_info, @dictionary)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
case @ti.type
|
31
|
+
|
32
|
+
# if the tag isn't in the @dictionary list, then treat it as text
|
33
|
+
@ti.handle_tag_as_text if @ti.element_is_tag? and !@ti.tag_in_dictionary?
|
34
|
+
|
35
|
+
validate_element
|
36
|
+
|
37
|
+
case @ti.type
|
38
38
|
when :opening_tag
|
39
|
-
element = {:is_tag => true, :tag => @ti[:tag]
|
40
|
-
element[:
|
41
|
-
|
39
|
+
element = {:is_tag => true, :tag => @ti[:tag], :definition => @ti.definition, :errors => @ti[:errors], :nodes => TagCollection.new }
|
40
|
+
element[:invalid_quick_param] = true if @ti.invalid_quick_param?
|
41
|
+
element[:params] = get_formatted_element_params
|
42
|
+
|
42
43
|
@bbtree.retrogress_bbtree if self_closing_tag_reached_a_closer?
|
43
|
-
|
44
|
+
|
44
45
|
@bbtree.build_up_new_tag(element)
|
45
|
-
|
46
|
+
|
46
47
|
@bbtree.escalate_bbtree(element)
|
47
48
|
when :text
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
tag_def = @bbtree.current_node.definition
|
50
|
+
if tag_def and tag_def[:multi_tag] == true
|
51
|
+
set_multi_tag_to_actual_tag
|
52
|
+
tag_def = @bbtree.current_node.definition
|
53
|
+
end
|
54
|
+
|
55
|
+
if within_open_tag? and tag_def[:require_between]
|
56
|
+
between = get_formatted_between
|
57
|
+
@bbtree.current_node[:between] = between
|
58
|
+
if use_text_as_parameter?
|
59
|
+
value_array = tag_def[:quick_param_format].nil? ? true : between.scan(tag_def[:quick_param_format])[0]
|
60
|
+
if value_array.nil?
|
61
|
+
if @ti[:invalid_quick_param].nil?
|
62
|
+
# Add text element (with error(s))
|
63
|
+
add_element = true
|
64
|
+
|
65
|
+
# ...and clear between, as this would result in two 'between' texts
|
66
|
+
@bbtree.current_node[:between] = ""
|
67
|
+
end
|
68
|
+
else
|
69
|
+
# Between text can be used as (first) parameter
|
70
|
+
@bbtree.current_node[:params][tag_def[:param_tokens][0][:token]] = between
|
56
71
|
end
|
57
|
-
next # don't add this node to @bbtree.current_node.children if we're within an open tag that requires_between (to be a param), and the between couldn't be used as a param... Yet it passed validation so the param must have been specified within the opening tag???
|
58
72
|
end
|
73
|
+
# Don't add this text node, as it is used as between (and might be used as first param)
|
74
|
+
next unless add_element
|
59
75
|
end
|
60
|
-
|
76
|
+
|
77
|
+
create_text_element
|
61
78
|
when :closing_tag
|
62
|
-
|
63
|
-
|
79
|
+
if @ti[:wrong_closing]
|
80
|
+
# Convert into text, so it
|
81
|
+
@ti.handle_tag_as_text
|
82
|
+
create_text_element
|
83
|
+
else
|
84
|
+
@bbtree.retrogress_bbtree if parent_of_self_closing_tag? and within_open_tag?
|
85
|
+
@bbtree.retrogress_bbtree
|
86
|
+
end
|
64
87
|
end
|
65
|
-
|
66
88
|
end # end of scan loop
|
67
|
-
|
68
|
-
validate_all_tags_closed_off
|
89
|
+
|
90
|
+
validate_all_tags_closed_off
|
69
91
|
validate_stack_level_too_deep_potential
|
70
92
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def set_multi_tag_to_actual_tag
|
97
|
+
# Try to find the actual tag
|
98
|
+
tag = get_actual_tag
|
99
|
+
if tag == :tag_not_found
|
100
|
+
# Add error
|
101
|
+
add_tag_error "Unknown multi-tag type for [#{@bbtree.current_node[:tag]}]", @bbtree.current_node
|
102
|
+
else
|
103
|
+
# Update current_node with found information, so it behaves as the actual tag
|
104
|
+
@bbtree.current_node[:definition] = @dictionary[tag]
|
105
|
+
@bbtree.current_node[:tag] = tag
|
81
106
|
end
|
82
|
-
@bbtree.current_node[:definition] = @dictionary[proper_tag]
|
83
|
-
@bbtree.current_node[:tag] = proper_tag
|
84
107
|
end
|
85
108
|
|
86
|
-
|
109
|
+
# The media tag support multiple other tags, this method checks the tag url param to find actual tag type (to use)
|
110
|
+
def get_actual_tag
|
87
111
|
supported_tags = @bbtree.current_node[:definition][:supported_tags]
|
88
112
|
|
89
113
|
supported_tags.each do |tag|
|
90
114
|
regex_list = @dictionary[tag][:url_matches]
|
91
|
-
|
115
|
+
|
92
116
|
regex_list.each do |regex|
|
93
|
-
return tag if regex =~ @ti.
|
117
|
+
return tag if regex =~ @ti.text
|
94
118
|
end
|
95
119
|
end
|
96
120
|
:tag_not_found
|
97
121
|
end
|
98
|
-
|
99
|
-
def
|
100
|
-
|
101
|
-
|
102
|
-
if parent_tag != @ti[:tag].to_sym and @bbtree.current_node[:definition][:multi_tag] # if opening tag doesn't match this closing tag... and if the opener was a multi_tag...
|
103
|
-
@ti[:is_tag] = false
|
104
|
-
@ti[:closing_tag] = false
|
105
|
-
@ti[:text] = @ti.tag_data[:complete_match]
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
122
|
+
|
123
|
+
def create_text_element
|
124
|
+
element = {:is_tag => false, :text => @ti.text, :errors => @ti[:errors] }
|
125
|
+
@bbtree.build_up_new_tag(element)
|
109
126
|
end
|
110
|
-
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
# This method allows us to format params if needed...
|
115
|
-
# TODO: Maybe this kind of thing *could* be handled in the bbtree_to_html where the %between% is
|
116
|
-
# sorted out and the html is generated, but... That code has yet to be refactored and we can.
|
117
|
-
# refactor this code easily to happen over there if necessary... Yes, I think it's more logical
|
118
|
-
# to be put over there, but that method needs to be cleaned up before we introduce the formatting overthere... and knowing the parent node is helpful!
|
127
|
+
|
128
|
+
# Gets the params, and format them if needed...
|
119
129
|
def get_formatted_element_params
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
param = conduct_special_formatting(param) if @ti[:tag].to_sym == :youtube # note: this line isn't ever used because @@tags don't allow it... I think if we have tags without the same kind of :require_between restriction, we'll need to pay close attention to this case
|
125
|
-
end
|
126
|
-
return param
|
127
|
-
else # must be text... @ti[:is_tag] == false
|
128
|
-
param = @ti[:text]
|
129
|
-
# perform special formatting for cenrtain tags
|
130
|
-
param = conduct_special_formatting(param) if @bbtree.current_node.definition[:url_matches]
|
131
|
-
return param
|
130
|
+
params = @ti[:params]
|
131
|
+
if @ti.definition[:url_matches]
|
132
|
+
# perform special formatting for certain tags
|
133
|
+
params[:url] = match_url_id(params[:url], @ti.definition[:url_matches])
|
132
134
|
end
|
135
|
+
return params
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get 'between tag' for tag
|
139
|
+
def get_formatted_between
|
140
|
+
between = @ti[:text]
|
141
|
+
# perform special formatting for cenrtain tags
|
142
|
+
between = match_url_id(between, @bbtree.current_node.definition[:url_matches]) if @bbtree.current_node.definition[:url_matches]
|
143
|
+
return between
|
133
144
|
end
|
134
|
-
|
135
|
-
def
|
136
|
-
regex_matches = @bbtree.current_node.definition[:url_matches] if regex_matches.nil? # for testing purposes we can force in regex_matches
|
137
|
-
|
145
|
+
|
146
|
+
def match_url_id(url, regex_matches)
|
138
147
|
regex_matches.each do |regex|
|
139
148
|
if url =~ regex
|
140
149
|
id = $1
|
@@ -142,188 +151,209 @@ module RubyBBCode
|
|
142
151
|
end
|
143
152
|
end
|
144
153
|
|
145
|
-
return url # if we couldn't find a match, then just return the url, hopefully it's a valid
|
154
|
+
return url # if we couldn't find a match, then just return the url, hopefully it's a valid ID...
|
146
155
|
end
|
147
|
-
|
148
|
-
|
156
|
+
|
149
157
|
# Validates the element
|
150
|
-
def
|
151
|
-
return
|
152
|
-
return
|
153
|
-
return
|
154
|
-
true
|
158
|
+
def validate_element
|
159
|
+
return unless valid_text_or_opening_element?
|
160
|
+
return unless valid_closing_element?
|
161
|
+
return unless valid_param_supplied_as_text?
|
155
162
|
end
|
156
|
-
|
163
|
+
|
157
164
|
def valid_text_or_opening_element?
|
158
165
|
if @ti.element_is_text? or @ti.element_is_opening_tag?
|
159
|
-
return false
|
160
|
-
return false
|
166
|
+
return false unless valid_opening_tag?
|
167
|
+
return false unless valid_constraints_on_child?
|
161
168
|
end
|
162
169
|
true
|
163
170
|
end
|
164
|
-
|
165
|
-
def
|
171
|
+
|
172
|
+
def valid_opening_tag?
|
166
173
|
if @ti.element_is_opening_tag?
|
167
|
-
|
174
|
+
if @ti.only_allowed_in_parent_tags? and (!within_open_tag? or !@ti.allowed_in? parent_tag[:tag]) and !self_closing_tag_reached_a_closer?
|
168
175
|
# Tag doesn't belong in the last opened tag
|
169
|
-
throw_child_requires_specific_parent_error
|
176
|
+
throw_child_requires_specific_parent_error
|
177
|
+
return false
|
170
178
|
end
|
171
179
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
180
|
+
if @ti.invalid_quick_param?
|
181
|
+
throw_invalid_quick_param_error @ti
|
182
|
+
return false
|
183
|
+
end
|
184
|
+
|
185
|
+
# Note that if allow_between_as_param is true, other checks already test the (correctness of the) 'between parameter'
|
186
|
+
unless @ti.definition[:param_tokens].nil? or @ti.definition[:allow_between_as_param] == true
|
187
|
+
# Check if all required parameters are added
|
188
|
+
@ti.definition[:param_tokens].each do |token|
|
189
|
+
if @ti[:params][token[:token]].nil? and token[:optional].nil?
|
190
|
+
add_tag_error "Tag [#{@ti[:tag]}] must have '#{token[:token]}' parameter"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Check if no 'custom parameters' are added
|
195
|
+
@ti[:params].keys.each do |token|
|
196
|
+
if @ti.definition[:param_tokens].find {|param_token| param_token[:token]==token}.nil?
|
197
|
+
add_tag_error "Tag [#{@ti[:tag]}] doesn't have a '#{token}' parameter"
|
198
|
+
end
|
177
199
|
end
|
178
200
|
end
|
179
201
|
end
|
180
202
|
true
|
181
203
|
end
|
182
|
-
|
204
|
+
|
183
205
|
def self_closing_tag_reached_a_closer?
|
184
|
-
@ti.definition[:self_closable] and @bbtree.current_node[:tag] == @ti
|
206
|
+
@ti.definition[:self_closable] and @bbtree.current_node[:tag] == @ti[:tag]
|
185
207
|
end
|
186
|
-
|
187
|
-
def
|
188
|
-
# TODO: Rename this if statement to #validate_constraints_on_child
|
208
|
+
|
209
|
+
def valid_constraints_on_child?
|
189
210
|
if within_open_tag? and parent_has_constraints_on_children?
|
190
211
|
# Check if the found tag is allowed
|
191
|
-
|
192
|
-
allowed_tags =
|
193
|
-
if (!@ti[:is_tag] and
|
212
|
+
last_tag_def = parent_tag[:definition]
|
213
|
+
allowed_tags = last_tag_def[:only_allow]
|
214
|
+
if (!@ti[:is_tag] and last_tag_def[:require_between] != true and @ti[:text].lstrip != "") or (@ti[:is_tag] and (allowed_tags.include?(@ti[:tag]) == false)) # TODO: refactor this, it's just too long
|
194
215
|
# Last opened tag does not allow tag
|
195
|
-
throw_parent_prohibits_this_child_error
|
216
|
+
throw_parent_prohibits_this_child_error
|
217
|
+
return false
|
196
218
|
end
|
197
219
|
end
|
198
220
|
true
|
199
221
|
end
|
200
|
-
|
222
|
+
|
201
223
|
def valid_closing_element?
|
202
|
-
|
203
|
-
|
224
|
+
|
204
225
|
if @ti.element_is_closing_tag?
|
205
|
-
|
206
|
-
|
226
|
+
|
227
|
+
if parent_tag.nil?
|
228
|
+
add_tag_error "Closing tag [/#{@ti[:tag]}] doesn't match an opening tag"
|
229
|
+
@ti[:wrong_closing] = true
|
207
230
|
return false
|
208
231
|
end
|
209
|
-
|
210
|
-
if
|
211
|
-
|
232
|
+
|
233
|
+
if parent_tag[:tag] != @ti[:tag] and !parent_of_self_closing_tag?
|
234
|
+
# Make an exception for 'supported tags'
|
235
|
+
if @ti.definition[:supported_tags].nil? or ! @ti.definition[:supported_tags].include? parent_tag[:tag]
|
236
|
+
add_tag_error "Closing tag [/#{@ti[:tag]}] doesn't match [#{parent_tag[:tag]}]"
|
237
|
+
@ti[:wrong_closing] = true
|
238
|
+
return false
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
tag_def = @bbtree.current_node.definition
|
243
|
+
if tag_def[:require_between] and @bbtree.current_node[:between].nil?
|
244
|
+
err = "No text between [#{@ti[:tag]}] and [/#{@ti[:tag]}] tags."
|
245
|
+
err = "Cannot determine multi-tag type: #{err}" if tag_def[:multi_tag]
|
246
|
+
add_tag_error err, @bbtree.current_node
|
212
247
|
return false
|
213
248
|
end
|
214
|
-
end
|
249
|
+
end
|
215
250
|
true
|
216
251
|
end
|
217
|
-
|
252
|
+
|
218
253
|
def parent_of_self_closing_tag?
|
219
254
|
tag_being_parsed = @ti.definition
|
220
255
|
was_last_tag_self_closable = @bbtree.current_node[:definition][:self_closable] unless @bbtree.current_node[:definition].nil?
|
221
|
-
|
256
|
+
|
222
257
|
was_last_tag_self_closable and last_tag_fit_in_this_tag?
|
223
258
|
end
|
224
|
-
|
259
|
+
|
225
260
|
def last_tag_fit_in_this_tag?
|
226
261
|
@ti.definition[:only_allow].each do |tag|
|
227
262
|
return true if tag == @bbtree.current_node[:tag]
|
228
263
|
end unless @ti.definition[:only_allow].nil?
|
229
264
|
return false
|
230
265
|
end
|
231
|
-
|
232
|
-
# This validation is for text elements with between text
|
266
|
+
|
267
|
+
# This validation is for text elements with between text
|
233
268
|
# that might be construed as a param.
|
234
269
|
# The validation code checks if the params match constraints
|
235
|
-
# imposed by the node/tag/parent.
|
270
|
+
# imposed by the node/tag/parent.
|
236
271
|
def valid_param_supplied_as_text?
|
237
|
-
|
238
|
-
|
272
|
+
tag_def = @bbtree.current_node.definition
|
273
|
+
|
239
274
|
# this conditional ensures whether the validation is apropriate to this tag type
|
240
|
-
if @ti.element_is_text? and within_open_tag? and
|
241
|
-
|
275
|
+
if @ti.element_is_text? and within_open_tag? and tag_def[:require_between] and use_text_as_parameter? and not tag_def[:quick_param_format].nil?
|
276
|
+
|
242
277
|
# check if valid
|
243
|
-
if @ti[:text].match(
|
244
|
-
|
278
|
+
if @ti[:text].match(tag_def[:quick_param_format]).nil?
|
279
|
+
add_tag_error tag_def[:quick_param_format_description].gsub('%param%', @ti[:text])
|
245
280
|
return false
|
246
281
|
end
|
247
282
|
end
|
248
283
|
true
|
249
284
|
end
|
250
|
-
|
285
|
+
|
251
286
|
def validate_all_tags_closed_off
|
252
|
-
# if we're still expecting a closing tag and we've come to the end of the string... throw error
|
253
|
-
|
287
|
+
# if we're still expecting a closing tag and we've come to the end of the string... throw error(s)
|
288
|
+
if expecting_a_closing_tag?
|
289
|
+
@bbtree.tags_list.each do |tag|
|
290
|
+
add_tag_error "[#{tag[:tag]}] not closed", tag
|
291
|
+
tag[:closed] = false
|
292
|
+
end
|
293
|
+
end
|
254
294
|
end
|
255
|
-
|
295
|
+
|
256
296
|
def validate_stack_level_too_deep_potential
|
257
297
|
if @bbtree.nodes.count > 2200
|
258
298
|
throw_stack_level_will_be_too_deep_error
|
259
299
|
end
|
260
300
|
end
|
261
|
-
|
301
|
+
|
262
302
|
def throw_child_requires_specific_parent_error
|
263
303
|
err = "[#{@ti[:tag]}] can only be used in [#{@ti.definition[:only_in].to_sentence(to_sentence_bbcode_tags)}]"
|
264
|
-
err += ", so using it in a [#{parent_tag}] tag is not allowed" if expecting_a_closing_tag?
|
265
|
-
|
304
|
+
err += ", so using it in a [#{parent_tag[:tag]}] tag is not allowed" if expecting_a_closing_tag?
|
305
|
+
add_tag_error err
|
266
306
|
end
|
267
|
-
|
268
|
-
def
|
269
|
-
|
307
|
+
|
308
|
+
def throw_invalid_quick_param_error(tag)
|
309
|
+
add_tag_error tag.definition[:quick_param_format_description].gsub('%param%', tag[:invalid_quick_param]), tag
|
270
310
|
end
|
271
|
-
|
311
|
+
|
272
312
|
def throw_parent_prohibits_this_child_error
|
273
|
-
allowed_tags =
|
274
|
-
err = "[#{parent_tag}] can only contain [#{allowed_tags.to_sentence(to_sentence_bbcode_tags)}] tags, so "
|
313
|
+
allowed_tags = parent_tag[:definition][:only_allow]
|
314
|
+
err = "[#{parent_tag[:tag]}] can only contain [#{allowed_tags.to_sentence(to_sentence_bbcode_tags)}] tags, so "
|
275
315
|
err += "[#{@ti[:tag]}]" if @ti[:is_tag]
|
276
316
|
err += "\"#{@ti[:text]}\"" unless @ti[:is_tag]
|
277
317
|
err += ' is not allowed'
|
278
|
-
|
279
|
-
end
|
280
|
-
|
281
|
-
def throw_unexpected_end_of_string_error
|
282
|
-
@errors = ["[#{@bbtree.tags_list.to_sentence(to_sentence_bbcode_tags)}] not closed"]
|
318
|
+
add_tag_error err
|
283
319
|
end
|
284
|
-
|
320
|
+
|
285
321
|
def throw_stack_level_will_be_too_deep_error
|
286
|
-
@errors
|
322
|
+
@errors << "Stack level would go too deep. You must be trying to process a text containing thousands of BBTree nodes at once. (limit around 2300 tags containing 2,300 strings). Check RubyBBCode::TagCollection#to_html to see why this validation is needed."
|
287
323
|
end
|
288
|
-
|
289
|
-
|
324
|
+
|
290
325
|
def to_sentence_bbcode_tags
|
291
|
-
{:words_connector => "], [",
|
292
|
-
:two_words_connector => "] and [",
|
326
|
+
{:words_connector => "], [",
|
327
|
+
:two_words_connector => "] and [",
|
293
328
|
:last_word_connector => "] and ["}
|
294
329
|
end
|
295
|
-
|
296
|
-
|
330
|
+
|
297
331
|
def expecting_a_closing_tag?
|
298
332
|
@bbtree.expecting_a_closing_tag?
|
299
333
|
end
|
300
|
-
|
334
|
+
|
301
335
|
def within_open_tag?
|
302
336
|
@bbtree.within_open_tag?
|
303
337
|
end
|
304
|
-
|
305
|
-
def
|
306
|
-
|
307
|
-
|
308
|
-
end
|
309
|
-
|
310
|
-
def candidate_for_using_between_as_param?
|
311
|
-
# TODO: the bool values...
|
312
|
-
# are unclear and should be worked on. Additional tag might be tag[:requires_param] such that
|
313
|
-
# [img] would have that as true... and [url] would have that as well...
|
314
|
-
# as it is now, if a tag (say youtube) has tag[:require_between] == true and tag[:allow_tag_param].nil?
|
315
|
-
# then the :between is assumed to be the param... that is, a tag that should respond 'true' to tag.requires_param?
|
316
|
-
tag = @bbtree.current_node.definition
|
317
|
-
tag[:allow_tag_param_between] and @bbtree.current_node.param_not_set?
|
338
|
+
|
339
|
+
def use_text_as_parameter?
|
340
|
+
tag = @bbtree.current_node
|
341
|
+
tag.definition[:allow_between_as_param] and tag.params_not_set? and !tag.invalid_quick_param?
|
318
342
|
end
|
319
|
-
|
343
|
+
|
320
344
|
def parent_tag
|
321
345
|
@bbtree.parent_tag
|
322
346
|
end
|
323
|
-
|
347
|
+
|
324
348
|
def parent_has_constraints_on_children?
|
325
349
|
@bbtree.parent_has_constraints_on_children?
|
326
350
|
end
|
327
|
-
|
351
|
+
|
352
|
+
private
|
353
|
+
|
354
|
+
def add_tag_error(message, tag = @ti)
|
355
|
+
@errors << message
|
356
|
+
tag[:errors] << message
|
357
|
+
end
|
328
358
|
end
|
329
359
|
end
|