RedCloth 4.2.4-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of RedCloth might be problematic. Click here for more details.
- data/.gemtest +0 -0
- data/.gitignore +26 -0
- data/.rspec +1 -0
- data/CHANGELOG +235 -0
- data/COPYING +18 -0
- data/Gemfile +7 -0
- data/README +198 -0
- data/Rakefile +16 -0
- data/bin/redcloth +28 -0
- data/doc/textile_reference.html +631 -0
- data/lib/case_sensitive_require/RedCloth.rb +6 -0
- data/lib/redcloth.rb +44 -0
- data/lib/redcloth/erb_extension.rb +27 -0
- data/lib/redcloth/formatters/base.rb +63 -0
- data/lib/redcloth/formatters/html.rb +345 -0
- data/lib/redcloth/formatters/latex.rb +322 -0
- data/lib/redcloth/formatters/latex_entities.yml +2414 -0
- data/lib/redcloth/textile_doc.rb +103 -0
- data/lib/redcloth/version.rb +34 -0
- data/lib/tasks/pureruby.rake +17 -0
- data/redcloth.gemspec +47 -0
- data/spec/benchmark_spec.rb +15 -0
- data/spec/custom_tags_spec.rb +50 -0
- data/spec/erb_spec.rb +10 -0
- data/spec/extension_spec.rb +26 -0
- data/spec/fixtures/basic.yml +1028 -0
- data/spec/fixtures/code.yml +257 -0
- data/spec/fixtures/definitions.yml +82 -0
- data/spec/fixtures/extra_whitespace.yml +64 -0
- data/spec/fixtures/filter_html.yml +177 -0
- data/spec/fixtures/filter_pba.yml +20 -0
- data/spec/fixtures/html.yml +348 -0
- data/spec/fixtures/images.yml +279 -0
- data/spec/fixtures/instiki.yml +38 -0
- data/spec/fixtures/links.yml +291 -0
- data/spec/fixtures/lists.yml +462 -0
- data/spec/fixtures/poignant.yml +89 -0
- data/spec/fixtures/sanitize_html.yml +42 -0
- data/spec/fixtures/table.yml +434 -0
- data/spec/fixtures/textism.yml +509 -0
- data/spec/fixtures/threshold.yml +762 -0
- data/spec/formatters/class_filtered_html_spec.rb +7 -0
- data/spec/formatters/filtered_html_spec.rb +7 -0
- data/spec/formatters/html_no_breaks_spec.rb +9 -0
- data/spec/formatters/html_spec.rb +13 -0
- data/spec/formatters/id_filtered_html_spec.rb +7 -0
- data/spec/formatters/latex_spec.rb +13 -0
- data/spec/formatters/lite_mode_html_spec.rb +7 -0
- data/spec/formatters/no_span_caps_html_spec.rb +7 -0
- data/spec/formatters/sanitized_html_spec.rb +7 -0
- data/spec/formatters/style_filtered_html_spec.rb +7 -0
- data/spec/parser_spec.rb +102 -0
- data/spec/spec_helper.rb +36 -0
- data/tasks/compile.rake +47 -0
- data/tasks/gems.rake +37 -0
- data/tasks/ragel_extension_task.rb +127 -0
- data/tasks/release.rake +15 -0
- data/tasks/rspec.rake +13 -0
- data/tasks/rvm.rake +78 -0
- data/test/ragel_profiler.rb +73 -0
- data/test/validate_fixtures.rb +74 -0
- metadata +166 -0
@@ -0,0 +1,6 @@
|
|
1
|
+
# A workaround to make Rails 2.1 gem dependency easier on case-sensitive filesystems.
|
2
|
+
# Since the gem name is RedCloth and the file is redcloth.rb, config.gem 'RedCloth' doesn't
|
3
|
+
# work. You'd have to use config.gem 'RedCloth', :lib => 'redcloth', and that's not
|
4
|
+
# immediately obvious. This file remedies that.
|
5
|
+
#
|
6
|
+
require File.join(File.dirname(__FILE__), '..', 'redcloth')
|
data/lib/redcloth.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# If this is a frozen gem in Rails 2.1 and RedCloth 3.x was already
|
2
|
+
# loaded by Rails' ActionView::Helpers::TextHelper, the user will get
|
3
|
+
# "redcloth_scan.bundle: Class is not a module (TypeError)"
|
4
|
+
# This hack is to work around that Rails loading problem. The problem
|
5
|
+
# appears to be fixed in Edge Rails [51e4106].
|
6
|
+
Object.send(:remove_const, :RedCloth) if Object.const_defined?(:RedCloth) && RedCloth.is_a?(Class)
|
7
|
+
|
8
|
+
require 'rbconfig'
|
9
|
+
begin
|
10
|
+
prefix = Config::CONFIG['arch'] =~ /mswin|mingw/ ? "#{Config::CONFIG['MAJOR']}.#{Config::CONFIG['MINOR']}/" : ''
|
11
|
+
lib = "#{prefix}redcloth_scan"
|
12
|
+
require lib
|
13
|
+
rescue LoadError => e
|
14
|
+
e.message << %{\nCouldn't load #{lib}\nThe $LOAD_PATH was:\n#{$LOAD_PATH.join("\n")}}
|
15
|
+
raise e
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'redcloth/version'
|
19
|
+
require 'redcloth/textile_doc'
|
20
|
+
require 'redcloth/formatters/base'
|
21
|
+
require 'redcloth/formatters/html'
|
22
|
+
require 'redcloth/formatters/latex'
|
23
|
+
|
24
|
+
module RedCloth
|
25
|
+
|
26
|
+
# A convenience method for creating a new TextileDoc. See
|
27
|
+
# RedCloth::TextileDoc.
|
28
|
+
def self.new( *args, &block )
|
29
|
+
RedCloth::TextileDoc.new( *args, &block )
|
30
|
+
end
|
31
|
+
|
32
|
+
# Include extension modules (if any) in TextileDoc.
|
33
|
+
def self.include(*args)
|
34
|
+
RedCloth::TextileDoc.send(:include, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
begin
|
40
|
+
require 'erb'
|
41
|
+
require 'redcloth/erb_extension'
|
42
|
+
include ERB::Util
|
43
|
+
rescue LoadError
|
44
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class ERB
|
2
|
+
module Util
|
3
|
+
|
4
|
+
#
|
5
|
+
# A utility method for transforming Textile in _s_ to HTML.
|
6
|
+
#
|
7
|
+
# require "erb"
|
8
|
+
# include ERB::Util
|
9
|
+
#
|
10
|
+
# puts textilize("Isn't ERB *great*?")
|
11
|
+
#
|
12
|
+
# _Generates_
|
13
|
+
#
|
14
|
+
# <p>Isn’t <span class="caps">ERB</span> <strong>great</strong>?</p>
|
15
|
+
#
|
16
|
+
def textilize( s )
|
17
|
+
if s && s.respond_to?(:to_s)
|
18
|
+
RedCloth.new( s.to_s ).to_html
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias t textilize
|
23
|
+
module_function :t
|
24
|
+
module_function :textilize
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RedCloth::Formatters
|
2
|
+
module Base
|
3
|
+
|
4
|
+
def ignore(opts)
|
5
|
+
opts[:text]
|
6
|
+
end
|
7
|
+
alias_method :notextile, :ignore
|
8
|
+
|
9
|
+
def redcloth_version(opts)
|
10
|
+
p(:text => "#{opts[:prefix]}#{RedCloth::VERSION}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def inline_redcloth_version(opts)
|
14
|
+
RedCloth::VERSION::STRING
|
15
|
+
end
|
16
|
+
|
17
|
+
[:del_phrase, :sup_phrase, :sub_phrase, :span_phrase].each do |phrase_method|
|
18
|
+
method = phrase_method.to_s.split('_')[0]
|
19
|
+
define_method(phrase_method) do |opts|
|
20
|
+
"#{opts[:beginning_space]}#{self.send(method, opts)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def pba(opts)
|
27
|
+
opts.delete(:style) if filter_styles
|
28
|
+
opts.delete(:class) if filter_classes
|
29
|
+
opts.delete(:id) if filter_ids
|
30
|
+
|
31
|
+
atts = ''
|
32
|
+
opts[:"text-align"] = opts.delete(:align)
|
33
|
+
opts[:style] += ';' if opts[:style] && (opts[:style][-1..-1] != ';')
|
34
|
+
[:float, :"text-align", :"vertical-align"].each do |a|
|
35
|
+
opts[:style] = "#{a}:#{opts[a]};#{opts[:style]}" if opts[a]
|
36
|
+
end
|
37
|
+
[:"padding-right", :"padding-left"].each do |a|
|
38
|
+
opts[:style] = "#{a}:#{opts[a]}em;#{opts[:style]}" if opts[a]
|
39
|
+
end
|
40
|
+
[:style, :class, :lang, :id, :colspan, :rowspan, :title, :start, :align].each do |a|
|
41
|
+
atts << " #{a}=\"#{ html_esc(opts[a].to_s, :html_escape_attributes) }\"" if opts[a]
|
42
|
+
end
|
43
|
+
atts
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method, opts)
|
47
|
+
opts[:text] || ""
|
48
|
+
end
|
49
|
+
|
50
|
+
def before_transform(text)
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def after_transform(text)
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def formatter_methods
|
59
|
+
singleton_methods.map! {|method| method.to_sym }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
module RedCloth::Formatters::HTML
|
2
|
+
include RedCloth::Formatters::Base
|
3
|
+
|
4
|
+
[:h1, :h2, :h3, :h4, :h5, :h6, :p, :pre, :div].each do |m|
|
5
|
+
define_method(m) do |opts|
|
6
|
+
"<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
[:strong, :code, :em, :i, :b, :ins, :sup, :sub, :span, :cite].each do |m|
|
11
|
+
define_method(m) do |opts|
|
12
|
+
opts[:block] = true
|
13
|
+
"<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def hr(opts)
|
18
|
+
"<hr#{pba(opts)} />\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def acronym(opts)
|
22
|
+
opts[:block] = true
|
23
|
+
"<acronym#{pba(opts)}>#{caps(:text => opts[:text])}</acronym>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def caps(opts)
|
27
|
+
if no_span_caps
|
28
|
+
opts[:text]
|
29
|
+
else
|
30
|
+
opts[:class] = 'caps'
|
31
|
+
span(opts)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def del(opts)
|
36
|
+
opts[:block] = true
|
37
|
+
"<del#{pba(opts)}>#{opts[:text]}</del>"
|
38
|
+
end
|
39
|
+
|
40
|
+
[:ol, :ul].each do |m|
|
41
|
+
define_method("#{m}_open") do |opts|
|
42
|
+
opts[:block] = true
|
43
|
+
"#{"\n" if opts[:nest] > 1}#{"\t" * (opts[:nest] - 1)}<#{m}#{pba(opts)}>\n"
|
44
|
+
end
|
45
|
+
define_method("#{m}_close") do |opts|
|
46
|
+
"#{"\t" * (opts[:nest] - 1)}</#{m}>#{"\n" if opts[:nest] <= 1}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def li_open(opts)
|
51
|
+
"#{"\t" * opts[:nest]}<li#{pba(opts)}>#{opts[:text]}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def li_close(opts=nil)
|
55
|
+
"</li>\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
def dl_open(opts)
|
59
|
+
opts[:block] = true
|
60
|
+
"<dl#{pba(opts)}>\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
def dl_close(opts=nil)
|
64
|
+
"</dl>\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
[:dt, :dd].each do |m|
|
68
|
+
define_method(m) do |opts|
|
69
|
+
"\t<#{m}#{pba(opts)}>#{opts[:text]}</#{m}>\n"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def td(opts)
|
74
|
+
tdtype = opts[:th] ? 'th' : 'td'
|
75
|
+
"\t\t<#{tdtype}#{pba(opts)}>#{opts[:text]}</#{tdtype}>\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
def tr_open(opts)
|
79
|
+
"\t<tr#{pba(opts)}>\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def tr_close(opts)
|
83
|
+
"\t</tr>\n"
|
84
|
+
end
|
85
|
+
|
86
|
+
def table_open(opts)
|
87
|
+
"<table#{pba(opts)}>\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
def table_close(opts)
|
91
|
+
"</table>\n"
|
92
|
+
end
|
93
|
+
|
94
|
+
def bc_open(opts)
|
95
|
+
opts[:block] = true
|
96
|
+
"<pre#{pba(opts)}>"
|
97
|
+
end
|
98
|
+
|
99
|
+
def bc_close(opts)
|
100
|
+
"</pre>\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
def bq_open(opts)
|
104
|
+
opts[:block] = true
|
105
|
+
cite = opts[:cite] ? " cite=\"#{ escape_attribute opts[:cite] }\"" : ''
|
106
|
+
"<blockquote#{cite}#{pba(opts)}>\n"
|
107
|
+
end
|
108
|
+
|
109
|
+
def bq_close(opts)
|
110
|
+
"</blockquote>\n"
|
111
|
+
end
|
112
|
+
|
113
|
+
def link(opts)
|
114
|
+
"<a href=\"#{escape_attribute opts[:href]}\"#{pba(opts)}>#{opts[:name]}</a>"
|
115
|
+
end
|
116
|
+
|
117
|
+
def image(opts)
|
118
|
+
opts.delete(:align)
|
119
|
+
opts[:alt] = opts[:title]
|
120
|
+
img = "<img src=\"#{escape_attribute opts[:src]}\"#{pba(opts)} alt=\"#{escape_attribute opts[:alt].to_s}\" />"
|
121
|
+
img = "<a href=\"#{escape_attribute opts[:href]}\">#{img}</a>" if opts[:href]
|
122
|
+
img
|
123
|
+
end
|
124
|
+
|
125
|
+
def footno(opts)
|
126
|
+
opts[:id] ||= opts[:text]
|
127
|
+
%Q{<sup class="footnote" id=\"fnr#{opts[:id]}\"><a href=\"#fn#{opts[:id]}\">#{opts[:text]}</a></sup>}
|
128
|
+
end
|
129
|
+
|
130
|
+
def fn(opts)
|
131
|
+
no = opts[:id]
|
132
|
+
opts[:id] = "fn#{no}"
|
133
|
+
opts[:class] = ["footnote", opts[:class]].compact.join(" ")
|
134
|
+
"<p#{pba(opts)}><a href=\"#fnr#{no}\"><sup>#{no}</sup></a> #{opts[:text]}</p>\n"
|
135
|
+
end
|
136
|
+
|
137
|
+
def snip(opts)
|
138
|
+
"<pre#{pba(opts)}><code>#{opts[:text]}</code></pre>\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
def quote1(opts)
|
142
|
+
"‘#{opts[:text]}’"
|
143
|
+
end
|
144
|
+
|
145
|
+
def quote2(opts)
|
146
|
+
"“#{opts[:text]}”"
|
147
|
+
end
|
148
|
+
|
149
|
+
def multi_paragraph_quote(opts)
|
150
|
+
"“#{opts[:text]}"
|
151
|
+
end
|
152
|
+
|
153
|
+
def ellipsis(opts)
|
154
|
+
"#{opts[:text]}…"
|
155
|
+
end
|
156
|
+
|
157
|
+
def emdash(opts)
|
158
|
+
"—"
|
159
|
+
end
|
160
|
+
|
161
|
+
def endash(opts)
|
162
|
+
" – "
|
163
|
+
end
|
164
|
+
|
165
|
+
def arrow(opts)
|
166
|
+
"→"
|
167
|
+
end
|
168
|
+
|
169
|
+
def dim(opts)
|
170
|
+
opts[:text].gsub!('x', '×')
|
171
|
+
opts[:text].gsub!("'", '′')
|
172
|
+
opts[:text].gsub!('"', '″')
|
173
|
+
opts[:text]
|
174
|
+
end
|
175
|
+
|
176
|
+
def trademark(opts)
|
177
|
+
"™"
|
178
|
+
end
|
179
|
+
|
180
|
+
def registered(opts)
|
181
|
+
"®"
|
182
|
+
end
|
183
|
+
|
184
|
+
def copyright(opts)
|
185
|
+
"©"
|
186
|
+
end
|
187
|
+
|
188
|
+
def entity(opts)
|
189
|
+
"&#{opts[:text]};"
|
190
|
+
end
|
191
|
+
|
192
|
+
def amp(opts)
|
193
|
+
"&"
|
194
|
+
end
|
195
|
+
|
196
|
+
def gt(opts)
|
197
|
+
">"
|
198
|
+
end
|
199
|
+
|
200
|
+
def lt(opts)
|
201
|
+
"<"
|
202
|
+
end
|
203
|
+
|
204
|
+
def br(opts)
|
205
|
+
if hard_breaks == false
|
206
|
+
"\n"
|
207
|
+
else
|
208
|
+
"<br#{pba(opts)} />\n"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def quot(opts)
|
213
|
+
"""
|
214
|
+
end
|
215
|
+
|
216
|
+
def squot(opts)
|
217
|
+
"’"
|
218
|
+
end
|
219
|
+
|
220
|
+
def apos(opts)
|
221
|
+
"'"
|
222
|
+
end
|
223
|
+
|
224
|
+
def html(opts)
|
225
|
+
"#{opts[:text]}\n"
|
226
|
+
end
|
227
|
+
|
228
|
+
def html_block(opts)
|
229
|
+
inline_html(:text => "#{opts[:indent_before_start]}#{opts[:start_tag]}#{opts[:indent_after_start]}") +
|
230
|
+
"#{opts[:text]}" +
|
231
|
+
inline_html(:text => "#{opts[:indent_before_end]}#{opts[:end_tag]}#{opts[:indent_after_end]}")
|
232
|
+
end
|
233
|
+
|
234
|
+
def notextile(opts)
|
235
|
+
if filter_html
|
236
|
+
html_esc(opts[:text], :html_escape_preformatted)
|
237
|
+
else
|
238
|
+
opts[:text]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def inline_html(opts)
|
243
|
+
if filter_html
|
244
|
+
html_esc(opts[:text], :html_escape_preformatted)
|
245
|
+
else
|
246
|
+
"#{opts[:text]}" # nil-safe
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def ignored_line(opts)
|
251
|
+
opts[:text] + "\n"
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
# escapement for regular HTML (not in PRE tag)
|
257
|
+
def escape(text)
|
258
|
+
html_esc(text)
|
259
|
+
end
|
260
|
+
|
261
|
+
# escapement for HTML in a PRE tag
|
262
|
+
def escape_pre(text)
|
263
|
+
html_esc(text, :html_escape_preformatted)
|
264
|
+
end
|
265
|
+
|
266
|
+
# escaping for HTML attributes
|
267
|
+
def escape_attribute(text)
|
268
|
+
html_esc(text, :html_escape_attributes)
|
269
|
+
end
|
270
|
+
|
271
|
+
def after_transform(text)
|
272
|
+
text.chomp!
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
def before_transform(text)
|
277
|
+
clean_html(text) if sanitize_html
|
278
|
+
end
|
279
|
+
|
280
|
+
# HTML cleansing stuff
|
281
|
+
BASIC_TAGS = {
|
282
|
+
'a' => ['href', 'title'],
|
283
|
+
'img' => ['src', 'alt', 'title'],
|
284
|
+
'br' => [],
|
285
|
+
'i' => nil,
|
286
|
+
'u' => nil,
|
287
|
+
'b' => nil,
|
288
|
+
'pre' => nil,
|
289
|
+
'kbd' => nil,
|
290
|
+
'code' => ['lang'],
|
291
|
+
'cite' => nil,
|
292
|
+
'strong' => nil,
|
293
|
+
'em' => nil,
|
294
|
+
'ins' => nil,
|
295
|
+
'sup' => nil,
|
296
|
+
'sub' => nil,
|
297
|
+
'del' => nil,
|
298
|
+
'table' => nil,
|
299
|
+
'tr' => nil,
|
300
|
+
'td' => ['colspan', 'rowspan'],
|
301
|
+
'th' => nil,
|
302
|
+
'ol' => ['start'],
|
303
|
+
'ul' => nil,
|
304
|
+
'li' => nil,
|
305
|
+
'p' => nil,
|
306
|
+
'h1' => nil,
|
307
|
+
'h2' => nil,
|
308
|
+
'h3' => nil,
|
309
|
+
'h4' => nil,
|
310
|
+
'h5' => nil,
|
311
|
+
'h6' => nil,
|
312
|
+
'blockquote' => ['cite'],
|
313
|
+
'notextile' => nil
|
314
|
+
}
|
315
|
+
|
316
|
+
# Clean unauthorized tags.
|
317
|
+
def clean_html( text, allowed_tags = BASIC_TAGS )
|
318
|
+
text.gsub!( /<!\[CDATA\[/, '' )
|
319
|
+
text.gsub!( /<(\/*)([A-Za-z]\w*)([^>]*?)(\s?\/?)>/ ) do |m|
|
320
|
+
raw = $~
|
321
|
+
tag = raw[2].downcase
|
322
|
+
if allowed_tags.has_key? tag
|
323
|
+
pcs = [tag]
|
324
|
+
allowed_tags[tag].each do |prop|
|
325
|
+
['"', "'", ''].each do |q|
|
326
|
+
q2 = ( q != '' ? q : '\s' )
|
327
|
+
if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
|
328
|
+
attrv = $1
|
329
|
+
next if (prop == 'src' or prop == 'href') and not attrv =~ %r{^(http|https|ftp):}
|
330
|
+
pcs << "#{prop}=\"#{attrv.gsub('"', '\\"')}\""
|
331
|
+
break
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end if allowed_tags[tag]
|
335
|
+
"<#{raw[1]}#{pcs.join " "}#{raw[4]}>"
|
336
|
+
else # Unauthorized tag
|
337
|
+
if block_given?
|
338
|
+
yield m
|
339
|
+
else
|
340
|
+
''
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|