ruby-bbcode 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,125 +1,123 @@
1
1
  module RubyBBCode
2
- # TagInfo is basically what the regex scan get's converted into
3
- # during the tag_sifter#process_text method.
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
- def tag_data
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
- def definition=(val)
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
- text = @tag_data[:text]
42
- # convert_newlines_to_br
43
- text.gsub!("\r\n", "\n")
44
- text.gsub!("\n", "<br />\n")
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
- def handle_unregistered_tags_as_text
56
- if element_is_tag? and tag_missing_from_tag_dictionary?
57
- # Handle as text from now on!
58
- self[:is_tag] = false
59
- self[:closing_tag] = false
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
- def allowed_outside_parent_tags?
89
- @definition[:only_in].nil?
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
- def constrained_to_within_parent_tags?
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
- def allowed_in(tag_symbol)
97
- @definition[:only_in].include?(tag_symbol)
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
- def can_have_params?
101
- @definition[:allow_tag_param]
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
- # Checks if the tag param matches the regex pattern defined in tags.rb
105
- def invalid_param?
106
- self[:params][:tag_param].match(@definition[:tag_param]).nil?
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
- def find_tag_info(tag_info)
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
- if tag_info[5][0] == ?=
120
- ti[:params][:tag_param] = tag_info[5][1..-1]
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
- #FIXME: Find params... Delete this or write a test to cover this and implement it
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
@@ -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
- # Debugging/ visualization purposes
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
- return :tag if @element[:is_tag]
33
- return :text if !@element[:is_tag]
32
+ @element[:is_tag] ? :tag : :text
34
33
  end
35
-
36
- # Checks to see if the parameter for the TagNode has been set.
37
- def param_not_set?
38
- (@element[:params].nil? or @element[:params][:tag_param].nil?)
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
- # check if the parameter for the TagNode is set
42
- def param_set?
43
- !param_not_set?
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
- return false if type == :text or children.length == 0 # text nodes return false too
48
- return true if children.length > 0
47
+ type == :tag and children.length > 0
49
48
  end
50
-
51
- def allow_tag_param?
52
- definition[:allow_tag_param]
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('<', '&lt;').gsub('>', '&gt;').gsub('"', "&quot;") : text_to_parse
12
-
13
- @dictionary = dictionary # the dictionary for all the defined tags in tags.rb
14
- @bbtree = BBTree.new({:nodes => TagCollection.new}, dictionary)
12
+
13
+ @dictionary = dictionary # dictionary containing all allowed/defined tags
14
+ @bbtree = BBTree.new({:nodes => TagCollection.new})
15
15
  @ti = nil
16
- @errors = false
16
+ @errors = []
17
17
  end
18
-
19
- def invalid?
20
- @errors != false
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
- @ti.handle_unregistered_tags_as_text # if the tag isn't in the @dictionary list, then treat it as text
33
- handle_closing_tags_that_are_multi_as_text_if_it_doesnt_match_the_latest_opener_tag_on_the_stack
34
-
35
- return if !valid_element?
36
-
37
- case @ti.type # Validation of tag succeeded, add to @bbtree.tags_list and/or bbtree
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].to_sym, :definition => @ti.definition, :nodes => TagCollection.new }
40
- element[:params] = {:tag_param => get_formatted_element_params} if @ti.can_have_params? and @ti.has_params?
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
- set_parent_tag_from_multi_tag_to_concrete! if @bbtree.current_node.definition && @bbtree.current_node.definition[:multi_tag] == true
49
- element = {:is_tag => false, :text => @ti.text }
50
- if within_open_tag?
51
- tag = @bbtree.current_node.definition
52
- if tag[:require_between]
53
- @bbtree.current_node[:between] = get_formatted_element_params
54
- if candidate_for_using_between_as_param?
55
- use_between_as_tag_param # Did not specify tag_param, so use between text.
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
- @bbtree.build_up_new_tag(element)
76
+
77
+ create_text_element
61
78
  when :closing_tag
62
- @bbtree.retrogress_bbtree if parent_of_self_closing_tag? and @bbtree.within_open_tag?
63
- @bbtree.retrogress_bbtree
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 # TODO: consider automatically closing off all the tags... I think that's how the HTML 5 parser works too
89
+
90
+ validate_all_tags_closed_off
69
91
  validate_stack_level_too_deep_potential
