ubbparser 0.0.6 → 0.0.7
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.
- data/lib/ubbparser.rb +55 -55
- metadata +3 -3
data/lib/ubbparser.rb
CHANGED
@@ -30,14 +30,14 @@ module UBBParser
|
|
30
30
|
# Attributes are given to the render methods as a hash.
|
31
31
|
def self.attrib_str_to_hash(attrib_str)
|
32
32
|
result = {:original_attrib_str => attrib_str.gsub(/^=/, "")}
|
33
|
-
|
33
|
+
|
34
34
|
attrib_str.insert(0, "default") if (attrib_str[0] == "=")
|
35
35
|
attrib_str.scan(/((\S*)=(\"[^\"]*\"|\'[^\']*\'|\S*))/) { | all, key, val |
|
36
36
|
result[(key.gsub(/-/, "_").to_sym rescue key)] = val.gsub(/^[\"\']/, "").gsub(/[\"\']$/, "")
|
37
37
|
}
|
38
38
|
return result
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# Converts a hash into a string with key-values. You can use one of the following options:
|
42
42
|
# [:allowed_keys] An array of keys that are only allowed
|
43
43
|
# [:denied_keys] An array of keys that are denied
|
@@ -61,25 +61,25 @@ module UBBParser
|
|
61
61
|
def self.parse(text, parse_options = {})
|
62
62
|
result = ""
|
63
63
|
scnr = StringScanner.new(text)
|
64
|
-
parse_options.each { | k, v | v.to_s.gsub(/-/, "_").gsub(/[^\w]+/, "") if (k.to_s.start_with?("class_")); v }
|
64
|
+
parse_options.each { | k, v | v.to_s.gsub(/-/, "_").gsub(/[^\w]+/, "") if (k.to_s.start_with?("class_")); v }
|
65
65
|
while (!scnr.eos?)
|
66
66
|
untagged_text = CGI.escapeHTML(scnr.scan(/[^\[]*/))
|
67
|
-
|
67
|
+
|
68
68
|
# convert newlines to breaks
|
69
69
|
untagged_text.gsub!(/\n/, "<br />") if (!parse_options.include?(:convert_newlines) || parse_options[:convert_newlines])
|
70
|
-
|
70
|
+
|
71
71
|
# check for untagged url's
|
72
72
|
uri_pattern = /(((http|https|ftp)\:\/\/)|(www))[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,4}(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9\-\._\?\,\'\/\\\+&%\$#\=~])*[^\.\,\)\(\s]*/
|
73
73
|
untagged_text.gsub!(uri_pattern) { | url, | render_url(url, {}, parse_options) }
|
74
|
-
|
74
|
+
|
75
75
|
# check for untagged emails
|
76
76
|
email_pattern = /(([a-zA-Z0-9_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))/
|
77
77
|
untagged_text.gsub!(email_pattern) { | email, | render_email(email, {}, parse_options) }
|
78
|
-
|
78
|
+
|
79
79
|
result << untagged_text
|
80
|
-
|
80
|
+
|
81
81
|
# check for the upcoming ubb tag and process it (if valid tag)
|
82
|
-
if (scnr.match?(/\[/))
|
82
|
+
if (scnr.match?(/\[/))
|
83
83
|
scnr.skip(/\[/)
|
84
84
|
code = scnr.scan(/[\w-]*/)
|
85
85
|
method_name = ("render_" + code.to_s.gsub(/-/, "_")).to_sym
|
@@ -102,27 +102,27 @@ module UBBParser
|
|
102
102
|
end
|
103
103
|
return result
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
# Returns true if the given value matches the given regular expression.
|
107
107
|
# :category: Validation methods
|
108
108
|
def self.matches_regexp?(value, regexp)
|
109
109
|
return !value.to_s.match(regexp).nil?
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
# Returns true if the given value is a valid email address
|
113
113
|
# :category: Validation methods
|
114
114
|
def self.is_email?(value)
|
115
115
|
return false if !value.is_a?(String)
|
116
116
|
return self.matches_regexp?(value, /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i)
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
# Returns true if the given value is a valid url
|
120
120
|
# :category: Validation methods
|
121
121
|
def self.is_url?(value)
|
122
122
|
return self.matches_regexp?(value, /^(http|https)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?\'\\\+&%\$#\=~_\-]+))*$/)
|
123
123
|
end
|
124
|
-
|
125
|
-
|
124
|
+
|
125
|
+
|
126
126
|
|
127
127
|
# Converts the [anchor=myname]...[/anchor] tag into <a name='myname'>...</a>. Use the :class_anchor parse option to define html classes.
|
128
128
|
# :category: Render methods
|
@@ -131,7 +131,7 @@ module UBBParser
|
|
131
131
|
name.inner_text.gsub!(/\\|'/) { |c| "\\#{c}" }
|
132
132
|
"<a name='#{name}' class='#{parse_options[:class_anchor].to_s.strip}'>#{self.parse(inner_text, parse_options)}</a>"
|
133
133
|
end
|
134
|
-
|
134
|
+
|
135
135
|
# Converts the url in the inner_text into a webplayer, playing the audio file.
|
136
136
|
# :category: Render methods
|
137
137
|
def self.render_audio(inner_text, attributes = {}, parse_options = {})
|
@@ -143,13 +143,13 @@ module UBBParser
|
|
143
143
|
def self.render_b(inner_text, attributes = {}, parse_options = {})
|
144
144
|
"<strong>#{self.parse(inner_text, parse_options)}</strong>"
|
145
145
|
end
|
146
|
-
|
146
|
+
|
147
147
|
# Converts [br] into a <br />.
|
148
148
|
# :category: Render methods
|
149
149
|
def self.render_br(inner_text, attributes = {}, parse_options = {})
|
150
150
|
"<br />#{self.parse(inner_text, parse_options)}"
|
151
151
|
end
|
152
|
-
|
152
|
+
|
153
153
|
# Converts all lines in the inner_text as a bullet list. Each line represents one list item. Empty lines are ignored. Use the :class_bullet parse option to define html classes.
|
154
154
|
# :category: Render methods
|
155
155
|
def self.render_bullets(inner_text, attributes = {}, parse_options = {})
|
@@ -158,37 +158,37 @@ module UBBParser
|
|
158
158
|
items.map! { | item | "<li>" + self.parse(item, parse_options) + "</li>" }
|
159
159
|
return (items.empty?) ? "" : "<ul class='#{parse_options[:class_bullets].to_s.strip}'>" + items.join("") + "</ul>"
|
160
160
|
end
|
161
|
-
|
161
|
+
|
162
162
|
# Centers the inner_text.
|
163
163
|
# :category: Render methods
|
164
164
|
def self.render_center(inner_text, attributes = {}, parse_options = {})
|
165
165
|
"<div style='text-align: center'>#{self.parse(inner_text, parse_options)}</div>"
|
166
166
|
end
|
167
|
-
|
168
|
-
# Assures the inner_text is rendered below floating elements.
|
167
|
+
|
168
|
+
# Assures the inner_text is rendered below floating elements.
|
169
169
|
# :category: Render methods
|
170
170
|
def self.render_clear(inner_text, attributes = {}, parse_options = {})
|
171
171
|
"<div style='clear: both'></div>"
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
# Changes the font color of the inner_text
|
175
175
|
# :category: Render methods
|
176
176
|
def self.render_color(inner_text, attributes = {}, parse_options = {})
|
177
177
|
"<div style='color:#{attributes[:default]}'>#{self.parse(inner_text, parse_options)}</div>"
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
180
|
# Ignores all the inner_text
|
181
181
|
# :category: Render methods
|
182
182
|
def self.render_comment(inner_text, attributes = {}, parse_options = {})
|
183
183
|
""
|
184
184
|
end
|
185
|
-
|
185
|
+
|
186
186
|
# Places the inner_text in a fixed font type. Also adds the classes prettify and linenums for styling purposes. Use the :class_code parse option to define html classes.
|
187
187
|
# :category: Render methods
|
188
188
|
def self.render_code(inner_text, attributes = {}, parse_options = {})
|
189
189
|
"<pre class='#{parse_options[:class_code].to_s.strip}'>#{inner_text}</pre>"
|
190
190
|
end
|
191
|
-
|
191
|
+
|
192
192
|
# Renders csv-data into a html table. You can use the following attributes:
|
193
193
|
# [:has_header] The first row should be rendered as header cells (using th).
|
194
194
|
# :category: Render methods
|
@@ -216,7 +216,7 @@ module UBBParser
|
|
216
216
|
end
|
217
217
|
return result
|
218
218
|
end
|
219
|
-
|
219
|
+
|
220
220
|
# Renders an email address. There are two options to define:
|
221
221
|
# [email]info@osingasoftware.nl[/email]
|
222
222
|
# [email=info@osingasoftware.nl]Osinga Software[/email]
|
@@ -226,7 +226,7 @@ module UBBParser
|
|
226
226
|
email = (attributes[:default] || inner_text)
|
227
227
|
if (!self.is_email?(email))
|
228
228
|
parse_options[:class_email] = parse_options[:class_email].to_s + " ubbparser-error"
|
229
|
-
result = "<span class='#{parse_options[:class_email].to_s.strip}'>UBB error: invalid email address #{email}</span>"
|
229
|
+
result = "<span class='#{parse_options[:class_email].to_s.strip}'>UBB error: invalid email address #{email}</span>"
|
230
230
|
elsif ((parse_options.has_key?(:protect_email) && !parse_options[:protect_email]) || (attributes[:protected] == "false"))
|
231
231
|
result = "<a href='mailto:#{email}' class='#{parse_options[:class_email].to_s.strip}'>#{inner_text}</a>"
|
232
232
|
else
|
@@ -240,45 +240,45 @@ module UBBParser
|
|
240
240
|
end
|
241
241
|
return result
|
242
242
|
end
|
243
|
-
|
243
|
+
|
244
244
|
# Renders the inner_text in the specified list. The list should contain CSS style font-families, i.e.:
|
245
245
|
# [font=Arial, Helvetica, Sans]...[/font]
|
246
246
|
# :category: Render methods
|
247
247
|
def self.render_font(inner_text, attributes = {}, parse_options = {})
|
248
|
-
font = attributes[:original_attrib_str].gsub!(/\\|'/) { |c| "\\#{c}" }
|
248
|
+
font = attributes[:original_attrib_str].gsub!(/\\|'/) { |c| "\\#{c}" }
|
249
249
|
"<span style='font-family: #{font}'>#{self.parse(inner_text, parse_options)}</span>"
|
250
250
|
end
|
251
251
|
|
252
|
-
# Renders the inner_text in a H1 heading.
|
252
|
+
# Renders the inner_text in a H1 heading.
|
253
253
|
# :category: Render methods
|
254
254
|
def self.render_h1(inner_text, attributes = {}, parse_options = {})
|
255
255
|
"<h1>#{self.parse(inner_text, parse_options)}</h1>"
|
256
256
|
end
|
257
|
-
|
258
|
-
# Renders the inner_text in a H2 heading.
|
257
|
+
|
258
|
+
# Renders the inner_text in a H2 heading.
|
259
259
|
# :category: Render methods
|
260
260
|
def self.render_h2(inner_text, attributes = {}, parse_options = {})
|
261
261
|
"<h2>#{self.parse(inner_text, parse_options)}</h2>"
|
262
262
|
end
|
263
|
-
|
263
|
+
|
264
264
|
# Renders the inner_text in a H3 heading.
|
265
265
|
# :category: Render methods
|
266
266
|
def self.render_h3(inner_text, attributes = {}, parse_options = {})
|
267
267
|
"<h3>#{self.parse(inner_text, parse_options)}</h3>"
|
268
268
|
end
|
269
|
-
|
269
|
+
|
270
270
|
# Renders a horizontal ruler.
|
271
271
|
# :category: Render methods
|
272
272
|
def self.render_hr(inner_text, attributes = {}, parse_options = {})
|
273
273
|
"<hr />#{self.parse(inner_text, parse_options)}"
|
274
274
|
end
|
275
|
-
|
275
|
+
|
276
276
|
# Renders the inner_text in italic.
|
277
277
|
# :category: Render methods
|
278
278
|
def self.render_i(inner_text, attributes = {}, parse_options = {})
|
279
279
|
"<em>#{self.parse(inner_text, parse_options)}</em>"
|
280
280
|
end
|
281
|
-
|
281
|
+
|
282
282
|
# Renders an iframe. Use the inner_text as source. Use the :class_iframe parse option to define html classes.
|
283
283
|
# :category: Render methods
|
284
284
|
def self.render_iframe(inner_text, attributes = {}, parse_options = {})
|
@@ -290,13 +290,13 @@ module UBBParser
|
|
290
290
|
attrib_str = self.hash_to_attrib_str(attributes, :allowed_keys => [:src, :class, :frameborder, :marginwidth, :marginheight, :width, :height])
|
291
291
|
return "<iframe #{attrib_str}></iframe>"
|
292
292
|
end
|
293
|
-
|
293
|
+
|
294
294
|
# Doesn't render the ubb code in the inner_text. It does strip all html-tags from the inner_text
|
295
295
|
# :category: Render methods
|
296
296
|
def self.render_ignore(inner_text, attributes = {}, parse_options = {})
|
297
297
|
inner_text
|
298
298
|
end
|
299
|
-
|
299
|
+
|
300
300
|
# Renders an image. Use the :class_img parse option to define html classes.
|
301
301
|
# :category: Render methods
|
302
302
|
def self.render_img(inner_text, attributes = {}, parse_options = {})
|
@@ -308,7 +308,7 @@ module UBBParser
|
|
308
308
|
attrib_str = self.hash_to_attrib_str(attributes, :allowed_keys => [:src, :alt, :styles, :class])
|
309
309
|
return "<img #{attrib_str} />"
|
310
310
|
end
|
311
|
-
|
311
|
+
|
312
312
|
# Renders an image, floated on the left side of the text frame. Use the :class_img_left parse option to define html classes.
|
313
313
|
# :category: Render methods
|
314
314
|
def self.render_img_left(inner_text, attributes = {}, parse_options = {})
|
@@ -317,7 +317,7 @@ module UBBParser
|
|
317
317
|
attributes[:skip_class] = true
|
318
318
|
render_img(inner_text, attributes, parse_options)
|
319
319
|
end
|
320
|
-
|
320
|
+
|
321
321
|
# Renders an image, floated on the right side of the text frame. Use the :class_img_right parse option to define html classes.
|
322
322
|
# :category: Render methods
|
323
323
|
def self.render_img_right(inner_text, attributes = {}, parse_options = {})
|
@@ -326,13 +326,13 @@ module UBBParser
|
|
326
326
|
attributes[:skip_class] = true
|
327
327
|
render_img(inner_text, attributes, parse_options)
|
328
328
|
end
|
329
|
-
|
329
|
+
|
330
330
|
# Renders the inner_text with a justified text alignment.
|
331
331
|
# :category: Render methods
|
332
332
|
def self.render_justify(inner_text, attributes = {}, parse_options = {})
|
333
333
|
"<div style='text-align: justify'>#{self.parse(inner_text, parse_options)}</div>"
|
334
334
|
end
|
335
|
-
|
335
|
+
|
336
336
|
# Renders the inner_text with a left text alignment.
|
337
337
|
# :category: Render methods
|
338
338
|
def self.render_left(inner_text, attributes = {}, parse_options = {})
|
@@ -347,19 +347,19 @@ module UBBParser
|
|
347
347
|
items.map! { | item | "<li>" + self.parse(item, parse_options) + "</li>" }
|
348
348
|
return (items.empty?) ? "" : "<ul class='#{parse_options[:class_list].to_s.strip}'>" + items.join("") + "</ol>"
|
349
349
|
end
|
350
|
-
|
350
|
+
|
351
351
|
# Renders the inner_text as a paragraph. Use the :class_p parse option to define html classes.
|
352
352
|
# :category: Render methods
|
353
353
|
def self.render_p(inner_text, attributes = {}, parse_options = {})
|
354
354
|
"<p class='#{parse_options[:class_p].to_s.strip}'>#{self.parse(inner_text, parse_options)}</p>"
|
355
355
|
end
|
356
|
-
|
356
|
+
|
357
357
|
# Renders the inner_text with a right text alignment.
|
358
358
|
# :category: Render methods
|
359
359
|
def self.render_right(inner_text, attributes = {}, parse_options = {})
|
360
360
|
"<div style='text-align: right'>#{self.parse(inner_text, parse_options)}</div>"
|
361
361
|
end
|
362
|
-
|
362
|
+
|
363
363
|
# Renders the inner_text in a <div> block with inline CSS styles, i.e.:
|
364
364
|
# [style color: red; border: 1px solid green]...[/style]
|
365
365
|
# :category: Render methods
|
@@ -367,7 +367,7 @@ module UBBParser
|
|
367
367
|
styles = attributes[:original_attrib_str].gsub(/'/, "\'")
|
368
368
|
"<div style='#{styles}'>#{self.parse(inner_text, parse_options)}</div>"
|
369
369
|
end
|
370
|
-
|
370
|
+
|
371
371
|
# Converts the [table] to a <table>. Always use this in combination with [tr] and [td] or [th]. Use the :class_table parse option to define html classes.
|
372
372
|
# :category: Render methods
|
373
373
|
def self.render_table(inner_text, attributes = {}, parse_options = {})
|
@@ -383,7 +383,7 @@ module UBBParser
|
|
383
383
|
# Converts the [th] to a <th>. Always use this in combination with [table] and [tr].
|
384
384
|
# :category: Render methods
|
385
385
|
def self.render_th(inner_text, attributes = {}, parse_options = {})
|
386
|
-
|
386
|
+
|
387
387
|
"<th>#{self.parse(inner_text, parse_options)}</th>"
|
388
388
|
end
|
389
389
|
|
@@ -398,7 +398,7 @@ module UBBParser
|
|
398
398
|
def self.render_u(inner_text, attributes = {}, parse_options = {})
|
399
399
|
"<u>#{self.parse(inner_text, parse_options)}</u>"
|
400
400
|
end
|
401
|
-
|
401
|
+
|
402
402
|
# Renders a web addres. There are two options to define:
|
403
403
|
# [url]www.osingasoftware.nl[/ur]
|
404
404
|
# [url=www.osingasoftware.nl]Osinga Software[/url]
|
@@ -410,7 +410,7 @@ module UBBParser
|
|
410
410
|
url.gsub!(/\\|'/) { |c| "\\#{c}" }
|
411
411
|
return "<a href='#{url}' class='#{parse_options[:class_url].to_s.strip}'>#{inner_text}</a>"
|
412
412
|
end
|
413
|
-
|
413
|
+
|
414
414
|
# Renders a YouTube video using the video id or url in the inner_text.
|
415
415
|
# :category: Render methods
|
416
416
|
def self.render_vimeo(inner_text, attributes = {}, parse_options = {})
|
@@ -419,10 +419,10 @@ module UBBParser
|
|
419
419
|
attributes[:class] = parse_options[:class_vimeo]
|
420
420
|
attributes[:skip_class] = true
|
421
421
|
videoid = (inner_text.scan(/[0-9]{5,}/).to_a)[0].to_s
|
422
|
-
src = "http://player.vimeo.com/video/#{videoid}"
|
422
|
+
src = "http://player.vimeo.com/video/#{videoid}"
|
423
423
|
return render_iframe(src, attributes, parse_options)
|
424
424
|
end
|
425
|
-
|
425
|
+
|
426
426
|
# Renders a YouTube video using the video id or url in the inner_text.
|
427
427
|
# :category: Render methods
|
428
428
|
def self.render_youtube(inner_text, attributes = {}, parse_options = {})
|
@@ -434,14 +434,14 @@ module UBBParser
|
|
434
434
|
src = "http://www.youtube.com/embed/#{videoid}"
|
435
435
|
return render_iframe(src, attributes, parse_options)
|
436
436
|
end
|
437
|
-
|
437
|
+
|
438
438
|
# Renders a Youtube, Vimeo or Zideo video using the video id or url in the inner_text.
|
439
439
|
# It automatically determines which video renderer should be used based on the given url.
|
440
440
|
# :category: Render methods
|
441
441
|
def self.render_video(inner_text, attributes = {}, parse_options = {})
|
442
|
-
attributes[:class]
|
442
|
+
attributes[:class] = "#{attributes[:class]} #{parse_options[:class_zideo]}"
|
443
443
|
url = inner_text
|
444
|
-
if
|
444
|
+
if !url.match(/zideo\.nl/).nil?
|
445
445
|
return self.render_zideo(inner_text, attributes, parse_options)
|
446
446
|
elsif (!url.match(/[0-9]{5,}/).nil?) || (!url.match(/vimeo/).nil?)
|
447
447
|
return self.render_vimeo(inner_text, attributes, parse_options)
|
@@ -462,6 +462,6 @@ module UBBParser
|
|
462
462
|
videoid = !inner_text.match(/^\w+$/).nil? ? inner_text : (inner_text.scan(/playzideo\/(\w+)/).to_a)[1].to_s
|
463
463
|
src = "http://www.zideo.nl/zideomediaplayer.php?" + inner_text
|
464
464
|
return render_iframe(src, attributes, parse_options)
|
465
|
-
end
|
466
|
-
|
465
|
+
end
|
466
|
+
|
467
467
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ubbparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-12 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A simple and flexibel ubb parser.
|
15
15
|
email: info@osingasoftware.nl
|
@@ -38,7 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
38
|
version: '0'
|
39
39
|
requirements: []
|
40
40
|
rubyforge_project:
|
41
|
-
rubygems_version: 1.8.
|
41
|
+
rubygems_version: 1.8.25
|
42
42
|
signing_key:
|
43
43
|
specification_version: 3
|
44
44
|
summary: UBB Parser
|