rbbcode 0.1.11 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rbbcode/node_extensions.rb +126 -0
- data/lib/rbbcode/rbbcode_grammar.treetop +113 -0
- data/lib/rbbcode/sanitize.rb +13 -0
- data/lib/rbbcode.rb +49 -5
- metadata +34 -131
- data/.document +0 -5
- data/Gemfile +0 -13
- data/LICENSE.txt +0 -21
- data/README.markdown +0 -126
- data/Rakefile +0 -35
- data/VERSION +0 -1
- data/lib/rbbcode/html_maker.rb +0 -90
- data/lib/rbbcode/parser.rb +0 -32
- data/lib/rbbcode/schema.rb +0 -309
- data/lib/rbbcode/tree_maker.rb +0 -430
- data/pkg/rbbcode-0.1.8.gem +0 -0
- data/rbbcode.gemspec +0 -82
- data/spec/html_maker_spec.rb +0 -92
- data/spec/node_spec_helper.rb +0 -117
- data/spec/parser_spec.rb +0 -163
- data/spec/schema_spec.rb +0 -100
- data/spec/spec_helper.rb +0 -17
- data/spec/tree_maker_spec.rb +0 -123
data/Rakefile
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'rake'
|
11
|
-
|
12
|
-
require 'jeweler'
|
13
|
-
|
14
|
-
Jeweler::Tasks.new do |gem|
|
15
|
-
gem.name = 'rbbcode'
|
16
|
-
gem.homepage = "http://github.com/jarrett/rbbcode"
|
17
|
-
gem.license = "MIT"
|
18
|
-
gem.summary = 'Ruby BB Code parser'
|
19
|
-
gem.description = 'RbbCode is a customizable Ruby library for parsing BB Code. RbbCode validates and cleans input. It supports customizable schemas so you can set rules about what tags are allowed where. The default rules are designed to ensure valid HTML output.'
|
20
|
-
gem.email = "jarrett@jarrettcolby.com, aq1018@gmail.com"
|
21
|
-
gem.authors = ['Jarrett Colby', "aq1018@gmail.com"]
|
22
|
-
end
|
23
|
-
Jeweler::RubygemsDotOrgTasks.new
|
24
|
-
|
25
|
-
require 'rspec/core'
|
26
|
-
require 'rspec/core/rake_task'
|
27
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
28
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
29
|
-
spec.rspec_opts = "--color --format progress"
|
30
|
-
end
|
31
|
-
|
32
|
-
task :default => :spec
|
33
|
-
|
34
|
-
require 'yard'
|
35
|
-
YARD::Rake::YardocTask.new
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.1.11
|
data/lib/rbbcode/html_maker.rb
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
require 'sanitize-url'
|
3
|
-
|
4
|
-
module RbbCode
|
5
|
-
DEFAULT_TAG_MAPPINGS = {
|
6
|
-
'p' => 'p',
|
7
|
-
'br' => 'br',
|
8
|
-
'b' => 'strong',
|
9
|
-
'i' => 'em',
|
10
|
-
'u' => 'u',
|
11
|
-
'code' => 'code',
|
12
|
-
'quote' => 'blockquote',
|
13
|
-
'list' => 'ul',
|
14
|
-
'*' => 'li'
|
15
|
-
}
|
16
|
-
|
17
|
-
class HtmlMaker
|
18
|
-
include SanitizeUrl
|
19
|
-
|
20
|
-
def make_html(node)
|
21
|
-
output = ''
|
22
|
-
case node
|
23
|
-
when RbbCode::RootNode
|
24
|
-
node.children.each do |child|
|
25
|
-
output << make_html(child)
|
26
|
-
end
|
27
|
-
when RbbCode::TagNode
|
28
|
-
custom_tag_method = "html_from_#{node.tag_name}_tag"
|
29
|
-
if respond_to?(custom_tag_method)
|
30
|
-
output << send(custom_tag_method, node)
|
31
|
-
else
|
32
|
-
inner_html = ''
|
33
|
-
node.children.each do |child|
|
34
|
-
inner_html << make_html(child)
|
35
|
-
end
|
36
|
-
to_append = content_tag(map_tag_name(node.tag_name), inner_html)
|
37
|
-
if node.preformatted?
|
38
|
-
to_append = content_tag('pre', to_append)
|
39
|
-
end
|
40
|
-
output << to_append
|
41
|
-
#puts output
|
42
|
-
end
|
43
|
-
when RbbCode::TextNode
|
44
|
-
output << node.text
|
45
|
-
else
|
46
|
-
raise "Don't know how to make HTML from #{node.class}"
|
47
|
-
end
|
48
|
-
output
|
49
|
-
end
|
50
|
-
|
51
|
-
protected
|
52
|
-
|
53
|
-
def content_tag(tag_name, contents, attributes = {})
|
54
|
-
output = "<#{tag_name}"
|
55
|
-
attributes.each do |attr, value|
|
56
|
-
output << " #{attr}=\"#{value}\""
|
57
|
-
end
|
58
|
-
if contents.nil? or contents.empty? and not tag_name == 'code'
|
59
|
-
output << '/>'
|
60
|
-
else
|
61
|
-
output << ">#{contents}</#{tag_name}>"
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def html_from_img_tag(node)
|
66
|
-
src = sanitize_url(node.inner_bb_code)
|
67
|
-
content_tag('img', nil, {'src' => src, 'alt' => ''})
|
68
|
-
end
|
69
|
-
|
70
|
-
def html_from_url_tag(node)
|
71
|
-
if node.value.nil?
|
72
|
-
url = node.inner_bb_code
|
73
|
-
else
|
74
|
-
url = node.value
|
75
|
-
end
|
76
|
-
url = sanitize_url(url)
|
77
|
-
inner_html = node.children.inject('') do |output, child|
|
78
|
-
output + make_html(child)
|
79
|
-
end
|
80
|
-
content_tag('a', inner_html, {'href' => url})
|
81
|
-
end
|
82
|
-
|
83
|
-
def map_tag_name(tag_name)
|
84
|
-
unless DEFAULT_TAG_MAPPINGS.has_key?(tag_name)
|
85
|
-
raise "No tag mapping for '#{tag_name}'"
|
86
|
-
end
|
87
|
-
DEFAULT_TAG_MAPPINGS[tag_name]
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
data/lib/rbbcode/parser.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module RbbCode
|
2
|
-
class Parser
|
3
|
-
def initialize(config = {})
|
4
|
-
config.each_key do |key|
|
5
|
-
raise(ArgumentError, "Unknown option #{key}") unless known_options.include?(key)
|
6
|
-
end
|
7
|
-
@config = config
|
8
|
-
end
|
9
|
-
|
10
|
-
def parse(str)
|
11
|
-
str = escape_html_tags(str)
|
12
|
-
|
13
|
-
schema = @config[:schema] || RbbCode::Schema.new
|
14
|
-
|
15
|
-
tree_maker = @config[:tree_maker] || RbbCode::TreeMaker.new(schema)
|
16
|
-
tree = tree_maker.make_tree(str)
|
17
|
-
|
18
|
-
html_maker = @config[:html_maker] || RbbCode::HtmlMaker.new
|
19
|
-
html_maker.make_html(tree)
|
20
|
-
end
|
21
|
-
|
22
|
-
protected
|
23
|
-
|
24
|
-
def escape_html_tags(str)
|
25
|
-
str.gsub('<', '<').gsub('>', '>')
|
26
|
-
end
|
27
|
-
|
28
|
-
def known_options
|
29
|
-
[:schema, :tree_maker, :html_maker]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/rbbcode/schema.rb
DELETED
@@ -1,309 +0,0 @@
|
|
1
|
-
module RbbCode
|
2
|
-
DEFAULT_ALLOWED_TAGS = [
|
3
|
-
'p',
|
4
|
-
'br',
|
5
|
-
'b',
|
6
|
-
'i',
|
7
|
-
'u',
|
8
|
-
'url',
|
9
|
-
'img',
|
10
|
-
'code',
|
11
|
-
'quote',
|
12
|
-
'list',
|
13
|
-
'*'
|
14
|
-
]
|
15
|
-
|
16
|
-
DEFAULT_BLOCK_LEVEL_ELEMENTS = [
|
17
|
-
'quote',
|
18
|
-
'list',
|
19
|
-
'*'
|
20
|
-
]
|
21
|
-
|
22
|
-
class SchemaNode
|
23
|
-
def initialize(schema)
|
24
|
-
@schema = schema
|
25
|
-
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
def normalize_ancestors(ancestors)
|
30
|
-
if ancestors.length == 1 and ancestors[0].is_a?(Array)
|
31
|
-
ancestors = ancestors[0]
|
32
|
-
end
|
33
|
-
ancestors
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class SchemaTag < SchemaNode
|
38
|
-
def closes_twins
|
39
|
-
@schema.close_twins(@name)
|
40
|
-
self
|
41
|
-
end
|
42
|
-
|
43
|
-
def does_not_close_twins
|
44
|
-
@schema.dont_close_twins(@name)
|
45
|
-
self
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize(schema, name)
|
49
|
-
@schema = schema
|
50
|
-
@name = name
|
51
|
-
end
|
52
|
-
|
53
|
-
def is_not_preformatted
|
54
|
-
@schema.unmark_as_preformatted(@name)
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
def is_preformatted
|
59
|
-
@schema.mark_as_preformatted(@name)
|
60
|
-
self
|
61
|
-
end
|
62
|
-
|
63
|
-
def may_be_nested
|
64
|
-
@schema.allow_descent(@name, @name)
|
65
|
-
self
|
66
|
-
end
|
67
|
-
|
68
|
-
def may_contain_text
|
69
|
-
@schema.allow_text(@name)
|
70
|
-
self
|
71
|
-
end
|
72
|
-
|
73
|
-
def may_not_be_empty
|
74
|
-
@schema.forbid_emptiness(@name)
|
75
|
-
end
|
76
|
-
|
77
|
-
def may_not_be_nested
|
78
|
-
@schema.forbid_descent(@name, @name)
|
79
|
-
self
|
80
|
-
end
|
81
|
-
|
82
|
-
def may_descend_from(tag_name)
|
83
|
-
@schema.allow_descent(tag_name, @name)
|
84
|
-
self
|
85
|
-
end
|
86
|
-
|
87
|
-
def may_only_be_parent_of(*tag_names)
|
88
|
-
@schema.forbid_children_except(@name, tag_names)
|
89
|
-
self
|
90
|
-
end
|
91
|
-
|
92
|
-
def may_not_contain_text
|
93
|
-
@schema.forbid_text(@name)
|
94
|
-
self
|
95
|
-
end
|
96
|
-
|
97
|
-
def may_not_descend_from(tag_name)
|
98
|
-
@schema.forbid_descent(tag_name, @name)
|
99
|
-
self
|
100
|
-
end
|
101
|
-
|
102
|
-
def must_be_child_of(*tag_names)
|
103
|
-
@schema.require_parents(tag_names, @name)
|
104
|
-
self
|
105
|
-
end
|
106
|
-
|
107
|
-
def must_be_empty
|
108
|
-
@schema.forbid_children_except(@name, [])
|
109
|
-
may_not_contain_text
|
110
|
-
self
|
111
|
-
end
|
112
|
-
|
113
|
-
def need_not_be_child_of(tag_name)
|
114
|
-
@schema.unrequire_parent(tag_name, @name)
|
115
|
-
self
|
116
|
-
end
|
117
|
-
|
118
|
-
# Returns true if tag_name is valid in the context defined by its list of ancestors.
|
119
|
-
# ancestors should be ordered from most recent ancestor to most distant.
|
120
|
-
def valid_in_context?(*ancestors)
|
121
|
-
@schema.tag_valid_in_context?(@name, normalize_ancestors(ancestors))
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class SchemaText < SchemaNode
|
126
|
-
def valid_in_context?(*ancestors)
|
127
|
-
@schema.text_valid_in_context?(normalize_ancestors(ancestors))
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
class Schema
|
132
|
-
def allow_descent(ancestor, descendant) #:nodoc:
|
133
|
-
if @forbidden_descent.has_key?(descendant.to_s) and @forbidden_descent[descendant.to_s].include?(ancestor.to_s)
|
134
|
-
@forbidden_descent[descendant.to_s].delete(ancestor.to_s)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def allow_emptiness(tag_name)
|
139
|
-
@never_empty.delete(tag_name.to_s)
|
140
|
-
end
|
141
|
-
|
142
|
-
def allow_tag(*tag_names)
|
143
|
-
tag_names.each do |tag_name|
|
144
|
-
unless @allowed_tags.include?(tag_name.to_s)
|
145
|
-
@allowed_tags << tag_name.to_s
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def allow_text(tag_name)
|
151
|
-
@no_text.delete(tag_name.to_s)
|
152
|
-
end
|
153
|
-
|
154
|
-
def block_level?(tag_name)
|
155
|
-
DEFAULT_BLOCK_LEVEL_ELEMENTS.include?(tag_name.to_s)
|
156
|
-
end
|
157
|
-
|
158
|
-
alias_method :allow_tags, :allow_tag
|
159
|
-
|
160
|
-
def clear
|
161
|
-
@allowed_tags = []
|
162
|
-
@never_empty = []
|
163
|
-
@forbidden_descent = {}
|
164
|
-
@required_parents = {}
|
165
|
-
@no_text = []
|
166
|
-
@twin_closers = []
|
167
|
-
@preformatted = []
|
168
|
-
end
|
169
|
-
|
170
|
-
def close_twins(tag_name)
|
171
|
-
@twin_closers << tag_name.to_s unless @twin_closers.include?(tag_name.to_s)
|
172
|
-
end
|
173
|
-
|
174
|
-
def close_twins?(tag_name)
|
175
|
-
@twin_closers.include?(tag_name.to_s)
|
176
|
-
end
|
177
|
-
|
178
|
-
def does_not_close_twins(tag_name)
|
179
|
-
@twin_closers.delete(tag_name.to_s)
|
180
|
-
end
|
181
|
-
|
182
|
-
def forbid_children_except(parent, children)
|
183
|
-
@child_requirements[parent.to_s] = children.collect { |c| c.to_s }
|
184
|
-
end
|
185
|
-
|
186
|
-
def forbid_descent(ancestor, descendant) #:nodoc:
|
187
|
-
@forbidden_descent[descendant.to_s] ||= []
|
188
|
-
unless @forbidden_descent[descendant.to_s].include?(ancestor.to_s)
|
189
|
-
@forbidden_descent[descendant.to_s] << ancestor.to_s
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def forbid_emptiness(tag_name)
|
194
|
-
@never_empty << tag_name.to_s unless @never_empty.include?(tag_name.to_s)
|
195
|
-
end
|
196
|
-
|
197
|
-
def forbid_tag(name)
|
198
|
-
@allowed_tags.delete(name.to_s)
|
199
|
-
end
|
200
|
-
|
201
|
-
def forbid_text(tag_name)
|
202
|
-
@no_text << tag_name.to_s unless @no_text.include?(tag_name.to_s)
|
203
|
-
end
|
204
|
-
|
205
|
-
def initialize
|
206
|
-
@allowed_tags = DEFAULT_ALLOWED_TAGS.dup
|
207
|
-
@forbidden_descent = {}
|
208
|
-
@required_parents = {}
|
209
|
-
@child_requirements = {}
|
210
|
-
@never_empty = []
|
211
|
-
@no_text = []
|
212
|
-
@twin_closers = []
|
213
|
-
@preformatted = []
|
214
|
-
use_defaults
|
215
|
-
end
|
216
|
-
|
217
|
-
def line_break_tag_name
|
218
|
-
'br'
|
219
|
-
end
|
220
|
-
|
221
|
-
def mark_as_preformatted(tag_name)
|
222
|
-
@preformatted << tag_name.to_s unless @preformatted.include?(tag_name.to_s)
|
223
|
-
end
|
224
|
-
|
225
|
-
def paragraph_tag_name
|
226
|
-
'p'
|
227
|
-
end
|
228
|
-
|
229
|
-
def preformatted?(tag_name)
|
230
|
-
@preformatted.include?(tag_name.to_s)
|
231
|
-
end
|
232
|
-
|
233
|
-
def require_parents(parents, child) #:nodoc:
|
234
|
-
@required_parents[child.to_s] = parents.collect { |p| p.to_s }
|
235
|
-
parents.each do |parent|
|
236
|
-
if @forbidden_descent.has_key?(child.to_s)
|
237
|
-
@forbidden_descent[child.to_s].delete(parent)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
def tag(name)
|
243
|
-
SchemaTag.new(self, name)
|
244
|
-
end
|
245
|
-
|
246
|
-
def tag_may_be_empty?(tag_name)
|
247
|
-
!@never_empty.include?(tag_name.to_s)
|
248
|
-
end
|
249
|
-
|
250
|
-
def tag_valid_in_context?(tag_name, ancestors)
|
251
|
-
return false unless @allowed_tags.include?(tag_name.to_s)
|
252
|
-
if @required_parents.has_key?(tag_name.to_s) and !@required_parents[tag_name.to_s].include?(ancestors[0].to_s)
|
253
|
-
return false
|
254
|
-
end
|
255
|
-
if @child_requirements.has_key?(ancestors[0].to_s) and !@child_requirements[ancestors[0].to_s].include?(tag_name.to_s)
|
256
|
-
return false
|
257
|
-
end
|
258
|
-
if @forbidden_descent.has_key?(tag_name.to_s)
|
259
|
-
@forbidden_descent[tag_name.to_s].each do |forbidden_ancestor|
|
260
|
-
return false if ancestors.include?(forbidden_ancestor)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
return true
|
264
|
-
end
|
265
|
-
|
266
|
-
def text
|
267
|
-
SchemaText.new(self)
|
268
|
-
end
|
269
|
-
|
270
|
-
def text_valid_in_context?(*ancestors)
|
271
|
-
ancestors.flatten!
|
272
|
-
if @no_text.include?(ancestors[0].to_s)
|
273
|
-
return false
|
274
|
-
end
|
275
|
-
return true
|
276
|
-
end
|
277
|
-
|
278
|
-
def unmark_as_preformatted(tag_name)
|
279
|
-
@preformatted.delete(tag_name.to_s)
|
280
|
-
end
|
281
|
-
|
282
|
-
def unrequire_parent(parent, child)
|
283
|
-
@required_parents.delete(child.to_s)
|
284
|
-
end
|
285
|
-
|
286
|
-
def use_defaults
|
287
|
-
tag('br').must_be_empty
|
288
|
-
tag('p').may_not_be_nested
|
289
|
-
tag('b').may_not_be_nested
|
290
|
-
tag('b').may_not_be_empty
|
291
|
-
tag('i').may_not_be_nested
|
292
|
-
tag('i').may_not_be_empty
|
293
|
-
tag('u').may_not_be_nested
|
294
|
-
tag('u').may_not_be_empty
|
295
|
-
tag('url').may_not_be_nested
|
296
|
-
tag('img').may_not_be_nested
|
297
|
-
tag('code').may_not_be_nested
|
298
|
-
tag('code').is_preformatted
|
299
|
-
#tag('code').may_not_be_empty
|
300
|
-
tag('quote').may_not_be_empty
|
301
|
-
tag('p').may_not_be_nested
|
302
|
-
tag('*').must_be_child_of('list')
|
303
|
-
tag('*').closes_twins
|
304
|
-
tag('list').may_not_descend_from('p')
|
305
|
-
tag('list').may_only_be_parent_of('*')
|
306
|
-
tag('list').may_not_contain_text
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|