package Foorum::Formatter::BBCode2; use strict; use warnings; our $VERSION = '1.001000'; use strict; use warnings; our @bbcode_tags = qw(code quote b u i color size list url email img font align flash music video); sub new { my ( $class, $args ) = @_; $args ||= {}; $class->_croak('Options must be a hash reference') if ref($args) ne 'HASH'; my $self = {}; bless $self, $class; $self->_init($args) or return; return $self; } sub _init { my ( $self, $args ) = @_; my %html_tags = ( code => '
Code:
' . '
%s
', quote => '
%s
' . '
%s
', b => '%s', u => '%s', i => '%s', color => '%s', size => '%s', url => '%s', email => '%s', img => '', ul => '', ol_number => '
    %s
', ol_alpha => '
    %s
', font => '%s', align => '
%s
', flash => q!
!, ); my %options = ( allowed_tags => \@bbcode_tags, html_tags => \%html_tags, stripscripts => 1, linebreaks => 0, %{$args}, ); $self->{options} = \%options; return $self; } # Parse the input! sub parse { my ( $self, $bbcode ) = @_; return if ( !defined $bbcode ); $self->{_stack} = (); $self->{_in_code_block} = 0; $self->{_skip_nest} = ''; $self->{_nest_count} = 0; $self->{_nest_count_stack} = 0; $self->{_dont_nest} = [ 'code', 'url', 'email', 'img' ]; $self->{bbcode} = ''; $self->{html} = ''; $self->{bbcode} = $bbcode; my $input = $bbcode; main: while (1) { # End tag if ( $input =~ /^(\[\/[^\]]+\])/s ) { my $end = lc $1; if (( $self->{_skip_nest} ne '' && $end ne "[/$self->{_skip_nest}]" ) || ( $self->{_in_code_block} && '[/code]' ne $end ) ) { _content( $self, $end ); } else { _end_tag( $self, $end ); } $input = $'; } # Opening tag elsif ( $input =~ /^(\[[^\]]+\])/s ) { if ( $self->{_in_code_block} ) { _content( $self, $1 ); } else { _open_tag( $self, $1 ); } $input = $'; } # None BBCode content till next tag elsif ( $input =~ /^([^\[]+)/s ) { _content( $self, $1 ); $input = $'; } # BUG #14138 unmatched bracket, content till end of input elsif ( $input =~ /^(.+)$/s ) { _content( $self, $1 ); $input = $'; } # Now what? else { last main if ( !$input ); # We're at the end now, stop parsing! } } $self->{html} = join( '', @{ $self->{_stack} } ); return $self->{html}; } sub _open_tag { my ( $self, $open ) = @_; my ( $tag, $rest ) = $open =~ m/\[([^=\]]+)(.*)?\]/s; # Don't do this! ARGH! $tag = lc $tag; if ( _dont_nest( $self, $tag ) && 'img' eq $tag ) { $self->{_skip_nest} = $tag; } if ( $self->{_skip_nest} eq $tag ) { $self->{_nest_count}++; $self->{_nest_count_stack}++; } $self->{_in_code_block}++ if ( 'code' eq $tag ); push @{ $self->{_stack} }, '[' . $tag . $rest . ']'; } sub _content { my ( $self, $content ) = @_; $content =~ s|\r*||gs; $content =~ s|\n|
\n|gs if ( $self->{options}->{linebreaks} && $self->{_in_code_block} == 0 ); push @{ $self->{_stack} }, $content; } sub _end_tag { my ( $self, $end ) = @_; my ( $tag, $arg ); my @buf = ($end); if ( "[/$self->{_skip_nest}]" eq $end && $self->{_nest_count} > 1 ) { push @{ $self->{_stack} }, $end; $self->{_nest_count}--; return; } $self->{_in_code_block} = 0 if ( '[/code]' eq $end ); # Loop through the stack while (1) { my $item = pop( @{ $self->{_stack} } ); push @buf, $item; if ( !defined $item ) { map { push @{ $self->{_stack} }, $_ if ($_) } reverse @buf; last; } if ( "[$self->{_skip_nest}]" eq "$item" ) { $self->{_nest_count_stack}--; next if ( $self->{_nest_count_stack} > 0 ); } $self->{_nest_count}-- if ( "[/$self->{_skip_nest}]" eq $end && $self->{_nest_count} > 0 ); if ( $item =~ /\[([^=\]]+).*\]/s ) { $tag = $1; if ( $tag && $end eq "[/$tag]" ) { push @{ $self->{_stack} }, ( _is_allowed( $self, $tag ) ) ? _do_BB( $self, @buf ) : reverse @buf; # Clear the _skip_nest? $self->{_skip_nest} = '' if ( defined $self->{_skip_nest} && $tag eq $self->{_skip_nest} ); last; } } } $self->{_nest_count_stack} = 0; } sub _do_BB { my ( $self, @buf ) = @_; my ( $tag, $attr ); my $html; # Get the opening tag my $open = pop(@buf); # We prefer to read in non-reverse way @buf = reverse @buf; # Closing tag is kinda useless, pop it pop(@buf); # Rest should be content; my $content = join( ' ', @buf ); # What are we dealing with anyway? Any attributes maybe? if ( $open =~ /\[([^=\]]+)=?([^\]]+)?]/ ) { $tag = $1; $attr = $2; } # custom if ( 'music' eq $tag ) { # patch for music if ( $content =~ /\.(ram|rmm|mp3|mp2|mpa|ra|mpga)$/ ) { $html = qq!
!; } elsif ( $content =~ /\.(rm|mpg|mpv|mpeg|dat)$/ ) { $html = qq!
!; } elsif ( $content =~ /\.(wma|mpa)$/ ) { $html = qq!
!; } elsif ( $content =~ /\.(asf|asx|avi|wmv)$/ ) { $html = qq!
!; } return $html; } elsif ( 'video' eq $tag ) { if ( $content =~ /^http\:\/\/www.youtube.com\/v\// ) { $html = qq!
!; } return $html; } elsif ( 'size' eq $tag ) { $attr = 8 if ( $attr < 8 ); # validation $attr = 16 if ( $attr > 16 ); $html = sprintf( $self->{options}->{html_tags}->{size}, $attr, $content ); return $html; } # Kludgy way to handle specific BBCodes ... if ( 'quote' eq $tag ) { $html = sprintf( $self->{options}->{html_tags}->{quote}, ($attr) ? "$attr wrote:" : 'Quote:', $content ); } elsif ( 'code' eq $tag ) { $html = sprintf( $self->{options}->{html_tags}->{code}, _code($content) ); } elsif ( 'list' eq $tag ) { $html = _list( $self, $attr, $content ); } elsif ( ( 'email' eq $tag || 'url' eq $tag ) && !$attr ) { $html = sprintf( $self->{options}->{html_tags}->{$tag}, $content, $content ); } elsif ($attr) { $attr =~ s/^(.*?)[\"\'].*?$/$1/isg; $html = sprintf( $self->{options}->{html_tags}->{$tag}, $attr, $content ); } else { $html = sprintf( $self->{options}->{html_tags}->{$tag}, $content ); } # Return ... return $html; } sub _is_allowed { my ( $self, $check ) = @_; map { return 1 if ( $_ eq $check ); } @{ $self->{options}->{allowed_tags} }; return 0; } sub _dont_nest { my ( $self, $check ) = @_; map { return 1 if ( $_ eq $check ); } @{ $self->{_dont_nest} }; return 0; } sub _code { my $code = shift; $code =~ s|^\s+?[\n\r]+?||; $code =~ s|<|\<|g; $code =~ s|>|\>|g; $code =~ s|\[|\[|g; $code =~ s|\]|\]|g; $code =~ s| |\ |g; $code =~ s|\n|
|g; return $code; } sub _list { my ( $self, $attr, $content ) = @_; $content =~ s|^
[\s\r\n]*|\n|s; $content =~ s|\[\*\]([^(\[]+)|_list_removelastbr($1)|egs; $content =~ s|
$|\n|s; if ($attr) { return sprintf( $self->{options}->{html_tags}->{ol_number}, $content ) if ( $attr =~ /^\d/ ); return sprintf( $self->{options}->{html_tags}->{ol_alpha}, $content ) if ( $attr =~ /^\D/ ); } else { return sprintf( $self->{options}->{html_tags}->{ul}, $content ); } } sub _list_removelastbr { my $content = shift; $content =~ s|
[\s\r\n]*$||; $content =~ s|^\s*||; $content =~ s|\s*$||; return "
  • $content
  • \n"; } sub _croak { my ( $class, @error ) = @_; require Carp; Carp::croak(@error); } 1;