BBCode::Tag - Perl representation of a BBCode tag


BBCode-Parser documentation Contained in the BBCode-Parser distribution.

Index


Code Index:

NAME

Top

BBCode::Tag - Perl representation of a BBCode tag

DESCRIPTION

Top

See the documentation on BBCode::Parser for an overview of the typical usage of this package.

GENERAL USE

Top

METHODS

new

	$parser = BBCode::Parser->new(...);
	$tag = BBCode::Tag->new($parser, 'B');

Called as a class method. Takes three or more parameters: a class name (ignored), a BBCode::Parser object, the tag to be created, and any initial parameters. Returns a newly constructed tag of the appropriate subclass.

Initial parameters can be provided in one of two ways:

Example:

	$url = BBCode::Tag->new(
		$parser,
		'URL',
		# Sets the default parameter (style 1)
		'http://www.example.com/',
		# Sets the FOLLOW parameter
		[ 'FOLLOW', '1' ],
	);
	$text = BBCode::Tag->new(
		$parser,
		'TEXT',
		# Sets the default parameter (style 2)
		[ undef, 'Example.com' ],
	);
	$url->pushBody($text);

parser

	$parser = $tag->parser();

Returns the BBCode::Parser object that this tag was constructed with.

isPermitted

	if($tag->isPermitted('URL')) {
		# $tag can contain [URL] tags
	} else {
		# [URL] tags are forbidden
	}

Checks if the given BBCode tag is allowed in the body of this tag.

forbidTags

	$tag->forbidTags(qw(IMG URL));

Mark the given tag(s) as forbidden, so that this tag (including all its children, grandchildren, etc.) can never contain any of the forbidden tags.

At the moment, if a tag already contains one of the tags now forbidden, a warning is raised. In the future, this behavior will likely change.

body

	# Iterate over all this tag's immediate children
	my @body = $tag->body();
	foreach my $subtag (@body) { ...; }

	# Forcibly add a new child, overriding $tag->isPermitted()
	my $body = $tag->body();
	my $bold = BBCode::Tag->new($tag->parser(), 'B');
	push @$body, $bold;

Returns the list of child tags for this tag. In list context, returns a list; otherwise, returns an array reference.

CAUTION: The reference returned in scalar context is a direct pointer to a BBCode::Tag internal structure. It is possible to bypass checks on security and correctness by altering it directly.

bodyHTML

	print HANDLE $tag->bodyHTML();

Recursively converts everything inside this tag into HTML. In array context, returns the HTML line-by-line (with '\n' already appended); in scalar context, returns the HTML as one string.

Odds are that you want to use toHTML() instead.

bodyText

	print HANDLE $tag->bodyText();

Recursively converts everything inside this tag into plain text. In array context, returns the plain text line-by-line (with '\n' already appended); in scalar context, returns the text as one string.

Odds are that you want to use toText() instead.

pushBody

	$tag->pushBody(
		'Image: ',
		BBCode::Tag->new(
			$tag->parser(),
			'IMG',
			'http://www.example.org/img.png',
		)
	);

Appends one or more new child tags to this tag's body. Security and correctness checks are performed. Use eval to catch any exceptions.

If any arguments are strings, they are upgraded to virtual [TEXT] tags.

toBBCode

Converts this BBCode tree back to BBCode. The resulting "deparsed" BBCode can reveal discrepancies between what the user means vs. what BBCode::Parser thinks the user means.

In a web environment, a round-trip using toBBCode is recommended each time the user previews his/her message. This makes it easier for the user to spot troublesome code.

toHTML

Converts this BBCode tree to HTML. This is generally the entire point of using BBCode.

At the moment, only XHTML 1.0 Strict output is supported. Future versions will likely support other HTML standards.

toText

Converts this BBCode tree to plain text.

Note that the result may contain Unicode characters. It is strongly recommended that you use UTF-8 encoding whenever you store or transmit the resulting text, to prevent loss of information. You might look at the Text::Unidecode module if you want 7-bit ASCII output.

