File Coverage

blib/lib/BBCode/Tag.pm
Criterion Covered Total %
statement 154 172 89.5
branch 56 74 75.6
condition 15 21 71.4
subroutine 33 37 89.1
pod 24 31 77.4
total 282 335 84.1


line stmt bran cond sub pod time code
1             # $Id: Tag.pm 284 2006-12-01 07:51:49Z chronos $
2             package BBCode::Tag;
3 6     6   30 use BBCode::Util qw(:quote :tag multilineText);
  6         14  
  6         1674  
4 6     6   37 use BBCode::TagSet;
  6         12  
  6         139  
5 6     6   32 use Carp qw(croak);
  6         14  
  6         254  
6 6     6   33 use HTML::Entities ();
  6         13  
  6         100  
7 6     6   30 use strict;
  6         11  
  6         261  
8 6     6   31 use warnings;
  6         10  
  6         13762  
9             our $VERSION = '0.34';
10              
11             # Note: Due to the huge differences between using BBCode::Tag and
12             # subclassing BBCode::Tag, the POD is no longer interleaved
13             # with the code. It has been moved to the end of the file.
14              
15              
16             # Class methods meant for overriding
17              
18             sub Tag($):method {
19 2097     2097 1 3281 my $class = shift;
20 2097   66     7273 $class = ref($class) || $class;
21 2097         3479 $class =~ s/'/::/g;
22 2097         7677 $class =~ s/^.*:://;
23 2097         8434 return $class;
24             }
25              
26             sub Class($):method {
27 103     103 1 382 return ();
28             }
29              
30             sub BodyPermitted($):method {
31 261     261 1 1223 return 0;
32             }
33              
34             sub BodyTags($):method {
35 0     0 1 0 return ();
36             }
37              
38             sub NamedParams($):method {
39 139     139 1 425 return ();
40             }
41              
42             sub RequiredParams($):method {
43 0     0 1 0 return shift->NamedParams;
44             }
45              
46             sub DefaultParam($):method {
47 2     2 1 5 return undef;
48             }
49              
50             sub OpenPre($):method {
51 56     56 1 199 return "";
52             }
53              
54             sub OpenPost($):method {
55 56     56 1 162 return "";
56             }
57              
58             sub ClosePre($):method {
59 54     54 1 180 return "";
60             }
61              
62             sub ClosePost($):method {
63 54     54 1 128 return "";
64             }
65              
66              
67             # Instance methods meant for overriding
68              
69             sub validateParam($$$):method {
70 137     137 1 461 return $_[2];
71             }
72              
73              
74             # Methods meant to be inherited
75              
76             sub new:method {
77 270     270 1 772 my($pkg, $parser, $tag) = splice(@_, 0, 2);
78              
79 270 100       689 if($pkg eq __PACKAGE__) {
80 209         402 $tag = shift;
81 209         713 $pkg = $parser->resolveTag($tag);
82             }
83 270         1255 $tag = $pkg->Tag;
84              
85 270 100       947 croak "Tag [$tag] is not permitted by current settings"
86             if not $parser->isPermitted($tag);
87              
88 269         2060 my $this = (bless { parser => $parser }, $pkg)->init();
89              
90 269         686 while(@_) {
91 183         345 my($k,$v) = (undef,shift);
92 183 50 33     1448 ($k,$v) = @$v if ref $v and UNIVERSAL::isa($v,'ARRAY');
93 183 100 100     1008 $k = $this->DefaultParam if not defined $k or $k eq '';
94 183 100       437 croak "No default parameter for [".$this->Tag."]" if not defined $k;
95 182         580 $this->param($k, $v);
96             }
97              
98 260         770 return $this;
99             }
100              
101             sub init($):method {
102 269     269 0 363 my $this = shift;
103              
104 269         1038 $this->{params} = {};
105 269         1078 foreach($this->NamedParams) {
106 291         996 $this->{params}->{$_} = undef;
107             }
108              
109 269 100       1095 if($this->BodyPermitted) {
110 139         369 $this->{body} = [];
111 139         674 $this->{permit} = BBCode::TagSet->new;
112 139         479 $this->{forbid} = BBCode::TagSet->new;
113 139 50       575 if($this->BodyTags) {
114 139         445 $this->{permit}->add($this->BodyTags);
115             } else {
116 0         0 $this->{permit}->add(':ALL');
117             }
118             }
119              
120 269         583 return $this;
121             }
122              
123             sub parser($):method {
124 97     97 1 457 return shift->{parser};
125             }
126              
127             sub isPermitted($$):method {
128 199     199 1 314 my($this,$child) = @_;
129 199 50       589 if(exists $this->{body}) {
130 199         716 foreach(tagHierarchy($child)) {
131 583 50       1939 return 0 if $this->{forbid}->contains($_);
132 583 100       1843 return 1 if $this->{permit}->contains($_);
133             }
134             }
135 2         387 return 0;
136             }
137              
138             sub forbidTags($@):method {
139 197     197 1 306 my $this = shift;
140 197 100       527 if(exists $this->{body}) {
141 68         93 my $set;
142 68 50 33     499 if(@_ == 1 and UNIVERSAL::isa($_[0],'BBCode::TagSet')) {
143 68         99 $set = shift;
144             } else {
145 0         0 $set = BBCode::TagSet->new(@_);
146             }
147 68         302 $this->{permit}->remove($set);
148 68         286 $this->{forbid}->add($set);
149 68         376 foreach my $child ($this->body) {
150 0 0       0 warn qq(Nested child is now forbidden) unless $this->isPermitted($child);
151 0         0 $child->forbidTags($set);
152             }
153             }
154 197         326 return $this;
155             }
156              
157             sub body($):method {
158 552     552 1 1304 my $this = shift;
159 552 100       1224 if(exists $this->{body}) {
160 406 100       881 return @{$this->{body}} if wantarray;
  287         1007  
161 119         275 return $this->{body};
162             } else {
163 146 100       334 return () if wantarray;
164 130         278 return [];
165             }
166             }
167              
168             sub bodyHTML($):method {
169 94     94 1 277 return multilineText map { scalar $_->toHTML } shift->body;
  133         552  
170             }
171              
172             sub bodyText($):method {
173 5     5 1 10 return multilineText map { scalar $_->toText } shift->body;
  5         17  
174             }
175              
176             sub pushBody($@):method {
177 199     199 1 500 my $this = shift;
178 199 50       584 croak qq(Body contents not permitted) unless $this->BodyPermitted;
179 199         971 while(@_) {
180 199         423 my $tag = shift;
181 199 50       421 if(ref $tag) {
182 199 50       754 croak qq(Expected a BBCode::Tag) unless UNIVERSAL::isa($tag, 'BBCode::Tag');
183             } else {
184 0         0 $tag = BBCode::Tag->new($this->{parser}, 'TEXT', [ undef, $tag ]);
185             }
186 199 100       730 croak qq(Invalid tag nesting) if not $this->isPermitted($tag);
187 197         975 $tag->forbidTags($this->{forbid});
188 197         445 push @{$this->{body}}, $tag;
  197         742  
189             }
190 197         538 return $this;
191             }
192              
193             sub param($$;$):method {
194 568     568 0 1292 my($this,$param) = splice @_, 0, 2;
195              
196 568 100 66     2701 $param = $this->DefaultParam if not defined $param or $param eq '';
197 568 50       1103 croak qq(Missing parameter name) unless defined $param;
198 568         902 $param = uc $param;
199 568 100       1609 croak qq(Invalid parameter name "$param") unless exists $this->{params}->{$param};
200              
201 567 100       1156 if(@_) {
202 182         567 $this->{params}->{$param} = $this->validateParam($param,@_);
203             }
204              
205 560         2275 return $this->{params}->{$param};
206             }
207              
208             sub params($):method {
209 78     78 0 123 my $this = shift;
210 78         134 my @ret;
211 78         224 foreach my $k ($this->NamedParams) {
212 110 50       279 next unless exists $this->{params}->{$k};
213 110         193 my $v = $this->{params}->{$k};
214 110 100       319 push @ret, $k, $v if defined $v;
215             }
216 78 50       383 return @ret if wantarray;
217 0         0 return { @ret };
218             }
219              
220             sub replace($):method {
221 304     304 0 495 return $_[0];
222             }
223              
224             sub replaceBody($):method {
225 243     243 0 718 my $this = shift->replace;
226 243         579 my $body = $this->body;
227 243         477 @$body = grep { defined } map { $_->replaceBody } @$body;
  193         7951  
  193         751  
228 243         827 return $this;
229             }
230              
231             sub isFollowed($):method {
232 14     14 0 25 my $this = shift;
233 14         51 my $follow = $this->parser->follow_links;
234 14 100 100     70 if($follow or $this->parser->follow_override) {
235 11         19 eval {
236 11         37 my $f = $this->param('FOLLOW');
237 11 100       38 $follow = $f if defined $f;
238             };
239             }
240 14         52 return $follow;
241             }
242              
243             sub openInNewWindow($):method {
244 8     8 0 14 my $this = shift;
245 8         24 my($nw,$nwo) = $this->parser->get(qw(newwindow_links newwindow_override));
246 8 50       22 if($nwo) {
247 0         0 eval {
248 0         0 my $user = $this->param('NEWWINDOW');
249 0 0       0 $nw = $user if defined $user;
250             };
251             }
252 8         39 return $nw;
253             }
254              
255             sub toBBCode($):method {
256 56     56 1 163 my $this = shift->replace;
257              
258 56         332 my $ret = $this->OpenPre.'['.$this->Tag;
259              
260 56         280 my @p = $this->params;
261              
262 56 100       152 if(@p) {
263 23         83 my $def = $this->DefaultParam;
264 23         50 my @params;
265              
266 23         63 while(@p) {
267 35         82 my($k,$v) = splice @p, 0, 2;
268 35 100 100     182 if(defined $def and $def eq $k) {
269 16         233 $ret .= '='.quote($v);
270 16         67 $def = undef;
271             } else {
272 19         70 push @params, quote($k).'='.quote($v);
273             }
274             }
275              
276 23         87 $ret = join(", ", $ret, @params);
277             }
278              
279 56         314 $ret .= ']'.$this->OpenPost;
280              
281 56 100       189 if($this->BodyPermitted) {
282 54         152 foreach($this->body) {
283 80         379 $ret .= $_->toBBCode;
284             }
285 54         287 $ret .= $this->ClosePre.'[/'.$this->Tag.']'.$this->ClosePost;
286             }
287              
288 56         202 return multilineText $ret;
289             }
290              
291             sub toHTML($):method {
292 0     0 1 0 my $this = shift;
293 0         0 my $that = $this->replace;
294 0 0       0 if($this == $that) {
295 0         0 croak qq(Not implemented);
296             } else {
297 0         0 return $that->toHTML;
298             }
299             }
300              
301             sub toText($):method {
302 0     0 1 0 my $this = shift->replace;
303 0         0 return $this->bodyText();
304             }
305              
306             sub toLinkList($;$):method {
307 26     26 1 50 my $this = shift->replace;
308 26         119 my $ret = shift;
309 26 100       44 $ret = [] if not defined $ret;
310 26         44 foreach my $child ($this->body) {
311 25         76 $child->toLinkList($ret);
312             }
313 26 100       49 return @$ret if wantarray;
314 25         36 return $ret;
315             }
316              
317             1;
318              
319             =head1 NAME
320              
321             BBCode::Tag - Perl representation of a BBCode tag
322              
323             =head1 DESCRIPTION
324              
325             See L for an overview of
326             the typical usage of this package.
327              
328             =head1 GENERAL USE
329              
330             =head2 METHODS
331              
332             =head3 new
333              
334             $parser = BBCode::Parser->new(...);
335             $tag = BBCode::Tag->new($parser, 'B');
336              
337             Called as a class method. Takes three or more parameters: a class name
338             (ignored), a L, the tag to be created,
339             and any initial parameters. Returns a newly constructed tag of the
340             appropriate subclass.
341              
342             Initial parameters can be provided in one of two ways:
343              
344             =over
345              
346             =item *
347              
348             The value for the default parameter can be given as a plain string.
349              
350             =item *
351              
352             The value for any named parameter can be given as an anonymous array of length
353             2. The first element is the parameter name, and the second is the value. If
354             the first element is undefined or the empty string, the default parameter is
355             set instead.
356              
357             =back
358              
359             Example:
360              
361             $url = BBCode::Tag->new(
362             $parser,
363             'URL',
364             # Sets the default parameter (style 1)
365             'http://www.example.com/',
366             # Sets the FOLLOW parameter
367             [ 'FOLLOW', '1' ],
368             );
369             $text = BBCode::Tag->new(
370             $parser,
371             'TEXT',
372             # Sets the default parameter (style 2)
373             [ undef, 'Example.com' ],
374             );
375             $url->pushBody($text);
376              
377             =head3 parser
378              
379             $parser = $tag->parser();
380              
381             Returns the L that this tag was
382             constructed with.
383              
384             =head3 isPermitted
385              
386             if($tag->isPermitted('URL')) {
387             # $tag can contain [URL] tags
388             } else {
389             # [URL] tags are forbidden
390             }
391              
392             Checks if the given BBCode tag is allowed in the body of this tag.
393              
394             =head3 forbidTags
395              
396             $tag->forbidTags(qw(IMG URL));
397              
398             Mark the given tagZ<>(s) as forbidden, so that this tag (including all its
399             children, grandchildren, etc.) can never contain any of the forbidden tags.
400              
401             At the moment, if a tag already contains one of the tags now forbidden, a
402             warning is raised. In the future, this behavior will likely change.
403              
404             =head3 body
405              
406             # Iterate over all this tag's immediate children
407             my @body = $tag->body();
408             foreach my $subtag (@body) { ...; }
409              
410             # Forcibly add a new child, overriding $tag->isPermitted()
411             my $body = $tag->body();
412             my $bold = BBCode::Tag->new($tag->parser(), 'B');
413             push @$body, $bold;
414              
415             Returns the list of child tags for this tag. In list context, returns
416             a list; otherwise, returns an array reference.
417              
418             CAUTION: The reference returned in scalar context is a direct pointer to a
419             C internal structure. It is possible to bypass checks on
420             security and correctness by altering it directly.
421              
422             =head3 bodyHTML
423              
424             print HANDLE $tag->bodyHTML();
425              
426             Recursively converts everything inside this tag into HTML. In array context,
427             returns the HTML line-by-line (with '\n' already appended); in scalar context,
428             returns the HTML as one string.
429              
430             Odds are that you want to use L instead.
431              
432             =head3 bodyText
433              
434             print HANDLE $tag->bodyText();
435              
436             Recursively converts everything inside this tag into plain text. In array
437             context, returns the plain text line-by-line (with '\n' already appended); in
438             scalar context, returns the text as one string.
439              
440             Odds are that you want to use L instead.
441              
442             =head3 pushBody
443              
444             $tag->pushBody(
445             'Image: ',
446             BBCode::Tag->new(
447             $tag->parser(),
448             'IMG',
449             'http://www.example.org/img.png',
450             )
451             );
452              
453             Appends one or more new child tags to this tag's body. Security and
454             correctness checks are performed. Use C to catch any exceptions.
455              
456             If any arguments are strings, they are upgraded to virtual [TEXT] tags.
457              
458             =head3 toBBCode
459              
460             Converts this BBCode tree back to BBCode. The resulting "deparsed" BBCode can
461             reveal discrepancies between what the user means vs. what BBCode::Parser
462             thinks the user means.
463              
464             In a web environment, a round-trip using C is recommended each time
465             the user previews his/her message. This makes it easier for the user to spot
466             troublesome code.
467              
468             =head3 toHTML
469              
470             Converts this BBCode tree to HTML. This is generally the entire point of
471             using BBCode.
472              
473             At the moment, only XHTML 1.0 Strict output is supported. Future versions will
474             likely support other HTML standards.
475              
476             =head3 toText
477              
478             Converts this BBCode tree to plain text.
479              
480             Note that the result may contain Unicode characters. It is strongly
481             recommended that you use UTF-8 encoding whenever you store or transmit the
482             resulting text, to prevent loss of information. You might look at
483             L if you want 7-bit ASCII output.
484              
485             =head3 toLinkList
486              
487             foreach $link ($tag->toLinkList) {
488             my($followed,$tag,$href,$text) = @$link;
489             print " $text\n";
490             }
491              
492             Converts this BBCode tree into a list of all hyperlinks.
493              
494             Each hyperlink is itself an anonymous array of length 4. The first element
495             is a boolean that tells whether or not the link should be followed by search
496             engines (see L for
497             details). The second element is a string that holds the BBCode tag name
498             that created this hyperlink. The third element is a string that holds the
499             actual hyperlink address. The fourth element is the text content (if any)
500             describing the link.
501              
502             In scalar context, returns a reference to the array of hyperlinks. In list
503             context, returns the array itself.
504              
505             =head1 SUBCLASSING
506              
507             While the details of subclassing presented below are currently accurate, a
508             number of major changes are likely (mostly dealing with the addition of new
509             BBCode tags at runtime). The API is not yet stable and will almost certainly
510             change in incompatible ways. Hic sunt dracones.
511              
512             =head2 CLASS METHODS
513              
514             =head3 Tag
515              
516             Returns the name of the tag as used in BBCode. For instance, the
517             following code prints "URL":
518              
519             my $parser = BBCode::Parser->new;
520             my $tree = $parser->parse("[URL]example.com[/URL]");
521             printf "%s\n", $tree->body->[0]->Tag;
522              
523             The default implementation returns the final component of the object's class
524             name. (For instance, C becomes "URL".) Override this in
525             subclasses as needed.
526              
527             =head3 Class
528              
529             Returns a list of zero or more strings, each of which is a class
530             that this tag belongs to (without any colon prefixes). For instance, [B] and
531             [I] tags are both of class :INLINE, meaning that they can be found inside
532             fellow inline tags. Therefore, both their implementations return qw(INLINE).
533             Tag classes are listed in order from most specific to least.
534              
535             For a more thorough discussion of tag classes, see
536             L<"CLASSES" in BBCode::Parser|BBCode::Parser/"CLASSES">.
537              
538             The default implementation returns an empty list.
539              
540             =head3 BodyPermitted
541              
542             C indicates whether or not the tag can contain a body of some
543             sort (whether it be text, more tags, or both).
544              
545             The default implementation returns false.
546              
547             =head3 BodyTags
548              
549             Returns a list of tags and classes that are permitted or forbidden
550             in the body of this tag. See Lpermit()|BBCode::Parser/"permit">
551             for syntax. If this tag doesn't permit a body at all, this value is ignored.
552              
553             The default implementation returns an empty list (all tags are permitted).
554              
555             =head3 NamedParams
556              
557             Returns a list of named parameters that can be set on this tag.
558             By default, the order in this list determines the order in "deparsed" BBCode.
559             Override L if this isn't acceptable.
560              
561             At the moment, parameter aliases are not available. This may change in the
562             future.
563              
564             The default implementation returns an empty list (no parameters are permitted).
565              
566             =head3 RequiredParams
567              
568             Returns a list of named parameters that B be set on this tag.
569              
570             If the returned list contains a named parameter that doesn't exist in the
571             C list, then the tag cannot be used. So don't do that.
572              
573             The default implementation returns whatever C returns (all
574             permitted parameters are required).
575              
576             (At the moment, this value B doesn't do anything, despite having been
577             there since before 0.01 was released. However, it will eventually take effect
578             somewhere around $tag->replaceBody time as $parser->parse finishes up. I think
579             a $tag->finalize method is in order.)
580              
581             =head3 DefaultParam
582              
583             Returns the name of a single parameter that is fundamental
584             enough that it is I parameter of the tag. Returns C if no such
585             parameter exists.
586              
587             As an example, the C<[URL HREF]> parameter is important enough to the C<[URL]>
588             tag that the following two lines of BBCode are equivalent:
589              
590             [URL HREF=example.com]Link[/URL]
591             [URL=example.com]Link[/URL]
592              
593             In this example, C returns 'HREF'.
594              
595             The default implementation returns C.
596              
597             =head3 OpenPre
598              
599             Returns a "fudge factor" value used in the default C. The
600             returned string is inserted into the "deparsed" BBCode just before the opening
601             tag.
602              
603             It is B recommended that this value should only contain whitespace.
604              
605             The default implementation returns the empty string.
606              
607             =head3 OpenPost
608              
609             Returns a "fudge factor" value used in the default C. The
610             returned string is inserted into the "deparsed" BBCode just after the opening
611             tag and before the contents begin.
612              
613             It is B recommended that this value should only contain whitespace.
614              
615             The default implementation returns the empty string.
616              
617             =head3 ClosePre
618              
619             Returns a "fudge factor" value used in the default C. The
620             returned string is inserted into the "deparsed" BBCode just before the closing
621             tag and after the contents end.
622              
623             It is B recommended that this value should only contain whitespace.
624              
625             The default implementation returns the empty string.
626              
627             =head3 ClosePost
628              
629             Returns a "fudge factor" value used in the default C. The
630             returned string is inserted into the "deparsed" BBCode just after the closing
631             tag.
632              
633             It is B recommended that this value should only contain whitespace.
634              
635             The default implementation returns the empty string.
636              
637             =head2 INSTANCE METHODS
638              
639             =head3 validateParam
640              
641             Takes three parameters: the object, the name of a parameter, and the requested
642             value for the parameter. Returns the actual value for the parameter. Throws
643             an exception if the requested value is entirely unacceptable.
644              
645             The default implementation returns all values unchanged. Override this to
646             perform checking on the values of named parameters.
647              
648             FIXME: This API is clunky, especially for inheriting.
649              
650             =head1 SEE ALSO
651              
652             L
653              
654             =head1 AUTHOR
655              
656             Donald King Edlking@cpan.orgE
657              
658             =cut