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