SUBCLASSING

Top

While the details of subclassing presented below are currently accurate, a number of major changes are likely (mostly dealing with the addition of new BBCode tags at runtime). The API is not yet stable and will almost certainly change in incompatible ways. Hic sunt dracones.

CLASS METHODS

Tag

Returns the name of the tag as used in BBCode. For instance, the following code prints "URL":

	my $parser = BBCode::Parser->new;
	my $tree = $parser->parse("[URL]example.com[/URL]");
	printf "%s\n", $tree->body->[0]->Tag;

The default implementation returns the final component of the object's class name. (For instance, BBCode::Tag::URL becomes "URL".) Override this in subclasses as needed.

Class

Returns a list of zero or more strings, each of which is a class that this tag belongs to (without any colon prefixes). For instance, [B] and [I] tags are both of class :INLINE, meaning that they can be found inside fellow inline tags. Therefore, both their implementations return qw(INLINE). Tag classes are listed in order from most specific to least.

For a more thorough discussion of tag classes, see "CLASSES" in BBCode::Parser.

The default implementation returns an empty list.

BodyPermitted

BodyPermitted indicates whether or not the tag can contain a body of some sort (whether it be text, more tags, or both).

The default implementation returns false.

BodyTags

Returns a list of tags and classes that are permitted or forbidden in the body of this tag. See BBCode::Parser->permit() for syntax. If this tag doesn't permit a body at all, this value is ignored.

The default implementation returns an empty list (all tags are permitted).

NamedParams

Returns a list of named parameters that can be set on this tag. By default, the order in this list determines the order in "deparsed" BBCode. Override toBBCode() if this isn't acceptable.

At the moment, parameter aliases are not available. This may change in the future.

The default implementation returns an empty list (no parameters are permitted).

RequiredParams

Returns a list of named parameters that must be set on this tag.

If the returned list contains a named parameter that doesn't exist in the NamedParams() list, then the tag cannot be used. So don't do that.

The default implementation returns whatever NamedParams() returns (all permitted parameters are required).

