blib/lib/HTML/BBCode.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 161 | 170 | 94.7 |
branch | 78 | 88 | 88.6 |
condition | 37 | 44 | 84.0 |
subroutine | 17 | 18 | 94.4 |
pod | 2 | 2 | 100.0 |
total | 295 | 322 | 91.6 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package HTML::BBCode; | ||||||
2 | |||||||
3 | =head1 NAME | ||||||
4 | |||||||
5 | HTML::BBCode - Perl extension for converting BBcode to HTML. | ||||||
6 | |||||||
7 | =head1 SYNOPSIS | ||||||
8 | |||||||
9 | use HTML::BBCode; | ||||||
10 | |||||||
11 | my $bbc = HTML::BBCode->new( \%options ); | ||||||
12 | my $html = $bbc->parse($bbcode); | ||||||
13 | |||||||
14 | # Input | ||||||
15 | print $bbc->{bbcode}; | ||||||
16 | |||||||
17 | # Output | ||||||
18 | print $bbc->{html}; | ||||||
19 | |||||||
20 | =head1 DESCRIPTION | ||||||
21 | |||||||
22 | C |
||||||
23 | boards- to its HTML equivalent. | ||||||
24 | |||||||
25 | Please note that, although this was the first BBCode module, it's by | ||||||
26 | far not the best nor fastest. It's also not heavilly maintained, so | ||||||
27 | you might want to look at L |
||||||
28 | |||||||
29 | =head2 METHODS | ||||||
30 | |||||||
31 | The following methods can be used | ||||||
32 | |||||||
33 | =head3 new | ||||||
34 | |||||||
35 | my $bbc = HTML::BBCode->new({ | ||||||
36 | allowed_tags => [ @bbcode_tags ], | ||||||
37 | stripscripts => 1, | ||||||
38 | linebreaks => 1, | ||||||
39 | }); | ||||||
40 | |||||||
41 | C |
||||||
42 | passed to it. The object's default configuration allows all BBCode to | ||||||
43 | be converted to the default HTML. | ||||||
44 | |||||||
45 | =head4 options | ||||||
46 | |||||||
47 | =over 5 | ||||||
48 | |||||||
49 | =item allowed_tags | ||||||
50 | |||||||
51 | Defaults to all currently know C |
||||||
52 | b, u, i, color, size, quote, code, list, url, email, img. With this | ||||||
53 | option, you can specify what BBCode tags you would like to convert. | ||||||
54 | |||||||
55 | =item stripscripts | ||||||
56 | |||||||
57 | Enabled by default, this option will remove all the XSS trickery (and | ||||||
58 | thus is probably best not to turn it off). | ||||||
59 | |||||||
60 | =item no_html | ||||||
61 | |||||||
62 | This option has been removed since version 2.0 | ||||||
63 | |||||||
64 | =item no_jslink | ||||||
65 | |||||||
66 | This option has been removed since version 2.0 | ||||||
67 | |||||||
68 | =item linebreaks | ||||||
69 | |||||||
70 | Disabled by default. | ||||||
71 | |||||||
72 | When true, will substitute linebreaks into HTML (' ') |
||||||
73 | |||||||
74 | =back | ||||||
75 | |||||||
76 | =head3 parse | ||||||
77 | |||||||
78 | my $html = $bbc->parse($bbcode); | ||||||
79 | |||||||
80 | Parses text supplied as a single scalar string and returns the HTML as | ||||||
81 | a single scalar string. | ||||||
82 | |||||||
83 | =head1 CAVEAT: API CHANGES | ||||||
84 | |||||||
85 | Please do note that the C |
||||||
86 | the new method have been removed since version 2.0 due to the XSS protection | ||||||
87 | (provided by L |
||||||
88 | break your current scripts (if you used the C |
||||||
89 | |||||||
90 | =head1 SEE ALSO | ||||||
91 | |||||||
92 | =over 4 | ||||||
93 | |||||||
94 | =item * L |
||||||
95 | |||||||
96 | =item * L |
||||||
97 | |||||||
98 | =item * L |
||||||
99 | |||||||
100 | =back | ||||||
101 | |||||||
102 | =head1 BUGS | ||||||
103 | |||||||
104 | C |
||||||
105 | |||||||
106 | =head1 AUTHOR | ||||||
107 | |||||||
108 | Menno Blom, E |
||||||
109 | |||||||
110 | =head1 COPYRIGHT AND LICENSE | ||||||
111 | |||||||
112 | Copyright (C) 2004-2009 by Menno Blom | ||||||
113 | |||||||
114 | This library is free software; you can redistribute it and/or modify | ||||||
115 | it under the same terms as Perl itself. | ||||||
116 | |||||||
117 | =cut | ||||||
118 | #------------------------------------------------------------------------------# | ||||||
119 | 17 | 17 | 563844 | use strict; | |||
17 | 42 | ||||||
17 | 890 | ||||||
120 | 17 | 17 | 126 | use warnings; | |||
17 | 35 | ||||||
17 | 579 | ||||||
121 | 17 | 17 | 10255 | use HTML::BBCode::StripScripts; | |||
17 | 60 | ||||||
17 | 61218 | ||||||
122 | |||||||
123 | our $VERSION = '2.07'; | ||||||
124 | our @bbcode_tags = qw(code quote b u i color size list url email img); | ||||||
125 | |||||||
126 | sub new { | ||||||
127 | 24 | 24 | 1 | 260 | my ($class, $args) = @_; | ||
128 | 24 | 100 | 168 | $args ||= {}; | |||
129 | 24 | 50 | 122 | $class->_croak("Options must be a hash reference") | |||
130 | if ref($args) ne 'HASH'; | ||||||
131 | 24 | 61 | my $self = {}; | ||||
132 | 24 | 71 | bless $self, $class; | ||||
133 | 24 | 50 | 124 | $self->_init($args) or return undef; | |||
134 | |||||||
135 | 24 | 95 | return $self; | ||||
136 | } | ||||||
137 | |||||||
138 | sub _init { | ||||||
139 | 24 | 24 | 56 | my ($self, $args) = @_; | |||
140 | |||||||
141 | 24 | 399 | my %html_tags = ( | ||||
142 | code => ' Code: '. |
||||||
143 | ' %s ', |
||||||
144 | quote => ' %s '. |
||||||
145 | ' %s ', |
||||||
146 | b => '%s', | ||||||
147 | u => '%s', | ||||||
148 | i => '%s', | ||||||
149 | color => '%s', | ||||||
150 | size => '%s', | ||||||
151 | url => '%s', | ||||||
152 | email => '%s', | ||||||
153 | img => '', | ||||||
154 | ul => '
|
||||||
155 | ol_number => '
|
||||||
156 | ol_alpha => '
|
||||||
157 | ); | ||||||
158 | |||||||
159 | 24 | 173 | my %options = ( | ||||
160 | allowed_tags=> \@bbcode_tags, | ||||||
161 | html_tags => \%html_tags, | ||||||
162 | stripscripts => 1, | ||||||
163 | linebreaks => 0, | ||||||
164 | 24 | 82 | %{ $args }, | ||||
165 | ); | ||||||
166 | 24 | 153 | $self->{options} = \%options; | ||||
167 | |||||||
168 | 24 | 1545 | $self->{'hss'} = HTML::BBCode::StripScripts->new({ | ||||
169 | Context => 'Flow', | ||||||
170 | AllowSrc => 1, | ||||||
171 | AllowMailto => 1, | ||||||
172 | AllowHref => 1, | ||||||
173 | AllowRelURL => 1, | ||||||
174 | EscapeFiltered => 1, | ||||||
175 | BanAllBut => [qr/a div img li ol span ul/], | ||||||
176 | Rules => { | ||||||
177 | br => 1, | ||||||
178 | img => { | ||||||
179 | required => ['src'], | ||||||
180 | 'src' => 1, | ||||||
181 | 'alt' => 1, | ||||||
182 | '*' => 0, | ||||||
183 | }, | ||||||
184 | a => { | ||||||
185 | required => ['href'], | ||||||
186 | 'href' => 1, | ||||||
187 | '*' => 0, | ||||||
188 | }, | ||||||
189 | img => { | ||||||
190 | 'src' => 1, | ||||||
191 | 'alt' => 1, | ||||||
192 | '*' => 0, | ||||||
193 | }, | ||||||
194 | div => { | ||||||
195 | class => qr{^bbcode_}, | ||||||
196 | '*' => 0, | ||||||
197 | }, | ||||||
198 | span => { | ||||||
199 | style => \&_filter_style, | ||||||
200 | '*' => 0, | ||||||
201 | }, | ||||||
202 | ol => { | ||||||
203 | style => qr/^list-style-type:lower-alpha$/, | ||||||
204 | '*' => 0, | ||||||
205 | }, | ||||||
206 | ul => 1, | ||||||
207 | li => 1, | ||||||
208 | } | ||||||
209 | }); | ||||||
210 | |||||||
211 | 24 | 8448 | return $self; | ||||
212 | } | ||||||
213 | |||||||
214 | # Parse the input! | ||||||
215 | sub parse { | ||||||
216 | 70 | 70 | 1 | 19090 | my ($self, $bbcode) = @_; | ||
217 | 70 | 100 | 215 | return if(!defined $bbcode); | |||
218 | |||||||
219 | 69 | 174 | $self->{_stack} = []; | ||||
220 | 69 | 160 | $self->{_in_code_block} = 0; | ||||
221 | 69 | 137 | $self->{_skip_nest} = ''; | ||||
222 | 69 | 115 | $self->{_nest_count} = 0; | ||||
223 | 69 | 107 | $self->{_nest_count_stack} = 0; | ||||
224 | 69 | 241 | $self->{_dont_nest} = ['code', 'url', 'email', 'img']; | ||||
225 | 69 | 154 | $self->{bbcode} = ''; | ||||
226 | 69 | 410 | $self->{html} = ''; | ||||
227 | |||||||
228 | 69 | 119 | $self->{bbcode} = $bbcode; | ||||
229 | 69 | 109 | my $input = $bbcode; | ||||
230 | |||||||
231 | main: | ||||||
232 | 69 | 89 | while(1) { | ||||
233 | # End tag | ||||||
234 | 374 | 100 | 2024 | if($input =~ /^(\[\/[^\]]+\])/s) { | |||
100 | |||||||
100 | |||||||
100 | |||||||
235 | 84 | 170 | my $end = lc $1; | ||||
236 | 84 | 100 | 66 | 1876 | if(($self->{_skip_nest} ne '' && $end ne "[/$self->{_skip_nest}]") || | ||
100 | |||||||
33 | |||||||
237 | ($self->{_in_code_block} && $end ne "[/code]")) { | ||||||
238 | 1 | 3 | _content($self, $end); | ||||
239 | } else { | ||||||
240 | 83 | 191 | _end_tag($self, $end); | ||||
241 | } | ||||||
242 | 84 | 181 | $input = $'; | ||||
243 | } | ||||||
244 | |||||||
245 | # Opening tag | ||||||
246 | elsif($input =~ /^(\[[^\]]+\])/s ) { | ||||||
247 | 100 | 100 | 233 | if($self->{_in_code_block}) { | |||
248 | 1 | 3 | _content($self, $1); | ||||
249 | } else { | ||||||
250 | 99 | 257 | _open_tag($self, $1); | ||||
251 | } | ||||||
252 | 100 | 233 | $input = $'; | ||||
253 | } | ||||||
254 | |||||||
255 | # None BBCode content till next tag | ||||||
256 | elsif($input =~ /^([^\[]+)/s) { | ||||||
257 | 118 | 245 | _content($self, $1); | ||||
258 | 118 | 239 | $input = $'; | ||||
259 | } | ||||||
260 | |||||||
261 | # BUG #14138 unmatched bracket, content till end of input | ||||||
262 | elsif($input =~ /^(.+)$/s) { | ||||||
263 | 3 | 7 | _content($self, $1); | ||||
264 | 3 | 7 | $input = $'; | ||||
265 | } | ||||||
266 | |||||||
267 | # Now what? | ||||||
268 | else { | ||||||
269 | 69 | 50 | 206 | last main if(!$input); # We're at the end now, stop parsing! | |||
270 | } | ||||||
271 | } | ||||||
272 | 69 | 110 | $self->{html} = join('', @{$self->{_stack}}); | ||||
69 | 215 | ||||||
273 | 69 | 100 | 330 | return $self->{options}->{stripscripts} ? $self->_stripscripts() : $self->{html}; | |||
274 | } | ||||||
275 | |||||||
276 | sub _open_tag { | ||||||
277 | 99 | 99 | 224 | my ($self, $open) = @_; | |||
278 | 99 | 523 | my ($tag, $rest) = $open =~ m/\[([^=\]]+)(.*)?\]/s; # Don't do this! ARGH! | ||||
279 | 99 | 184 | $tag = lc $tag; | ||||
280 | 99 | 100 | 100 | 241 | if(_dont_nest($self, $tag) && $tag eq 'img') { | ||
281 | 9 | 18 | $self->{_skip_nest} = $tag; | ||||
282 | } | ||||||
283 | 99 | 100 | 288 | if($self->{_skip_nest} eq $tag) { | |||
284 | 9 | 16 | $self->{_nest_count}++; | ||||
285 | 9 | 15 | $self->{_nest_count_stack}++; | ||||
286 | } | ||||||
287 | 99 | 100 | 216 | $self->{_in_code_block}++ if($tag eq 'code'); | |||
288 | 99 | 118 | push @{$self->{_stack}}, '['.$tag.$rest.']'; | ||||
99 | 369 | ||||||
289 | } | ||||||
290 | |||||||
291 | sub _content { | ||||||
292 | 123 | 123 | 259 | my ($self, $content) = @_; | |||
293 | 123 | 1284 | $content =~ s|\r*||gs; | ||||
294 | 123 | 100 | 100 | 484 | $content =~ s|\n| \n|gs if($self->{options}->{linebreaks} && |
||
295 | $self->{_in_code_block} == 0); | ||||||
296 | 123 | 154 | push @{$self->{_stack}}, $content; | ||||
123 | 373 | ||||||
297 | } | ||||||
298 | |||||||
299 | sub _end_tag { | ||||||
300 | 83 | 83 | 142 | my ($self, $end) = @_; | |||
301 | 83 | 97 | my ($tag, $arg); | ||||
302 | 83 | 157 | my @buf = ( $end ); | ||||
303 | |||||||
304 | 83 | 50 | 66 | 416 | if("[/$self->{_skip_nest}]" eq $end && $self->{_nest_count} > 1) { | ||
305 | 0 | 0 | push @{$self->{_stack}}, $end; | ||||
0 | 0 | ||||||
306 | 0 | 0 | $self->{_nest_count}--; | ||||
307 | 0 | 0 | return; | ||||
308 | } | ||||||
309 | |||||||
310 | 83 | 100 | 354 | $self->{_in_code_block} = 0 if($end eq '[/code]'); | |||
311 | |||||||
312 | # Loop through the stack | ||||||
313 | 83 | 113 | while(1) { | ||||
314 | 204 | 220 | my $item = pop(@{$self->{_stack}}); | ||||
204 | 391 | ||||||
315 | 204 | 330 | push @buf, $item; | ||||
316 | |||||||
317 | 204 | 100 | 566 | if(!defined $item) { | |||
318 | 2 | 100 | 7 | map { push @{$self->{_stack}}, $_ if($_) } reverse @buf; | |||
6 | 19 | ||||||
4 | 15 | ||||||
319 | 2 | 3 | last; | ||||
320 | } | ||||||
321 | |||||||
322 | |||||||
323 | 202 | 100 | 551 | if("[$self->{_skip_nest}]" eq "$item") { | |||
324 | 5 | 16 | $self->{_nest_count_stack}--; | ||||
325 | 5 | 50 | 18 | next if($self->{_nest_count_stack} > 0); | |||
326 | } | ||||||
327 | |||||||
328 | 202 | 100 | 100 | 609 | $self->{_nest_count}-- | ||
329 | if("[/$self->{_skip_nest}]" eq $end && $self->{_nest_count} > 0) ; | ||||||
330 | |||||||
331 | |||||||
332 | 202 | 100 | 829 | if($item =~ /\[([^=\]]+).*\]/s) { | |||
333 | 101 | 192 | $tag = $1; | ||||
334 | 101 | 100 | 66 | 571 | if ($tag && $end eq "[/$tag]") { | ||
335 | 81 | 100 | 99 | push @{$self->{_stack}}, (_is_allowed($self, $tag)) | |||
81 | 254 | ||||||
336 | ? _do_BB($self, @buf) | ||||||
337 | : reverse @buf; | ||||||
338 | # Clear the _skip_nest? | ||||||
339 | 81 | 100 | 66 | 469 | $self->{_skip_nest} = '' if(defined $self->{_skip_nest} && | ||
340 | $tag eq $self->{_skip_nest}); | ||||||
341 | 81 | 138 | last; | ||||
342 | } | ||||||
343 | } | ||||||
344 | } | ||||||
345 | 83 | 237 | $self->{_nest_count_stack} = 0; | ||||
346 | } | ||||||
347 | |||||||
348 | sub _do_BB { | ||||||
349 | 75 | 75 | 203 | my ($self, @buf) = @_; | |||
350 | 75 | 95 | my ($tag, $attr); | ||||
351 | 0 | 0 | my $html; | ||||
352 | |||||||
353 | # Get the opening tag | ||||||
354 | 75 | 233 | my $open = pop(@buf); | ||||
355 | # We prefer to read in non-reverse way | ||||||
356 | 75 | 120 | @buf = reverse @buf; | ||||
357 | # Closing tag is kinda useless, pop it | ||||||
358 | 75 | 93 | pop(@buf); | ||||
359 | # Rest should be content; | ||||||
360 | 75 | 176 | my $content = join(' ', @buf); | ||||
361 | |||||||
362 | # What are we dealing with anyway? Any attributes maybe? | ||||||
363 | 75 | 50 | 439 | if($open =~ /\[([^=\]]+)=?([^\]]+)?]/) { | |||
364 | 75 | 127 | $tag = $1; | ||||
365 | 75 | 146 | $attr = $2; | ||||
366 | } | ||||||
367 | |||||||
368 | # Kludgy way to handle specific BBCodes ... | ||||||
369 | 75 | 100 | 100 | 685 | if($tag eq 'quote') { | ||
100 | 100 | ||||||
100 | |||||||
100 | |||||||
100 | |||||||
370 | 8 | 100 | 64 | $html = sprintf($self->{options}->{html_tags}->{quote}, | |||
371 | ($attr) ? "$attr wrote:" | ||||||
372 | : "Quote:", | ||||||
373 | $content | ||||||
374 | ); | ||||||
375 | } elsif($tag eq 'code') { | ||||||
376 | 3 | 22 | $html = sprintf($self->{options}->{html_tags}->{code}, _code($content)); | ||||
377 | } elsif($tag eq 'list') { | ||||||
378 | 8 | 29 | $html = _list($self, $attr, $content); | ||||
379 | } elsif(($tag eq 'email' || $tag eq 'url') && !$attr) { | ||||||
380 | 4 | 24 | $html = sprintf($self->{options}->{html_tags}->{$tag}, $content,$content); | ||||
381 | } elsif ($attr) { | ||||||
382 | 24 | 159 | $html = sprintf($self->{options}->{html_tags}->{$tag}, $attr, $content); | ||||
383 | } else { | ||||||
384 | 28 | 145 | $html = sprintf($self->{options}->{html_tags}->{$tag}, $content); | ||||
385 | } | ||||||
386 | # Return ... | ||||||
387 | 75 | 224 | return $html; | ||||
388 | } | ||||||
389 | |||||||
390 | sub _is_allowed { | ||||||
391 | 81 | 81 | 126 | my ($self, $check) = @_; | |||
392 | 447 | 100 | 1258 | map { | |||
393 | 81 | 202 | return 1 if ($_ eq $check); | ||||
394 | 81 | 98 | } @{$self->{options}->{allowed_tags}}; | ||||
395 | 6 | 19 | return 0; | ||||
396 | } | ||||||
397 | |||||||
398 | sub _dont_nest { | ||||||
399 | 99 | 99 | 143 | my ($self, $check) = @_; | |||
400 | 345 | 100 | 1145 | map { | |||
401 | 99 | 228 | return 1 if($_ eq $check); | ||||
402 | 99 | 139 | } @{$self->{_dont_nest}}; | ||||
403 | 65 | 207 | return 0; | ||||
404 | } | ||||||
405 | |||||||
406 | sub _code { | ||||||
407 | 3 | 3 | 6 | my $code = shift; | |||
408 | 3 | 10 | $code =~ s|^\s+?[\n\r]+?||; | ||||
409 | 3 | 8 | $code =~ s|<|\<|g; | ||||
410 | 3 | 9 | $code =~ s|>|\>|g; | ||||
411 | 3 | 11 | $code =~ s|\[|\[|g; | ||||
412 | 3 | 12 | $code =~ s|\]|\]|g; | ||||
413 | 3 | 10 | $code =~ s| |\ |g; | ||||
414 | 3 | 9 | $code =~ s|\n| |g; |
||||
415 | 3 | 19 | return $code; | ||||
416 | } | ||||||
417 | |||||||
418 | sub _list { | ||||||
419 | 8 | 8 | 18 | my ($self, $attr, $content) = @_; | |||
420 | 8 | 18 | $content =~ s|^ [\s\r\n]*|\n|s; |
||||
421 | 8 | 43 | $content =~ s|\[\*\]([^\[]+)|_list_removelastbr($1)|egs; | ||||
18 | 65 | ||||||
422 | 8 | 20 | $content =~ s| $|\n|s; |
||||
423 | 8 | 100 | 23 | if($attr) { | |||
424 | 4 | 100 | 28 | return sprintf($self->{options}->{html_tags}->{ol_number}, $content) | |||
425 | if($attr =~ /^\d/); | ||||||
426 | 2 | 50 | 55 | return sprintf($self->{options}->{html_tags}->{ol_alpha}, $content) | |||
427 | if($attr =~ /^\D/); | ||||||
428 | } else { | ||||||
429 | 4 | 36 | return sprintf($self->{options}->{html_tags}->{ul}, $content); | ||||
430 | } | ||||||
431 | } | ||||||
432 | |||||||
433 | sub _list_removelastbr { | ||||||
434 | 18 | 18 | 38 | my $content = shift; | |||
435 | 18 | 32 | $content =~ s| [\s\r\n]*$||; |
||||
436 | 18 | 51 | $content =~ s|^\s*||; | ||||
437 | 18 | 71 | $content =~ s|\s*$||; | ||||
438 | 18 | 78 | return " |
||||
439 | } | ||||||
440 | |||||||
441 | sub _stripscripts { | ||||||
442 | 67 | 67 | 96 | my $self = shift; | |||
443 | 67 | 376 | $self->{'html'} = $self->{'hss'}->filter_html($self->{'html'}); | ||||
444 | 67 | 26575 | return $self->{'html'}; | ||||
445 | } | ||||||
446 | |||||||
447 | sub _filter_style { | ||||||
448 | 29 | 29 | 6236 | my ($filter, $tag, $attr_name, $attr_val) = @_; | |||
449 | 29 | 100 | 100 | 216 | if ($attr_val eq 'font-weight:bold' | ||
100 | |||||||
66 | |||||||
450 | or $attr_val eq 'text-decoration:underline' | ||||||
451 | or $attr_val eq 'font-style:italic' | ||||||
452 | or $attr_val eq 'list-style-type') { | ||||||
453 | 22 | 60 | return $attr_val; | ||||
454 | } | ||||||
455 | 7 | 100 | 43 | if ( my ($color) = $attr_val =~ /^color:(.*)/ ) { | |||
456 | 4 | 26 | my @html_color = qw/ | ||||
457 | black gray maroon red green lime olive yellow | ||||||
458 | navy blue purple fuchsia teal aqua silver white | ||||||
459 | /; | ||||||
460 | 4 | 50 | 16 | return $attr_val if $color =~ /^#[a-fA-F\d]{6}$/; | |||
461 | 4 | 50 | 16 | return $attr_val if $color =~ /^#[a-fA-F\d]{3}$/; | |||
462 | 4 | 50 | 10 | return $attr_val if grep { $color eq $_ } @html_color; | |||
64 | 121 | ||||||
463 | 0 | 0 | return undef; | ||||
464 | } | ||||||
465 | 3 | 100 | 16 | if ( $attr_val =~ /font-size:\d+px/ ) { | |||
466 | 2 | 7 | return $attr_val; | ||||
467 | } | ||||||
468 | 1 | 4 | return undef; | ||||
469 | } | ||||||
470 | |||||||
471 | sub _croak { | ||||||
472 | 0 | 0 | my ($class, @error) = @_; | ||||
473 | 0 | require Carp; | |||||
474 | 0 | Carp::croak(@error); | |||||
475 | } | ||||||
476 | |||||||
477 | 1; |