70
92
  end
71
-
72
- def set_parent_tag_from_multi_tag_to_concrete!
73
- # if the proper tag can't be matched, we need to treat the parent tag as text instead! Or throw an error message....
74
-
75
- proper_tag = get_proper_tag
76
- if proper_tag == :tag_not_found
77
- @bbtree.redefine_parent_tag_as_text
78
-
79
- @bbtree.nodes << TagNode.new(@ti.tag_data) # escalate the bbtree with this element as though it's regular text data...
80
- return
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
- def get_proper_tag
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.tag_data[:text]
117
+ return tag if regex =~ @ti.text
94
118
  end
95
119
  end
96
120
  :tag_not_found
97
121
  end
98
-
99
- def handle_closing_tags_that_are_multi_as_text_if_it_doesnt_match_the_latest_opener_tag_on_the_stack
100
- if @ti.element_is_closing_tag?
101
- return if @bbtree.current_node[:definition].nil?
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
- if @ti[:is_tag]
121
- param = @ti[:params][:tag_param]
122
- if @ti.can_have_params? and @ti.has_params?
123
- # perform special formatting for cenrtain tags
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 conduct_special_formatting(url, regex_matches = nil)
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 youtube ID...
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 valid_element?
151
- return false if !valid_text_or_opening_element?
152
- return false if !valid_closing_element?
153
- return false if !valid_param_supplied_as_text?
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 if validate_opening_tag == false
160
- return false if validate_constraints_on_child == 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 validate_opening_tag
171
+
172
+ def valid_opening_tag?
166
173
  if @ti.element_is_opening_tag?
167
- unless @ti.allowed_outside_parent_tags? or (within_open_tag? and @ti.allowed_in(parent_tag.to_sym)) or self_closing_tag_reached_a_closer?
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; return false
176
+ throw_child_requires_specific_parent_error
177
+ return false
170
178
  end
171
179
 
172
- # Originally: tag[:allow_tag_param] and ti[:params][:tag_param] != nil
173
- if @ti.can_have_params? and @ti.has_params?
174
- # Test if matches
175
- if @ti.invalid_param?
176
- throw_invalid_param_error; return false
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.tag_data[:tag].to_sym
206
+ @ti.definition[:self_closable] and @bbtree.current_node[:tag] == @ti[:tag]
185
207
  end
186
-
187
- def validate_constraints_on_child
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
- last_tag = @dictionary[parent_tag]
192
- allowed_tags = last_tag[:only_allow]
193
- if (!@ti[:is_tag] and last_tag[:require_between] != true and @ti[:text].lstrip != "") or (@ti[:is_tag] and (allowed_tags.include?(@ti[:tag].to_sym) == false)) # TODO: refactor this, it's just too long
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; return false
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
- tag = @ti.definition
203
-
224
+
204
225
  if @ti.element_is_closing_tag?
205
- if parent_tag != @ti[:tag].to_sym and !parent_of_self_closing_tag?
206
- @errors = ["Closing tag [/#{@ti[:tag]}] doesn't match [#{parent_tag}]"]
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 tag[:require_between] == true and @bbtree.current_node[:between].nil?
211
- @errors = ["No text between [#{@ti[:tag]}] and [/#{@ti[:tag]}] tags."]
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
- tag = @bbtree.current_node.definition
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 tag[:require_between] and candidate_for_using_between_as_param?
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(tag[:tag_param]).nil?
244
- @errors = [tag[:tag_param_description].gsub('%param%', @ti[:text])]
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
- throw_unexpected_end_of_string_error if expecting_a_closing_tag?
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
- @errors = [err]
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 throw_invalid_param_error
269
- @errors = [@ti.definition[:tag_param_description].gsub('%param%', @ti[:params][:tag_param])]
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 = @dictionary[parent_tag][:only_allow]
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
- @errors = [err]
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 = ["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."]
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 use_between_as_tag_param
306
- param = get_formatted_element_params
307
- @bbtree.current_node.tag_param = param # @bbtree.current_node[:params] = {:tag_param => @ti[:text]}
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