(At the moment, this value still doesn't do anything, despite having been there since before 0.01 was released. However, it will eventually take effect somewhere around $tag->replaceBody time as $parser->parse finishes up. I think a $tag->finalize method is in order.)

DefaultParam

Returns the name of a single parameter that is fundamental enough that it is the parameter of the tag. Returns undef if no such parameter exists.

As an example, the [URL HREF] parameter is important enough to the [URL] tag that the following two lines of BBCode are equivalent:

	[URL HREF=example.com]Link[/URL]
	[URL=example.com]Link[/URL]

In this example, DefaultParam() returns 'HREF'.

The default implementation returns undef.

OpenPre

Returns a "fudge factor" value used in the default toBBCode(). The returned string is inserted into the "deparsed" BBCode just before the opening tag.

It is STRONGLY recommended that this value should only contain whitespace.

The default implementation returns the empty string.

OpenPost

Returns a "fudge factor" value used in the default toBBCode(). The returned string is inserted into the "deparsed" BBCode just after the opening tag and before the contents begin.

It is STRONGLY recommended that this value should only contain whitespace.

The default implementation returns the empty string.

ClosePre

Returns a "fudge factor" value used in the default toBBCode(). The returned string is inserted into the "deparsed" BBCode just before the closing tag and after the contents end.

It is STRONGLY recommended that this value should only contain whitespace.

The default implementation returns the empty string.

ClosePost

Returns a "fudge factor" value used in the default toBBCode(). The returned string is inserted into the "deparsed" BBCode just after the closing tag.

It is STRONGLY recommended that this value should only contain whitespace.

The default implementation returns the empty string.

INSTANCE METHODS

validateParam

Takes three parameters: the object, the name of a parameter, and the requested value for the parameter. Returns the actual value for the parameter. Throws an exception if the requested value is entirely unacceptable.

The default implementation returns all values unchanged. Override this to perform checking on the values of named parameters.

FIXME: This API is clunky, especially for inheriting.

SEE ALSO

Top

BBCode::Parser

AUTHOR

Top

Donald King <dlking@cpan.org>


BBCode-Parser documentation Contained in the BBCode-Parser distribution.

# $Id: Tag.pm 284 2006-12-01 07:51:49Z chronos $
package BBCode::Tag;
use BBCode::Util qw(:quote :tag multilineText);
use BBCode::TagSet;
use Carp qw(croak);
use HTML::Entities ();
use strict;
use warnings;
our $VERSION = '0.34';

# Note: Due to the huge differences between using BBCode::Tag and
#       subclassing BBCode::Tag, the POD is no longer interleaved
#       with the code.  It has been moved to the end of the file.


# Class methods meant for overriding

sub Tag($):method {
	my $class = shift;
	$class = ref($class) || $class;
	$class =~ s/'/::/g;
	$class =~ s/^.*:://;
	return $class;
}

sub Class($):method {
	return ();
}

sub BodyPermitted($):method {
	return 0;
}

sub BodyTags($):method {
	return ();
}

sub NamedParams($):method {
	return ();
}

sub RequiredParams($):method {
	return shift->NamedParams;
}

sub DefaultParam($):method {
	return undef;
}

sub OpenPre($):method {
	return "";
}

sub OpenPost($):method {
	return "";
}

sub ClosePre($):method {
	return "";
}

sub ClosePost($):method {
	return "";
}


# Instance methods meant for overriding

sub validateParam($$$):method {
	return $_[2];
}


# Methods meant to be inherited

sub new:method {
	my($pkg, $parser, $tag) = splice(@_, 0, 2);

	if($pkg eq __PACKAGE__) {
		$tag = shift;
		$pkg = $parser->resolveTag($tag);
	}
	$tag = $pkg->Tag;

	croak "Tag [$tag] is not permitted by current settings"
		if not $parser->isPermitted($tag);

	my $this = (bless { parser => $parser }, $pkg)->init();

	while(@_) {
		my($k,$v) = (undef,shift);
		($k,$v) = @$v if ref $v and UNIVERSAL::isa($v,'ARRAY');
		$k = $this->DefaultParam if not defined $k or $k eq '';
		croak "No default parameter for [".$this->Tag."]" if not defined $k;
		$this->param($k, $v);
	}

	return $this;
}

sub init($):method {
	my $this = shift;

	$this->{params} = {};
	foreach($this->NamedParams) {
		$this->{params}->{$_} = undef;
	}

	if($this->BodyPermitted) {
		$this->{body} = [];
		$this->{permit} = BBCode::TagSet->new;
		$this->{forbid} = BBCode::TagSet->new;
		if($this->BodyTags) {
			$this->{permit}->add($this->BodyTags);
		} else {
			$this->{permit}->add(':ALL');
		}
	}

	return $this;
}

sub parser($):method {
	return shift->{parser};
}

sub isPermitted($$):method {
	my($this,$child) = @_;
	if(exists $this->{body}) {
		foreach(tagHierarchy($child)) {
			return 0 if $this->{forbid}->contains($_);
			return 1 if $this->{permit}->contains($_);
		}
	}
	return 0;
}

sub forbidTags($@):method {
	my $this = shift;
	if(exists $this->{body}) {
		my $set;
		if(@_ == 1 and UNIVERSAL::isa($_[0],'BBCode::TagSet')) {
			$set = shift;
		} else {
			$set = BBCode::TagSet->new(@_);
		}
		$this->{permit}->remove($set);
		$this->{forbid}->add($set);
		foreach my $child ($this->body) {
			warn qq(Nested child is now forbidden) unless $this->isPermitted($child);
			$child->forbidTags($set);
		}
	}
	return $this;
}

sub body($):method {
	my $this = shift;
	if(exists $this->{body}) {
		return @{$this->{body}} if wantarray;
		return $this->{body};
	} else {
		return () if wantarray;
		return [];
	}
}

sub bodyHTML($):method {
	return multilineText map { scalar $_->toHTML } shift->body;
}

sub bodyText($):method {
	return multilineText map { scalar $_->toText } shift->body;
}

sub pushBody($@):method {
	my $this = shift;
	croak qq(Body contents not permitted) unless $this->BodyPermitted;
	while(@_) {
		my $tag = shift;
		if(ref $tag) {
			croak qq(Expected a BBCode::Tag) unless UNIVERSAL::isa($tag, 'BBCode::Tag');
		} else {
			$tag = BBCode::Tag->new($this->{parser}, 'TEXT', [ undef, $tag ]);
		}
		croak qq(Invalid tag nesting) if not $this->isPermitted($tag);
		$tag->forbidTags($this->{forbid});
		push @{$this->{body}}, $tag;
	}
	return $this;
}

sub param($$;$):method {
	my($this,$param) = splice @_, 0, 2;

	$param = $this->DefaultParam if not defined $param or $param eq '';
	croak qq(Missing parameter name) unless defined $param;
	$param = uc $param;
	croak qq(Invalid parameter name "$param") unless exists $this->{params}->{$param};

	if(@_) {
		$this->{params}->{$param} = $this->validateParam($param,@_);
	}

	return $this->{params}->{$param};
}

sub params($):method {
	my $this = shift;
	my @ret;
	foreach my $k ($this->NamedParams) {
		next unless exists $this->{params}->{$k};
		my $v = $this->{params}->{$k};
		push @ret, $k, $v if defined $v;
	}
	return @ret if wantarray;
	return { @ret };
}

sub replace($):method {
	return $_[0];
}

sub replaceBody($):method {
	my $this = shift->replace;
	my $body = $this->body;
	@$body = grep { defined } map { $_->replaceBody } @$body;
	return $this;
}

sub isFollowed($):method {
	my $this = shift;
	my $follow = $this->parser->follow_links;
	if($follow or $this->parser->follow_override) {
		eval {
			my $f = $this->param('FOLLOW');
			$follow = $f if defined $f;
		};
	}
	return $follow;
}

sub openInNewWindow($):method {
	my $this = shift;
	my($nw,$nwo) = $this->parser->get(qw(newwindow_links newwindow_override));
	if($nwo) {
		eval {
			my $user = $this->param('NEWWINDOW');
			$nw = $user if defined $user;
		};
	}
	return $nw;
}

sub toBBCode($):method {
	my $this = shift->replace;

	my $ret = $this->OpenPre.'['.$this->Tag;

	my @p = $this->params;

	if(@p) {
		my $def = $this->DefaultParam;
		my @params;

		while(@p) {
			my($k,$v) = splice @p, 0, 2;
			if(defined $def and $def eq $k) {
				$ret .= '='.quote($v);
				$def = undef;
			} else {
				push @params, quote($k).'='.quote($v);
			}
		}

		$ret = join(", ", $ret, @params);
	}

	$ret .= ']'.$this->OpenPost;

	if($this->BodyPermitted) {
		foreach($this->body) {
			$ret .= $_->toBBCode;
		}
		$ret .= $this->ClosePre.'[/'.$this->Tag.']'.$this->ClosePost;
	}

	return multilineText $ret;
}

sub toHTML($):method {
	my $this = shift;
	my $that = $this->replace;
	if($this == $that) {
		croak qq(Not implemented);
	} else {
		return $that->toHTML;
	}
}

sub toText($):method {
	my $this = shift->replace;
	return $this->bodyText();
}

sub toLinkList($;$):method {
	my $this = shift->replace;
	my $ret = shift;
	$ret = [] if not defined $ret;
	foreach my $child ($this->body) {
		$child->toLinkList($ret);
	}
	return @$ret if wantarray;
	return $ret;
}

1;