Parse-BBCode-0.15 000755 001750 001750 0 12361234004 12631 5 ustar 00tina tina 000000 000000 README 100644 001750 001750 604 12361234004 13552 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15
This archive contains the distribution Parse-BBCode,
version 0.15:
Module to parse BBCode and render it as HTML or text
This software is copyright (c) 2014 by Tina Müller.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
This README file was generated by Dist::Zilla::Plugin::Readme v5.019.
Changes 100644 001750 001750 7530 12361234004 14212 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15 Revision history for Perl module Parse::BBCode
0.15 2014-07-15 16:16:00 CEST
- POD, minimum perl, consistent version numbers
0.14_001 2014-07-12 23:00:00 CEST
- Bugfix: multiple quoted attributes were not parsed correctly (TLoD-Snake, github #3)
0.14 Sun May 13 16:56:03 CEST 2012
- See changes from the develop versions
- Make single quoting of attributes optional (see option attribute_quote)
0.13_004 Sat May 12 01:21:52 CEST 2012
- Bugfix/Change: Allow whitespaces after attributes
- Change: Allow underscores in attribute names
- Change: Allow single quotes in attributes
(all RT#76137)
0.13_003 Mon Oct 3 13:50:16 CEST 2011
- Bugfix: closing noparse tags
- new option: strip linebreaks before/after block tags is now configurable
0.13_002 Wed Sep 28 18:08:21 CEST 2011
- make attribute parsing inheritable
0.13_001 Sun Sep 25 17:03:51 CEST 2011
- Bugfix: another bugfix with short tags
- Bugfix: closing tags did not happen always (RT 71018)
- Bugfix: closing unclosed noparse tag (RT 71018)
- Bugfix: case insensitive search for closing noparse tags (RT 70964)
- Documentation Fix: default values (RT 70929)
0.13 Fri Aug 19 14:04:10 CEST 2011
- Bugfix: short tags broke tag before
0.12_005 Fri May 20 14:05:38 CEST 2011
New Feature: short tags like [cpan://Module|link title] (experimental)
0.12_004 Mon May 16 18:02:34 CEST 2011
New Feature: smiley processor
0.12_003 Mon May 16 13:16:46 CEST 2011
- New Feature: numbered lists [list=1][*]... [list=a]...
- New Features: url_finder, text_processor, linebreaks
0.12_002 Tue May 10 18:17:23 CEST 2011
- New Feature: pass your own information to the rendering subroutines. See
render()
0.12_001 Mon May 9 20:08:05 CEST 2011
- New Feature: Parse::BBCode::Tag: new accessors 'num' and 'level',
new method 'walk'
0.12 Sat May 7 22:50:28 CEST 2011
- Security: allow only http://... and /... links (some old and some
strange (MSIE) browsers interpret javascr
[/url]#,
q#<hr>#, $url_finder_1 ],
[ qq#http://foo/\ntest#,
qq#http://foo/\ntest#, 1, undef, 0],
[ q#
http://foo/#,
qq#<hr>
\n *smile*#, 0, $post, 1 ],
);
for my $test (@tests) {
my ($text, $exp, $url_finder, $post, $linebreaks) = @$test;
unless (defined $linebreaks) {
$linebreaks = 1;
}
my $p = Parse::BBCode->new({
url_finder => $url_finder,
text_processor => $post,
linebreaks => $linebreaks,
tags => {
'url' => 'url:%s',
},
}
);
my $title = ref $url_finder ? 'http://foo...' : 'http://foo/';
my $parsed = $p->render($text);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
#s/[\r\n]//g for ($exp, $parsed);
$text =~ s/\r/\\r/g;
$text =~ s/\n/\\n/g;
cmp_ok($parsed, 'eq', $exp, "parse '$text'");
}
}
my $p = Parse::BBCode->new({
tags => {
'list' => {
parse => 1,
class => 'block',
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
$$content =~ s/^\n+//;
$$content =~ s/\n+\z//;
return "$$content
";
},
},
'*' => {
parse => 1,
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
$$content =~ s/\n+\z//;
$$content = "%{html}a:%s
',
},
}
);
my @tests = (
[ qq#[list]\n[*]1\n[*]2\n[/list]#,
q#
# ],
[ q#[quote][*]1[*]2[/quote]#,
q#:[*]1[*]2
# ],
);
for my $test (@tests) {
my ($text, $exp, $forbid, $parser) = @$test;
$parser ||= $p;
if ($forbid) {
$parser->forbid($forbid);
}
my $parsed = $parser->render($text);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
s/[\r\n]//g for ($exp, $parsed);
$text =~ s/[\r\n]//g;
cmp_ok($parsed, 'eq', $exp, "parse '$text'");
}
$p = Parse::BBCode->new();
my $bbcode = q#start [b]1[/b][b]2[b]3[/b][b]4[/b] [b]5 [b]6[/b] [/b] [/b]#;
my $tree = $p->parse($bbcode);
my $tag = $tree->get_content->[3]->get_content->[1];
my $num = $tag->get_num;
my $level = $tag->get_level;
cmp_ok($num, '==', 3, "get_num");
cmp_ok($level, '==', 2, "get_level");
$p = Parse::BBCode->new({
tags => {
code => {
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
my $title = Parse::BBCode::escape_html($attr);
my $code = Parse::BBCode::escape_html($$content);
my $aid = $parser->get_params->{article_id};
my $cid = $tag->get_num;
return <<"EOM";
',
},
text_processor => sub {
my ($text) = @_;
$text = uc $text;
return Parse::BBCode::escape_html($text);
},
});
@tests = (
[ qq#:-)[b]bold
:-)[/b] :-(\n:-P :-'|\ntest:-P end#,
qq#BOLD<HR>
\n
\nTEST:-P END# ],
[q#:-) :-)#,
q#
#],
);
for my $test (@tests) {
my ($text, $exp) = @$test;
my $parsed = $p->render($text);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
$text =~ s/\r/\\r/g;
$text =~ s/\n/\\n/g;
cmp_ok($parsed, 'eq', $exp, "parse '$text'");
}
Makefile.PL 100644 001750 001750 2505 12361234004 14666 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15
# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v5.019.
use strict;
use warnings;
use ExtUtils::MakeMaker 6.30;
my %WriteMakefileArgs = (
"ABSTRACT" => "Module to parse BBCode and render it as HTML or text",
"AUTHOR" => "Tina Mueller
] test [thread=1]title
[/thread] end#,
q#test Thread: title <hr> (1) test Thread: title <hr> (1) end# ],
[ qq#test [thread://] end#,
q#test [thread://] end# ],
[ qq#[b]test[/b] [thread://1] [i]end[/i]#,
q#test Thread: 1 (1) end# ],
[ qq#[b]test[/b] [thread=1]test[/thread] [thread://1] [i]end[/i]#,
q#test Thread: test (1) Thread: 1 (1) end# ],
[ qq#[doc=perlipc]ipc[/doc] [doc://perlipc]#,
q#perlipc.html:ipc perlipc.html:perlipc# ],
);
for my $test (@tests) {
my ($text, $exp, $forbid, $parser) = @$test;
$parser ||= $p;
if ($forbid) {
$parser->forbid($forbid);
}
my $parsed = $parser->render($text);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
s/[\r\n]//g for ($exp, $parsed);
$text =~ s/[\r\n]//g;
cmp_ok($parsed, 'eq', $exp, "parse '$text'");
}
04_parse.t 100644 001750 001750 17070 12361234004 15004 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 86;
use Test::NoWarnings;
use Parse::BBCode;
use strict;
use warnings;
my %tag_def_html = (
perlmonks => '%{parse}s',
);
eval {
require
Email::Valid;
};
my $email_valid = $@ ? 0 : 1;
#$email_valid = 0;
my $bbc2html = Parse::BBCode->new({
tags => {
Parse::BBCode::HTML->defaults,
%tag_def_html,
'img' => '',
},
}
);
my $bbc2html_sq = Parse::BBCode->new({
tags => {
Parse::BBCode::HTML->defaults,
%tag_def_html,
'img' => '
',
},
attribute_quote => q/'/,
}
);
my $bbc2html_sdq = Parse::BBCode->new({
tags => {
Parse::BBCode::HTML->defaults,
%tag_def_html,
'img' => '
',
},
attribute_quote => q/'"/,
}
);
my $bbc2html2 = Parse::BBCode->new({
close_open_tags => 1,
escapes => {
Parse::BBCode::HTML->default_escapes,
},
});
my $bbc2html_block = Parse::BBCode->new({
tags => {
Parse::BBCode::HTML->defaults,
%tag_def_html,
'' => sub {
my $outer = $_[1];
my $block = $outer->get_class eq 'block' ? 1 : 0;
my $text = Parse::BBCode::escape_html($_[2]);
if ($block) {
$text =~ s[ (\r?\n|\r) (\r?\n|\r)* ]
[if ($2) { "
" } else { "
\n" } ]exg;
}
else {
$text =~ s[ (\r?\n|\r) ][
]xg;
}
$text;
},
},
}
);
my $pns = Parse::BBCode->new({
tags => {
b => '%s',
},
strict_attributes => 0,
}
);
my @tests = (
[ q#[img://23]#,
q#[img://23]# ],
[ q#[img=foo align=center]test[/img]#,
q## ],
[ q#[img=foo align='center']test[/img]#,
q#
#, undef, $bbc2html_sq ],
[ q#[img=foo align='center']test[/img]#,
q#
#, undef, $bbc2html_sdq ],
[ q#[img=foo align="center" ]test[/img]#,
q#
# ],
[ q#[url=/test]foo[/url] bla [url=/test2]foo2[/url]#,
q#foo bla foo2#],
[ q#[B]bold? [test#,
q#[B]bold? [test# ],
[ q#[B]bold[/B]#,
q#bold# ],
[ q#[b]bold[/B]#,
q#bold# ],
[ q#[b foo bar]bold[/B]#,
q#bold#, undef, $pns],
[ q#[i=23]italic [b]bold italic [/b][/i]# . "$/$/",
q#italic bold italic <html>
# ],
[ q#[U][noparse][u][c][/noparse][/u]# . "$/$/",
q#<html>[u][c]
# ],
[ q#[img=foo.jpg]desc [/img]#,
q## ],
[ q#[url=javascript:alert(123)]foo [i]italic[/i][/url]#,
q#[url=javascript:alert(123)]foo <html>italic[/url]# ],
[ q#[url=http://foo]foo [i]italic[/i][/url]#,
q#foo <html>italic# ],
[ q#[email=no"mail]mail [i]me[/i][/email]#,
$email_valid ? q#mail me# : q#mail me# ],
[ q#[email="test
# ],
[ q#[quote=who]cite <>[/quote]#,
q#
# ],
[ q#[noparse]foo[b][/noparse]#,
q#foo[b]# ],
[ q#[noparse]foo[b][/NOPARSE]#,
q#foo[b]# ],
[ q#[code]foo[code]bar[/code][/code]#,
q#
newline
inner
newline#, undef, $bbc2html_block ],
[ qq#[url=http://foo/][url=http://bar/]test[/url][/url]#,
q#[url=http://bar/]test[/url]#, ],
[ q#[url=relative]test[/url]#,
q#[url=relative]test[/url]#, ],
[ q#0#,
q#0#, ],
[ q# [] #,
q# [] #, ],
[ q#[b]test[/i]#,
q#test[/i]#, undef, $bbc2html2, 1],
[ q#[noparse="bar"]test[/noparse]#,
q#test#],
[ q#[noparse="bar" baz="boo"]test[/noparse]#,
q#test#],
[ q#[noparse="bar" bar="baz" baz="boo"]test[/noparse]#,
q#test#],
);
for my $test (@tests) {
my ($text, $exp, $forbid, $parser, $error) = @$test;
$error = 0 unless defined $error;
$parser ||= $bbc2html;
if ($forbid) {
$parser->forbid($forbid);
}
my $parsed = $parser->render($text);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
s/[\r\n]//g for ($exp, $parsed);
$text =~ s/[\r\n]//g;
cmp_ok($parser->get_error ? 1 : 0, '==', $error, "error $text");
cmp_ok($parsed, 'eq', $exp, "parse '$text'");
if ($forbid) {
$parser->permit($forbid);
}
}
eval {
my $parsed = $bbc2html->render();
};
my $error = $@;
#warn __PACKAGE__.':'.__LINE__.": <<$@>>\n";
cmp_ok($error, '=~', 'Missing input', "Missing input for render()");
$bbc2html->permit('foobar');
my $allowed = $bbc2html->get_allowed;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$allowed], ['allowed']);
ok(
(!grep { $_ eq 'foobar' } @$allowed),
"permit() an unsupported tag");
my %tags = Parse::BBCode->defaults;
my $bb1 = Parse::BBCode->new({ tags => \%tags });
my $bb2 = Parse::BBCode->new({ tags => \%tags });
my $render1 = $bb1->render("\n");
my $render2 = $bb2->render("\n");
cmp_ok($render2, 'eq', $render1, "don't change parameter hash");
10_xhtml.t 100644 001750 001750 6254 12361234004 15005 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 22;
use Test::NoWarnings;
use Parse::BBCode::XHTML;
use strict;
use warnings;
eval {
require
Email::Valid;
};
my $email_valid = $@ ? 0 : 1;
#$email_valid = 0;
my $parser = Parse::BBCode::XHTML->new();
my @tests = (
[ q#[B]bold? [test#,
q#[B]bold? [test# ],
[ q#[i=23]italic [b]bold italic [/b][/i]# . "$/$/",
q#italic bold italic <html>
# ],
[ q#[U][noparse][u][c][/noparse][/u]# . "$/$/",
q#<html>[u][c]
# ],
[ q#[img=/foo.jpg]desc [/img]#,
q## ],
[ q#[url=javascript:alert(123)]foo [i]italic[/i][/url]#,
q#[url=javascript:alert(123)]foo <html>italic[/url]# ],
[ q#[url=http://foo]foo [i]italic[/i][/url]#,
q#foo <html>italic# ],
[ q#[email=no"mail]mail [i]me[/i][/email]#,
$email_valid ? q#mail me# : q#mail me# ],
[ q#[email="test
# ],
[ q#[quote=who]cite[/quote]#,
q#%{}s
"
},
},
pre => {
code => sub {
""
},
},
a => '%{parse}s',
b => '%{parse}s',
c => '
"
},
},
pre => {
code => sub {
"
"
},
},
a => '%{parse}s',
b => '%{parse}s',
c => '
}],
[q{[a][code][a][c][/code][/a]}, q{
}],
[q{[a][code][/a][/code][/a]}, q{
}],
[q{[a][b][code][/a][/b][c][/code][/b][/a]},q{
}],
);
for (@tests){
my $parsed = $bbc2html->render($_->[0]);
is $parsed, $_->[1], $_->[0];
}
11_markdown.t 100644 001750 001750 4433 12361234004 15471 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 12;
use Test::NoWarnings;
use_ok('Parse::BBCode::Markdown');
use strict;
use warnings;
my $p = Parse::BBCode::Markdown->new({
});
my @tests = (
[ q#[size=7]big [b]bold[/b] text[/size]#,
q#big *bold* text# ],
[ q#[url=http://foo/]interesting [b]bold[/b] link[/url]#,
q#[interesting *bold* link](http://foo/)# ],
[ q#[url="http://foo/"]interesting [b]bold[/b] link[/url]#,
q#[interesting *bold* link](http://foo/)# ],
[ q#[url=/foo]interesting [b]bold[/b] link[/url]#,
q#[interesting *bold* link](/foo)# ],
[ q#[code=perl]say "foo";[/code]#,
qq#Code perl:\n--------------------\n| say "foo";\n--------------------# ],
# TODO
# [ q#[list=1][*]first[*]second[*]third[/list]#,
# q#
# ],
# [ q#[list=1][*]first with [url]foo[/url][*]second[*]third[/list]#,
# q#
# ],
# [ q#[list=1][*]first[*]second with [url]foo[/url][*]third[/list]#,
# q#
# ],
# [ q#[list=1][*]first[*]second with [url]foo[*]third[/list]#,
# q#
# ],
# [ q#[list=1][*]first[*]second with [url]foo and [b]bold[/b][*]third[/list]#,
# q#
# ],
[ q#[img]/path/to/image.png[/img]#,
q## ],
[ q#[img=/path/to/image.png]description[/img]#,
q## ],
[ q#[img=/path/to/image.png]description [b]with bold[/b][/img]#,
q## ],
[ qq#text [quote="foo"][quote="bar"]inner quote[/quote]outer quote[/quote]#,
qq#text foo:\n> bar:\n>> inner quote\n> outer quote\n# ],
[ q#[quote="admin@2008-06-27 19:00:25"][quote="foo@2007-08-13 22:12:32"]test[/quote]test[/quote]#,
qq#admin\@2008-06-27 19:00:25:\n> foo\@2007-08-13 22:12:32:\n>> test\n> test\n# ],
);
for (@tests) {
my ($in, $exp) = @$_;
my $parsed = $p->render($in);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
cmp_ok($parsed, 'eq', $exp, "$in");
}
03_pod_cover.t 100644 001750 001750 643 12361234004 15607 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t # $Id: 24_pod_cover.t 668 2006-10-02 16:09:19Z tinita $
use Test::More;
eval "use Test::Pod::Coverage 1.00";
plan skip_all => "Test::Pod::Coverage required for testing pod coverage" if $@;
plan tests => 3;
pod_coverage_ok( "Parse::BBCode", "Parse::BBCode is covered");
pod_coverage_ok( "Parse::BBCode::Tag", "Parse::BBCode::Tag is covered");
pod_coverage_ok( "Parse::BBCode::HTML", "Parse::BBCode::HTML is covered");
13_plaintext.t 100644 001750 001750 3561 12361234004 15662 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 16;
use Test::NoWarnings;
use_ok('Parse::BBCode::Text');
use strict;
use warnings;
my $p = Parse::BBCode::Text->new();
my @tests = (
[ q#[size=7]big [b]bold[/b] text[/size]#,
q#big bold text# ],
[ q#[url=http://foo/]interesting [b]bold[/b] link[/url]#,
q#interesting bold link# ],
[ q#[url="http://foo/"]interesting [b]bold[/b] link[/url]#,
q#interesting bold link# ],
[ q#[url=/foo]interesting [b]bold[/b] link[/url]#,
q#interesting bold link# ],
[ q#[list=1][*]first[*]second[*]third[/list]#,
qq#* first\n* second\n* third\n# ],
[ q#[list=1][*]first with [url]foo[/url][*]second[*]third[/list]#,
qq#* first with foo\n* second\n* third\n# ],
[ q#[list=1][*]first[*]second with [url]foo[/url][*]third[/list]#,
qq#* first\n* second with foo\n* third\n# ],
[ q#[list=1][*]first[*]second with [url]foo[*]third[/list]#,
qq#* first\n* second with [url]foo\n* third\n# ],
[ q#[list=1][*]first[*]second with [url]foo and [b]bold[/b][*]third[/list]#,
qq#* first\n* second with [url]foo and bold\n* third\n# ],
[ q#[img]/path/to/image.png[/img]#,
q#/path/to/image.png# ],
[ q#[img=/path/to/image.png]description[/img]#,
q#description# ],
[ q#[img=/path/to/image.png]description [b]with bold[/b][/img]#,
q#description with bold# ],
[ qq#text [quote="foo"][quote="bar"]inner quote[/quote]outer quote[/quote]#,
qq#text foo:\n> bar:\n>> inner quote\n> outer quote\n# ],
[ q#[quote="admin@2008-06-27 19:00:25"][quote="foo@2007-08-13 22:12:32"]test[/quote]test[/quote]#,
qq#admin\@2008-06-27 19:00:25:\n> foo\@2007-08-13 22:12:32:\n>> test\n> test\n# ],
);
for (@tests) {
my ($in, $exp) = @$_;
my $parsed = $p->render($in);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
cmp_ok($parsed, 'eq', $exp, "$in");
}
examples 000755 001750 001750 0 12361234004 14370 5 ustar 00tina tina 000000 000000 Parse-BBCode-0.15 bench.pl 100644 001750 001750 5177 12361234004 16156 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/examples #!/usr/bin/perl
use strict;
use warnings;
use Carp qw(carp croak);
use Data::Dumper;
use Benchmark;
my %loaded;
for (qw/ BBCode::Parser Parse::BBCode HTML::BBCode HTML::BBReverse AUBBC /) {
eval "use $_";
unless ($@) {
$loaded{$_} = $_->VERSION;
}
}
print "Benchmarking...\n";
for my $key (sort keys %loaded) {
print "$key\t$loaded{$key}\n";
}
my $code = <<'EOM';
[b]bold [i]italic[/i] test[/b]
[code]some [perl] code[/code]
[url=http://foo.example.org/]a link![/url]
EOM
my ($count, $multiply) = @ARGV;
$multiply ||= 1;
$code = $code x $multiply;
sub create_au {
my $pb = AUBBC->new();
return $pb;
}
sub create_pb {
my $pb = Parse::BBCode->new({
tags => {
b => '%s',
i => '%s',
url => '%s',
code =>'block:%{noparse}s
%{parse}s
',
noparse => '%{html}s',
},
close_open_tags => 1,
}
);
my @tests = (
[ 1, q#[i]italic[b]bold [quote]this is invalid[/quote] bold[/b][/i]#,
q#italicbold this is invalid
bold[/b][/i]#,
q#[i]italic[b]bold [/b][/i][quote]this is invalid[/quote] bold[/b][/i]#,
],
[ 0, q#[i]italic[b]bold [quote]this is invalid[/quote] bold[/b][/i]#,
q#[i]italic[b]bold this is invalid
bold[/b][/i]#,
q#[i]italic[b]bold [quote]this is invalid[/quote] bold[/b][/i]#,
],
[ 0, q#[i]italic[b]bold[/b] [quote]this is invalid[/quote] [/i]#,
q#[i]italicbold this is invalid
[/i]#,
q#[i]italic[b]bold[/b] [quote]this is invalid[/quote] [/i]#,
],
[ 1, q#[i]italic[b]bold [url]/foo[/url]#,
q#italicbold /foo#,
q#[i]italic[b]bold [url]/foo[/url][/b][/i]#,
],
[ 1, q#[b][i]italic#,
q#italic#,
q#[b][i]italic[/i][/b]#,
],
[ 1, q#[b][i]italic[/b]#,
q#italic#,
q#[b][i]italic[/i][/b]#,
],
[ 0, q#[noparse][b][i]italic[/i][/b]#,
q#[noparse]italic#,
q#[noparse][b][i]italic[/i][/b]#,
],
[ 1, q#[noparse][b][i]italic[/i][/b]#,
q#[b][i]italic[/i][/b]#,
q#[noparse][b][i]italic[/i][/b][/noparse]#,
],
[ 1, q#[noparse][i]italic#,
q#[i]italic#,
q#[noparse][i]italic[/noparse]#,
],
[ 1, q#[quote][noparse][i]italic#,
q#[i]italic
#,
q#[quote][noparse][i]italic[/noparse][/quote]#,
],
);
for (@tests) {
my ($close, $in, $exp, $exp_raw) = @$_;
$p->set_close_open_tags($close);
my $parsed = $p->render($in);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
my $close_string = $close ? 'yes' : 'no';
cmp_ok($parsed, 'eq', $exp, "invalid (close? $close_string) $in");
my $err = $p->error('block_inline') || $p->error('unclosed');
if ($err) {
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$err], ['err']);
my $tree = $p->get_tree;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$tree], ['tree']);
my $raw = $tree->raw_text;
#warn __PACKAGE__.':'.__LINE__.": $raw\n";
cmp_ok($raw, 'eq', $exp_raw, "raw text (close? $close_string) $in");
}
}
06_unbalanced.t 100644 001750 001750 2413 12361234004 15743 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 9;
use Test::NoWarnings;
use Parse::BBCode;
use strict;
use warnings;
my %tag_def_html = (
code => {
parse => 0,
code => sub {
my ($self, $attr, $content) = @_;
"$$content
"
},
},
pre => {
code => sub {
""
},
},
a => '%{parse}s',
b => '%{parse}s',
c => '
[/a]
# ],
[ q#[b][a][code][/a][/code]#, q#[b][a][/a]
# ],
);
for (@tests){
my ($in, $exp) = @$_;
my $parsed = $bbc2html->render($in);
#warn __PACKAGE__.':'.__LINE__.": $in => $parsed\n";
is($parsed, $exp, "unbalanced $in");
}
Parse 000755 001750 001750 0 12361234004 14372 5 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/lib BBCode.pm 100644 001750 001750 163256 12361234004 16223 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/lib/Parse package Parse::BBCode;
$Parse::BBCode::VERSION = '0.15';
use strict;
use warnings;
use Parse::BBCode::Tag;
use Parse::BBCode::HTML qw/ &defaults &default_escapes &optional /;
use base 'Class::Accessor::Fast';
__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors(qw/
tags allowed compiled plain strict_attributes close_open_tags error
tree escapes direct_attribute params url_finder text_processor linebreaks
smileys attribute_parser strip_linebreaks attribute_quote /);
#use Data::Dumper;
use Carp;
my $scalar_util = eval "require Scalar::Util; 1";
my %defaults = (
strict_attributes => 1,
direct_attribute => 1,
linebreaks => 1,
smileys => 0,
url_finder => 0,
strip_linebreaks => 1,
attribute_quote => '"',
);
sub new {
my ($class, $args) = @_;
$args ||= {};
my %args = %$args;
unless ($args{tags}) {
$args{tags} = { $class->defaults };
}
else {
$args{tags} = { %{ $args{tags} } };
}
unless ($args{escapes}) {
$args{escapes} = {$class->default_escapes };
}
else {
$args{escapes} = { %{ $args{escapes} } }
}
my $self = $class->SUPER::new({
%defaults,
%args
});
$self->set_allowed([ grep { length } keys %{ $self->get_tags } ]);
$self->_compile_tags;
return $self;
}
my $re_split = qr{ % (?:\{ (?:[a-zA-Z\|]+) \})? (?:attr|[Aas]) }x;
my $re_cmp = qr{ % (?:\{ ([a-zA-Z\|]+) \})? (attr|[Aas]) }x;
sub forbid {
my ($self, @tags) = @_;
my $allowed = $self->get_allowed;
my $re = join '|', map { quotemeta } @tags;
@$allowed = grep { ! m/^(?:$re)\z/ } @$allowed;
}
sub permit {
my ($self, @tags) = @_;
my $allowed = $self->get_allowed;
my %seen;
@$allowed = grep {
!$seen{$_}++ && $self->get_tags->{$_};
} (@$allowed, @tags);
}
sub _compile_tags {
my ($self) = @_;
# unless ($self->get_compiled) {
{
my $defs = $self->get_tags;
# get definition for how text should be rendered which is not in tags
my $plain;
if (exists $defs->{""}) {
$plain = delete $defs->{""};
if (ref $plain eq 'CODE') {
$self->set_plain($plain);
}
}
else {
my $url_finder = $self->get_url_finder;
my $linebreaks = $self->get_linebreaks;
my $smileys = $self->get_smileys;
if ($url_finder) {
my $result = eval { require URI::Find; 1 };
unless ($result) {
undef $url_finder;
}
}
my $escape = \&Parse::BBCode::escape_html;
my $post_processor_1 = $escape;
my $post_processor;
my $text_processor = $self->get_text_processor;
if ($text_processor) {
$post_processor_1 = $text_processor;
}
if ($smileys and ref($smileys->{icons}) eq 'HASH') {
$smileys = {
icons => $smileys->{icons},
base_url => $smileys->{base_url} || '/smileys/',
format => $smileys->{format} || '',
};
my $re = join '|', map { quotemeta $_ } sort { length $b <=> length $a }
keys %{ $smileys->{icons} };
my $code = sub {
my ($text, $post_processor) = @_;
my $out = '';
while ($text =~ s/\A (^|.*?[\s]) ($re) (?=[\s]|$)//xsm) {
my ($pre, $emo) = ($1, $2);
my $url = "$smileys->{base_url}$smileys->{icons}->{$emo}";
my $emo_escaped = Parse::BBCode::escape_html($emo);
my $image_tag = sprintf $smileys->{format}, $url, $emo_escaped;
$out .= $post_processor_1->($pre) . $image_tag;
}
$out .= $post_processor_1->($text);
return $out;
};
$post_processor = $code;
}
else {
$post_processor = $post_processor_1;
}
if ($url_finder) {
my $url_find_sub;
if (ref($url_finder) eq 'CODE') {
$url_find_sub = $url_finder;
}
else {
unless (ref($url_finder) eq 'HASH') {
$url_finder = {
max_length => 50,
format => '%s',
};
}
my $max_url = $url_finder->{max_length} || 0;
my $format = $url_finder->{format};
my $finder = URI::Find->new(sub {
my ($url) = @_;
my $title = $url;
if ($max_url and length($title) > $max_url) {
$title = substr($title, 0, $max_url) . "...";
}
my $escaped = Parse::BBCode::escape_html($url);
my $escaped_title = Parse::BBCode::escape_html($title);
my $href = sprintf $format, $escaped, $title;
return $href;
});
$url_find_sub = sub {
my ($ref_content, $post, $info) = @_;
$finder->find($ref_content, sub { $post->($_[0], $info) });
};
}
$plain = sub {
my ($parser, $attr, $content, $info) = @_;
unless ($info->{classes}->{url}) {
$url_find_sub->(\$content, $post_processor, $info);
}
else {
$content = $post_processor->($content);
}
$content =~ s/\r?\n|\r/
\n/g if $linebreaks;
$content;
};
}
else {
$plain = sub {
my ($parser, $attr, $content, $info) = @_;
my $text = $post_processor->($content, $info);
$text =~ s/\r?\n|\r/
\n/g if $linebreaks;
$text;
};
}
$self->set_plain($plain);
}
# now compile the rest of definitions
for my $key (keys %$defs) {
my $def = $defs->{$key};
#warn __PACKAGE__.':'.__LINE__.": $key: $def\n";
if (not ref $def) {
my $new_def = $self->_compile_def($def);
$defs->{$key} = $new_def;
}
elsif (not exists $def->{code} and exists $def->{output}) {
my $new_def = $self->_compile_def($def);
$defs->{$key} = $new_def;
}
$defs->{$key}->{class} ||= 'inline';
$defs->{$key}->{classic} = 1 unless defined $defs->{$key}->{classic};
$defs->{$key}->{close} = 1 unless defined $defs->{$key}->{close};
}
$self->set_compiled(1);
}
}
sub _compile_def {
my ($self, $def) = @_;
my $esc = $self->get_escapes;
my $parse = 0;
my $new_def = {};
my $output = $def;
my $close = 1;
my $class = 'inline';
if (ref $def eq 'HASH') {
$new_def = { %$def };
$output = delete $new_def->{output};
$parse = $new_def->{parse};
$close = $new_def->{close} if exists $new_def->{close};
$class = $new_def->{class} if exists $new_def->{class};
}
else {
}
# we have a string, compile
#warn __PACKAGE__.':'.__LINE__.": $key => $output\n";
if ($output =~ s/^(inline|block|url)://) {
$class = $1;
}
my @parts = split m!($re_split)!, $output;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@parts], ['parts']);
my @compiled;
for my $p (@parts) {
if ($p =~ m/$re_cmp/) {
my ($escape, $type) = ($1, $2);
$escape ||= 'parse';
my @escapes = split /\|/, $escape;
if (grep { $_ eq 'parse' } @escapes) {
$parse = 1;
}
push @compiled, [\@escapes, $type];
}
else {
push @compiled, $p;
}
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@compiled], ['compiled']);
}
my $code = sub {
my ($self, $attr, $string, $fallback, $tag) = @_;
my $out = '';
for my $c (@compiled) {
# just text
unless (ref $c) {
$out .= $c;
}
# tag attribute or content
else {
my ($escapes, $type) = @$c;
my @escapes = @$escapes;
my $var = '';
my $attributes = $tag->get_attr;
if ($type eq 'attr' and @$attributes > 1) {
my $name = shift @escapes;
for my $item (@$attributes[1 .. $#$attributes]) {
if ($item->[0] eq $name) {
$var = $item->[1];
last;
}
}
}
elsif ($type eq 'a') {
$var = $attr;
}
elsif ($type eq 'A') {
$var = $fallback;
}
elsif ($type eq 's') {
if (ref $string eq 'SCALAR') {
# this text is already finished and escaped
$string = $$string;
}
$var = $string;
}
for my $e (@escapes) {
my $sub = $esc->{$e};
if ($sub) {
$var = $sub->($self, $c, $var);
unless (defined $var) {
# if escape returns undef, we return it unparsed
return $tag->get_start
. (join '', map {
$self->_render_tree($_);
} @{ $tag->get_content })
. $tag->get_end;
}
}
}
$out .= $var;
}
}
return $out;
};
$new_def->{parse} = $parse;
$new_def->{code} = $code;
$new_def->{close} = $close;
$new_def->{class} = $class;
return $new_def;
}
sub _render_text {
my ($self, $tag, $text, $info) = @_;
#warn __PACKAGE__.':'.__LINE__.": text '$text'\n";
defined (my $code = $self->get_plain) or return $text;
return $code->($self, $tag, $text, $info);
}
sub parse {
my ($self, $text, $params) = @_;
my $parse_attributes = $self->get_attribute_parser ? $self->get_attribute_parser : $self->can('parse_attributes');
$self->set_error(undef);
my $defs = $self->get_tags;
my $tags = $self->get_allowed || [keys %$defs];
my @classic_tags = grep { $defs->{$_}->{classic} } @$tags;
my @short_tags = grep { $defs->{$_}->{short} } @$tags;
my $re_classic = join '|', map { quotemeta } sort {length $b <=> length $a } @classic_tags;
#$re_classic = qr/$re_classic/i;
my $re_short = join '|', map { quotemeta } sort {length $b <=> length $a } @short_tags;
#$re_short = qr/$re_short/i;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$re], ['re']);
my @tags;
my $out = '';
my @opened;
my $current_open_re = '';
my $callback_found_text = sub {
my ($text) = @_;
if (@opened) {
my $o = $opened[-1];
$o->add_content($text);
}
else {
if (@tags and !ref $tags[-1]) {
# text tag, concatenate
$tags[-1] .= $text;
}
else {
push @tags, $text;
}
}
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@opened], ['opened']);
};
my $callback_found_tag;
my $in_url = 0;
$callback_found_tag = sub {
my ($tag) = @_;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$tag], ['tag']);
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@opened], ['opened']);
if (@opened) {
my $o = $opened[-1];
my $class = $o->get_class;
#warn __PACKAGE__.':'.__LINE__.": tag $tag\n";
if (ref $tag and $class =~ m/inline|url/ and $tag->get_class eq 'block') {
$self->_add_error('block_inline', $tag);
pop @opened;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$o], ['o']);
if ($self->get_close_open_tags) {
# we close the tag for you
$self->_finish_tag($o, '[/' . $o->get_name . ']', 1);
$callback_found_tag->($o);
$callback_found_tag->($tag);
}
else {
# nope, no automatic closing, invalidate all
# open inline tags before
my @red = $o->_reduce;
$callback_found_tag->($_) for @red;
$callback_found_tag->($tag);
}
}
elsif (ref $tag) {
my $def = $defs->{lc $tag->get_name};
my $parse = $def->{parse};
if ($parse) {
$o->add_content($tag);
}
else {
my $content = $tag->get_content;
my $string = '';
for my $c (@$content) {
if (ref $c) {
$string .= $c->raw_text( auto_close => 0 );
}
else {
$string .= $c;
}
}
$tag->set_content([$string]);
$o->add_content($tag);
}
}
else {
$o->add_content($tag);
}
}
elsif (ref $tag) {
my $def = $defs->{lc $tag->get_name};
my $parse = $def->{parse};
if ($parse) {
push @tags, $tag;
}
else {
my $content = $tag->get_content;
my $string = '';
for my $c (@$content) {
if (ref $c) {
$string .= $c->raw_text( auto_close => 0 );
}
else {
$string .= $c;
}
}
$tag->set_content([$string]);
push @tags, $tag;
}
}
else {
push @tags, $tag;
}
$current_open_re = join '|', map {
quotemeta $_->get_name
} @opened;
};
my @class = 'block';
while (defined $text and length $text) {
$in_url = grep { $_->get_class eq 'url' } @opened;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$in_url], ['in_url']);
#warn __PACKAGE__.':'.__LINE__.": ============= match $text\n";
my $tag;
my ($before, $tag1, $tag2, $after);
if ($re_classic and $re_short) {
($before, $tag1, $tag2, $after) = split m{
(?:
\[ ($re_short) (?=://)
|
\[ ($re_classic) (?=\b|\]|\=)
)
}ix, $text, 2;
}
elsif (! $re_classic and $re_short) {
($before, $tag1, $after) = split m{
\[ ($re_short) (?=://)
}ix, $text, 2;
}
elsif ($re_classic and !$re_short) {
($before, $tag2, $after) = split m{
\[ ($re_classic) (?=\b|\]|\=)
}ix, $text, 2;
}
{ no warnings;
# warn __PACKAGE__.':'.__LINE__.": $before, $tag1, $tag2, $after)\n";
#warn __PACKAGE__.':'.__LINE__.": RE: $current_open_re\n";
}
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@opened], ['opened']);
if (length $before) {
# look if it contains a closing tag
#warn __PACKAGE__.':'.__LINE__.": BEFORE $before\n";
while (length $current_open_re and $before =~ s# (.*?) (\[ / ($current_open_re) \]) ##ixs) {
# found closing tag
my ($content, $end, $name) = ($1, $2, $3);
#warn __PACKAGE__.':'.__LINE__.": found closing tag $name!\n";
my $f;
# try to find the matching opening tag
my @not_close;
while (@opened) {
my $try = pop @opened;
$current_open_re = join '|', map {
quotemeta $_->get_name
} @opened;
if ($try->get_name eq lc $name) {
$f = $try;
last;
}
elsif (!$try->get_close) {
$self->_finish_tag($try, '');
unshift @not_close, $try;
}
else {
# unbalanced
$self->_add_error('unclosed', $try);
if ($self->get_close_open_tags) {
# close
$f = $try;
unshift @not_close, $try;
if (@opened) {
$opened[-1]->add_content('');
}
$self->_finish_tag($try, '[/'. $try->get_name() .']', 1);
}
else {
# just add unparsed text
$callback_found_tag->($_) for $try->_reduce;
}
}
}
if (@not_close) {
$not_close[-1]->add_content($content);
}
for my $n (@not_close) {
$f->add_content($n);
#$callback_found_tag->($n);
}
# add text before closing tag as content to the current open tag
if ($f) {
unless (@not_close) {
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$f], ['f']);
$f->add_content( $content );
}
# TODO
$self->_finish_tag($f, $end);
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$f], ['f']);
$callback_found_tag->($f);
}
}
# warn __PACKAGE__." === before='$before' ($tag)\n";
$callback_found_text->($before);
}
if (defined $tag1) {
$in_url = grep { $_->get_class eq 'url' } @opened;
# short tag
# $callback_found_text->($before) if length $before;
if ($after =~ s{ :// ([^\[]+) \] }{}x) {
my $content = $1;
my ($attr, $title) = split /\|/, $content, 2;
my $tag = $self->new_tag({
name => lc $tag1,
attr => [[$attr]],
attr_raw => $attr,
content => [(defined $title and length $title) ? $title : ()],
start => "[$tag1://$content]",
close => 0,
class => $defs->{lc $tag1}->{class},
single => $defs->{lc $tag1}->{single},
in_url => $in_url,
type => 'short',
});
if ($in_url and $tag->get_class eq 'url') {
$callback_found_text->($tag->get_start);
}
else {
$callback_found_tag->($tag);
}
}
else {
$callback_found_text->("[$tag1");
}
$text = $after;
next;
}
$tag = $tag2;
$in_url = grep { $_->get_class eq 'url' } @opened;
if ($after) {
# found start of a tag
#warn __PACKAGE__.':'.__LINE__.": find attribute for $tag\n";
my ($ok, $attributes, $attr_string, $end) = $self->$parse_attributes(
text => \$after,
tag => lc $tag,
);
if ($ok) {
my $attr = $attr_string;
$attr = '' unless defined $attr;
#warn __PACKAGE__.':'.__LINE__.": found attribute for $tag: $attr\n";
my $close = $defs->{lc $tag}->{close};
my $def = $defs->{lc $tag};
my $open = $self->new_tag({
name => lc $tag,
attr => $attributes,
attr_raw => $attr_string,
content => [],
start => "[$tag$attr]",
close => $close,
class => $defs->{lc $tag}->{class},
single => $defs->{lc $tag}->{single},
in_url => $in_url,
type => 'classic',
});
my $success = 1;
my $nested_url = $in_url && $open->get_class eq 'url';
{
my $last = $opened[-1];
if ($last and not $last->get_close and not $close) {
$self->_finish_tag($last, '');
# tag which should not have closing tag
pop @opened;
$callback_found_tag->($last);
}
}
if ($open->get_single && !$nested_url) {
$self->_finish_tag($open, '');
$callback_found_tag->($open);
}
elsif (!$nested_url) {
push @opened, $open;
my $def = $defs->{lc $tag};
#warn __PACKAGE__.':'.__LINE__.": $tag $def\n";
my $parse = $def->{parse};
if ($parse) {
$current_open_re = join '|', map {
quotemeta $_->get_name
} @opened;
}
else {
#warn __PACKAGE__.':'.__LINE__.": noparse, find content\n";
# just search for closing tag
if ($after =~ s# (.*?) (\[ / $tag \]) ##ixs) {
my $content = $1;
my $end = $2;
#warn __PACKAGE__.':'.__LINE__.": CONTENT $content\n";
my $finished = pop @opened;
$finished->set_content([$content]);
$self->_finish_tag($finished, $end);
$callback_found_tag->($finished);
}
else {
#warn __PACKAGE__.':'.__LINE__.": nope '$after'\n";
}
}
}
else {
$callback_found_text->($open->get_start);
}
}
else {
# unclosed tag
$callback_found_text->("[$tag$attr_string$end");
}
}
elsif ($tag) {
#warn __PACKAGE__.':'.__LINE__.": end\n";
$callback_found_text->("[$tag");
}
$text = $after;
#sleep 1;
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@tags], ['tags']);
}
# warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@opened], ['opened']);
if ($self->get_close_open_tags) {
while (my $opened = pop @opened) {
$self->_add_error('unclosed', $opened);
$self->_finish_tag($opened, '[/' . $opened->get_name . ']', 1);
$callback_found_tag->($opened);
}
}
else {
while (my $opened = shift @opened) {
my @text = $opened->_reduce;
push @tags, @text;
}
}
if ($scalar_util) {
Scalar::Util::weaken($callback_found_tag);
}
else {
# just to make sure no memleak if there's no Scalar::Util
undef $callback_found_tag;
}
#warn __PACKAGE__.':'.__LINE__.": !!!!!!!!!!!! left text: '$text'\n";
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@tags], ['tags']);
my $tree = $self->new_tag({
name => '',
content => [@tags],
start => '',
class => 'block',
attr => [[]],
});
$tree->_init_info({});
return $tree;
}
sub new_tag {
my $self = shift;
Parse::BBCode::Tag->new(@_)
}
sub _add_error {
my ($self, $error, $tag) = @_;
my $errors = $self->get_error || {};
push @{ $errors->{$error} }, $tag;
$self->set_error($errors);
}
sub error {
my ($self, $type) = @_;
my $errors = $self->get_error || {};
if ($type and $errors->{$type}) {
return $errors->{$type};
}
elsif (keys %$errors) {
return $errors;
}
return 0;
}
sub render {
my ($self, $text, $params) = @_;
if (@_ < 2) {
croak ("Missing input - Usage: \$parser->render(\$text)");
}
#warn __PACKAGE__.':'.__LINE__.": @_\n";
#sleep 2;
my $tree = $self->parse($text, $params);
my $out = $self->render_tree($tree, $params);
if ($self->get_error) {
$self->set_tree($tree);
}
return $out;
}
sub render_tree {
my ($self, $tree, $params) = @_;
$params ||= {};
$self->set_params($params);
my $rendered = $self->_render_tree($tree);
$self->set_params(undef);
return $rendered;
}
sub _render_tree {
my ($self, $tree, $outer, $info) = @_;
my $out = '';
$info ||= {
stack => [],
tags => {},
classes => {},
};
my $defs = $self->get_tags;
if (ref $tree) {
my $name = $tree->get_name;
my %tags = %{ $info->{tags} };
$tags{$name}++;
my @stack = @{ $info->{stack} };
push @stack, $name;
my %classes = %{ $info->{classes} };
$classes{ $tree->get_class || '' }++;
my %info = (
tags => \%tags,
stack => [@stack],
classes => \%classes,
);
my $code = $defs->{$name}->{code};
my $parse = $defs->{$name}->{parse};
my $attr = $tree->get_attr || [];
$attr = $attr->[0]->[0];
my $content = $tree->get_content;
my $fallback;
my $string = '';
if (($tree->get_type || 'classic') eq 'classic') {
$fallback = (defined $attr and length $attr) ? $attr : $content;
}
else {
$fallback = $attr;
$string = @$content ? '' : $attr;
}
if (ref $fallback) {
# we have recursive content, we don't want that in
# an attribute
$fallback = join '', grep {
not ref $_
} @$fallback;
}
if ($self->get_strip_linebreaks and ($tree->get_class || '') eq 'block') {
if (@$content == 1 and not ref $content->[0] and defined $content->[0]) {
$content->[0] =~ s/^\r?\n//;
$content->[0] =~ s/\r?\n\z//;
}
elsif (@$content > 1) {
if (not ref $content->[0] and defined $content->[0]) {
$content->[0] =~ s/^\r?\n//;
}
if (not ref $content->[-1] and defined $content->[-1]) {
$content->[-1] =~ s/\r?\n\z//;
}
}
}
if (not exists $defs->{$name}->{parse} or $parse) {
for my $c (@$content) {
$string .= $self->_render_tree($c, $tree, \%info);
}
}
else {
$string = join '', @$content;
}
if ($code) {
my $o = $code->($self, $attr, \$string, $fallback, $tree, \%info);
$out .= $o;
}
else {
$out .= $string;
}
}
else {
#warn __PACKAGE__.':'.__LINE__.": ==== $tree\n";
$out .= $self->_render_text($outer, $tree, $info);
}
return $out;
}
sub escape_html {
my ($str) = @_;
return '' unless defined $str;
$str =~ s/&/&/g;
$str =~ s/"/"/g;
$str =~ s/'/'/g;
$str =~ s/>/>/g;
$str =~ s/</g;
return $str;
}
sub parse_attributes {
my ($self, %args) = @_;
my $text = $args{text};
my $tagname = $args{tag};
my $attribute_quote = $self->get_attribute_quote;
my $attr_string = '';
my $attributes = [];
if (
($self->get_direct_attribute and $$text =~ s/^(=[^\]]*)?]//)
or
($$text =~ s/^( [^\]]*)?\]//)
) {
my $attr = $1;
my $end = ']';
$attr = '' unless defined $attr;
$attr_string = $attr;
unless (length $attr) {
return (1, [], $attr_string, $end);
}
if ($self->get_direct_attribute) {
$attr =~ s/^=//;
}
if ($self->get_strict_attributes and not length $attr) {
return (0, [], $attr_string, $end);
}
my @array;
if (length($attribute_quote) == 1) {
if ($attr =~ s/^(?:$attribute_quote(.+?)$attribute_quote(?:\s+|$)|(.*?)(?:\s+|$))//) {
my $val = defined $1 ? $1 : $2;
push @array, [$val];
}
while ($attr =~ s/^([a-zA-Z0-9_]+)=(?:$attribute_quote(.+?)$attribute_quote(?:\s+|$)|(.*?)(?:\s+|$))//) {
my $name = $1;
my $val = defined $2 ? $2 : $3;
push @array, [$name, $val];
}
}
else {
if ($attr =~ s/^(?:(["'])(.+?)\1|(.*?)(?:\s+|$))//) {
my $val = defined $2 ? $2 : $3;
push @array, [$val];
}
while ($attr =~ s/^([a-zA-Z0-9_]+)=(?:(["'])(.+?)\2|(.*?)(?:\s+|$))//) {
my $name = $1;
my $val = defined $3 ? $3 : $4;
push @array, [$name, $val];
}
}
if ($self->get_strict_attributes and length $attr and $attr =~ tr/ //c) {
return (0, [], $attr_string, $end);
}
$attributes = [@array];
return (1, $attributes, $attr_string, $end);
}
return (0, $attributes, $attr_string, '');
}
# TODO add callbacks
sub _finish_tag {
my ($self, $tag, $end, $auto_closed) = @_;
#warn __PACKAGE__.':'.__LINE__.": _finish_tag(@_)\n";
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$tag], ['tag']);
unless ($tag->get_finished) {
$tag->set_end($end);
$tag->set_finished(1);
$tag->set_auto_closed($auto_closed || 0);
}
return 1;
}
__END__
=pod
=head1 NAME
Parse::BBCode - Module to parse BBCode and render it as HTML or text
=head1 SYNOPSIS
Parse::BBCode parses common bbcode like
[b]bold[/b] [size=10]big[/size]
short tags like
[foo://test]
and custom bbcode tags.
For the documentation of short tags, see L<"SHORT TAGS">.
To parse a bbcode string, set up a parser with the default HTML defintions
of L%{html}s
',
code => sub {
my ($parser, $attr, $content, $attribute_fallback) = @_;
if ($attr eq 'perl') {
# use some syntax highlighter
$content = highlight_perl($content);
}
else {
$content = Parse::BBCode::escape_html($$content);
}
"$content"
},
test => 'this is klingon: %{klingon}s',
},
escapes => {
klingon => sub {
my ($parser, $tag, $text) = @_;
return translate_into_klingon($text);
},
},
}
);
my $code = 'some [b]b code[/b]';
my $parsed = $p->render($code);
=head1 DESCRIPTION
If you set up the Parse::BBCode object without arguments, the default tags
are loaded, and any text outside or inside of parseable tags will go through
a default subroutine which escapes HTML and replaces newlines with
tags. If you need to change this you can set the options 'url_finder',
'text_processor' and 'linebreaks'.
=head2 METHODS
=over 4
=item new
Constructor. Takes a hash reference with options as an argument.
my $parser = Parse::BBCode->new({
tags => {
url => ...,
i => ...,
},
escapes => {
link => ...,
},
close_open_tags => 1, # default 0
strict_attributes => 0, # default 1
direct_attributes => 1, # default 1
url_finder => 1, # default 0
smileys => 0, # default 0
linebreaks => 1, # default 1
);
=over 4
=item tags
See L<"TAG DEFINITIONS">
=item escapes
See L<"ESCAPES">
=item url_finder
See L<"URL FINDER">
=item smileys
If you want to replace smileys with an icon:
my $parser = Parse::BBCode->new({
smileys => {
base_url => '/your/url/to/icons/',
icons => { qw/ :-) smile.png :-( sad.png / },
# sprintf format:
# first argument url
# second argument original text smiley (HTML escaped)
format => '',
# if you need the url and text in a different order
# see perldoc -f sprintf, e.g.
# format => '
',
},
});
This subroutine will be applied during the url_finder (or first, if
url_finder is 0), and the rest will get processed by the text
procesor (default escaping html and replacing linebreaks).
Smileys are only replaced if surrounded by whitespace or start/end of line/text.
[b]bold
:-)[/b] :-(
In this example both smileys will be replaced. The first smiley is at the end
of the text because the text inside [b][/b] is processed on its own.
Open to any suggestions here.
=item linebreaks
The default text processor replaces linebreaks with
\n.
If you don't want this, set 'linebreaks' to 0.
=item text_processor
If you need to add any customized text processing (like smiley parsing, for
example), you can pass a subroutine here. Note that this subroutine also
needs to do HTML escaping itself!
See L<"TEXT PROCESSORS">
=item close_open_tags
Default: 0
If set to true (1), it will close open tags at the end or before block tags.
=item strict_attributes
Default: 1
If set to true (1), tags with invalid attributes are left unparsed. If set to
false (0), the attribute for this tags will be empty.
An invalid attribute:
[foo=bar far boo]...[/foo]
I might add an option to define your own attribute validation. Contact me if
you'd like to have this.
=item direct_attributes
Default: 1
Normal tag syntax is:
[tag=val1 attr2=val2 ...]
If set to 0, tag syntax is
[tag attr2=val2 ...]
=item attribute_quote
You can change how the attribute values shuold be quoted.
Default is a double quote (which is still optional):
my $parser = Parse::BBCode->new(
attribute_quote => '"',
...
);
[tag="foo" attr="bar" attr2=baz]...[/tag]
If you set it to single quote:
my $parser = Parse::BBCode->new(
attribute_quote => "'",
...
);
[tag='foo' attr=bar attr2='baz']...[/tag]
You can also set it to both: C<'">. Then both quoting types are
allowed:
my $parser = Parse::BBCode->new(
attribute_quote => q/'"/,
...
);
[tag='foo' attr="bar" attr2=baz]...[/tag]
=item attribute_parser
You can pass a subref that overrides the default attribute parsing.
See L<"ATTRIBUTE PARSING">
=item strip_linebreaks
Default: 1
Strips linebreaks at start/end of block tags
=back
=item render
Input: The text to parse, optional hashref
Returns: the rendered text
my $rendered = $parser->render($bbcode);
You can pass an optional hashref with information you need inside
of your self-defined rendering subs.
For example if you want to display code in a codebox with a link to
download the code you need the id of the article (in a forum) and the number
of the code tag.
my $parsed = $parser->render($bbcode, { article_id => 23 });
# in the rendering sub:
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
my $article_id = $parser->get_params->{article_id};
my $code_id = $tag->get_num;
# write downloadlink like
# download.pl?article_id=$article_id;code_id=$code_id
# in front of the displayed code
See examples/code_download.pl for a complete example of how to set up
the rendering and how to extract the code from the tree. If run as a CGI
skript it will give you a dialogue to save the code into a file, including
a reasonable default filename.
=item parse
Input: The text to parse.
Returns: the parsed tree (a L%{html}s
',
quote => 'block:%s
',
code => {
code => sub {
my ($parser, $attr, $content, $attribute_fallback) = @_;
if ($attr eq 'perl') {
# use some syntax highlighter
$content = highlight_perl($$content);
}
else {
$content = Parse::BBCode::escape_html($$content);
}
"$content"
},
parse => 0,
class => 'block',
},
hr => {
class => 'block',
output => '
',
single => 1,
},
},
}
);
The following list explains the above tag definitions:
=over 4
=item C<%s>
i => '%s'
[i] italic [/i]
turns out as
italic <html>
So C<%s> stands for the tag content. By default, it is parsed itself,
so that you can nest tags.
=item C<%{parse}s>
b => '%{parse}s'
[b] bold [/b]
turns out as
bold <html>
C<%{parse}s> is the same as C<%s> because 'parse' is the default.
=item C<%a>
size => '%{parse}s'
[size=7] some big text [/size]
turns out as
some big text
So %a stands for the tag attribute. By default it will be HTML
escaped.
=item url tag, C<%A>, C<%{link}A>
url => 'url:%{parse}s'
the first thing you can see is the C%{html}s
'
[noparse] [some]unbalanced[/foo] [/noparse]
With this definition the output would be
[some]unbalanced[/foo]
So inside a noparse tag you can write (almost) any invalid bbcode.
The only exception is the noparse tag itself:
[noparse] [some]unbalanced[/foo] [/noparse] [b]really bold[/b] [/noparse]
Output:
[some]unbalanced[/foo] really bold [/noparse]
Because the noparse tag ends at the first closing tag, even if you
have an additional opening noparse tag inside.
The C<%{html}s> defines that the content should be HTML escaped.
If you don't want any escaping you can't say C<%s> because the default
is 'parse'. In this case you have to write C<%{noescape}>.
=item Block tags
quote => 'block:%s
',
To force valid html you can add classes to tags. The default
class is 'inline'. To declare it as a block add C<'block:"> to the start
of the string.
Block tags inside of inline tags will either close the outer tag(s) or
leave the outer tag(s) unparsed, depending on the option C
',
single => 1,
},
The hr-Tag is a block tag (should not be inside inline tags),
and it has no closing tag (option C
=back
=head1 ESCAPES
my $p = Parse::BBCode->new({
...
escapes => {
link => sub {
},
},
});
You can define or override escapes. Default escapes are html, uri, link, email,
htmlcolor, num.
An escape functions as a validator and filter. For example, the 'link' escape
looks if it got a valid URI (starting with C> or C<\w+://>) and html-escapes
it. It returns the empty string if the input is invalid.
See L
\n/g;
return $text;
It will be applied to text outside of bbcode and inside of parseable
bbcode tags (and not to code tags or other tags with unparsed content).
If you need an additional post processor this usually cannot be done
after the HTML escaping and url finding. So if you write a text processor it
must do the HTML escaping itself.
For example if you want to replace smileys with image tags you cannot simply do:
$text =~ s/ :-\) //g;
because then the image tag would be HTML escaped after that.
On the other hand it's usually not possible to do something like
that *after* the HTML escaping since that might introduce text
sequences that look like a smiley (or whatever you want to replace).
So a simple example for a customized text processor would be:
...
url_finder => 1,
linebreaks => 1,
text_processor => sub {
# for $info hash description see render() method
my ($text, $info) = @_;
my $out = '';
while ($text =~ s/(.*)( |^)(:\))(?= |$)//mgs) {
# match a smiley and anything before
my ($pre, $sp, $smiley) = ($1, $2, $3);
# escape text and add smiley image tag
$out .= Parse::BBCode::escape_html($pre) . $sp . '
';
}
# leftover text
$out .= Parse::BBCode::escape_html($text);
return $out;
},
This will result in:
Replacing urls, applying your text_processor to the rest of the text and
after that replace linebreaks with
tags.
If you want to completely define the plain text processor yourself (ignoring
the 'linebreak', 'url_finder', 'smileys' and 'text_processor' options) you define the
special tag with the empty string:
my $p = Parse::BBCode->new({
tags => {
'' => sub {
my ($parser, $attr, $content, $info) = @_;
return frobnicate($content);
# remember to escape HTML!
},
...
=head1 SHORT TAGS
It can be very convenient to have short tags like [foo://id].
This is not really a part of BBCode, but I consider it as quite similar,
so I added it to this module.
For example to link to threads, cpan modules or wikipedia articles:
[thread://123]
[thread://123|custom title]
# can be implemented so that it links to thread 123 in the forum
# and additionally fetch the thread title.
[cpan://Module::Foo|some useful module]
[wikipedia://Harold & Maude]
You can define a short tag by adding the option C...
Also, forum HTML is usually not real HTML. It is usually a subset and
sometimes with additional tags.
So in the backend you need to parse it anyway to turn it into real HTML.
BBCode is widely known and used.
Unfortunately though, there is no specification; some forums only allow
attributes in double quotes, some forums implement only one attribute that
can be seperated by spaces, which makes it difficult to parse if you
want to support more than one attribute.
I tried to support the most common syntax (attributes without quotes, in
single or double quotes) and tags.
If you need additional tags it's relatively easy to implement them.
For example in my forum I implemented a [more] tag that hides long
text or code in thread view. Without Javascript you will see the expanded
content when clicking on the single article, or with Javascript the
content will be added inline via Ajax.
=head1 TODO
=over 4
=item BBCode to Textile|Markdown
There is a L%{noparse}s
',
},
});
if ($forbid) {
$p->forbid(qw/ img /);
}
my $out = $p->render($code);
}
sub parse_html_bb {
my ($code, $forbid) = @_;
my $p;
if ($forbid) {
$p = HTML::BBCode->new({
allowed_tags => [
qw/ b i code url list /
],
});
}
else {
$p = HTML::BBCode->new();
}
my $out = $p->parse($code);
}
sub parse_bbr {
my ($code, $forbid) = @_;
my $p;
if ($forbid) {
$p = HTML::BBReverse->new(
allowed_tags => [
qw/ b i code url list /
],
);
}
else {
$p = HTML::BBReverse->new(
);
}
my $out = $p->parse($code);
}
example.pl 100644 001750 001750 2432 12361234004 16521 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/examples #!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Parse::BBCode;
my %tag_def_html = (
code => {
code => sub {
my $c = $_[2];
$c = Parse::BBCode::escape_html($$c);
"
$c
"
},
},
perlmonks => 'url:%{parse}s',
url => 'url:%{parse}s',
i => '%{parse}s',
b => '%{parse}s',
);
my $bbc2html = Parse::BBCode->new({
tags => {
%tag_def_html,
},
}
);
my $text = <<'EOM';
[i]italic [b]bold italic [/b][/i]
[perlmonks=123]foo [i]italic[/i][/perlmonks]
[url=javascript:alert(123)]foo [i]italic[/i][/url]
[code]foo[b][/code]
[code]foo[code]bar[/code][/code]
[i]italic [b]bold italic [/i][/b]
[b]bold?
EOM
my $parsed = $bbc2html->render($text);
print "$parsed\n";
__DATA__
italic bold italic <html>
foo <html>italic
foo <html>italic
foo[b]
foo[code]bar<html>[/code]
italic [b]bold italic <html>[/b]
[b]bold?
08_various_tags.t 100644 001750 001750 20731 12361234004 16402 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/t use Test::More tests => 39;
use Parse::BBCode;
use strict;
use warnings;
my %args = (
tags => {
'' => sub { Parse::BBCode::escape_html($_[2]) },
i => '%s',
b => '%{parse}s',
size => {
output => '%{parse}s',
},
url => '%{parse}s',
wikipedia => '%{parse}s',
noparse => '%{html}s
',
c => {
code => sub {
my ($parser, $attr, $content) = @_;
$content = Parse::BBCode::escape_html($$content);
$content =~ s/ / /g;
return qq{$content};
},
},
code => {
code => sub {
my ($parser, $attr, $content, $attribute_fallback) = @_;
if ($attr eq 'perl') {
# use some syntax highlighter
$content = "/usr/bin/perl -e '$$content'";
}
else {
$content = Parse::BBCode::escape_html($$content);
}
"$content"
},
parse => 0,
},
raw => {
parse => 1,
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag) = @_;
my $text = $tag->raw_text . '|' . $tag->raw_content . '|' . $$content;
},
},
html2 => {
parse => 1,
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag) = @_;
$attr = $tag->get_attr;
my $text = "[0]="$at->[1]"};
}
$text .= ">$$content";
return $text;
},
},
Parse::BBCode::HTML->optional('html'),
Parse::BBCode::HTML->defaults(qw/ list * /),
'img' => '',
hr => {
class => 'block',
output => '
',
single => 1,
},
quote => 'block:%s
',
frob => 'bbcode [b]which[/i] should not be [/code]parsed
# ],
[ q#[code=perl]say "foo";[/code]#,
q#/usr/bin/perl -e 'say "foo";'# ],
[ q#[code=perl]say "foo";[/code]#,
q#/usr/bin/perl -e 'say "foo";'# ],
[ q#[raw]some [b]bold[/b] text[/raw]#,
q#[raw]some [b]bold[/b] text[/raw]|some [b]bold[/b] text|some bold text# ],
[ q#[html]bold text[/html]#,
q#bold text# ],
[ q#[html2=style color=red size="7"]big [b]bold[/b] text[/html2]#,
q#big bold text# ],
[ qq#before\n[list]\n[*]first\n[*]second\n[*]third\n[/list]\nafter#,
qq#before\n
\nafter# ],
[ q#[list][*]first with [url]/foo[/url][*]second[*]third[/list]#,
q#
# ],
[ q#[list][*]first[*]second with [url]/foo[/url][*]third[/list]#,
q#
# ],
[ q#[list][*]first[*]second with [url]/foo[*]third[/list]#,
q#
# ],
[ q#[list=1][*]first[*]second with [url]foo and [b]bold[/b][*]third[/list]#,
q#
# ],
[ q#[list][*]a[list][*]c1[/list][/list]#,
q#
# ],
[ q#[list=1][*]a[list][*]c1[/list][/list]#,
q#
# ],
[ q#[list=a][*]a[list][*]c1[/list][/list]#,
q#
# ],
[ q#[quote][*]a[*]b [/quote] test#,
q#[*]a[*]b
test# ],
[ q#test [*]a[*]b end#,
q#test [*]a[*]b end# ],
[ q#[img]/path/to/image.png[/img]#,
q## ],
[ q#[img=/path/to/image.png]description[/img]#,
q#
# ],
[ q#[img=/path/to/image.png]description [b]with bold[/b][/img]#,
q#
# ],
[ q#text [quote]with bold and [hr]line[/quote]#,
q#text
with bold and
# ],
[ qq#text [quote="foo"][quote="bar"]inner quote[/quote]outer quote[/quote]#,
q#text
line
# ],
[ q#[quote="admin@2008-06-27 19:00:25"][quote="foo@2007-08-13 22:12:32"]test[/quote]test[/quote]#,
q#inner quote
outer quote
# ],
[ q#text [b]with bold and [hr]line[/b]#,
q#text [b]with bold and test
test
line[/b]# ],
[ q#text with bold and [hr]line#,
q#text with bold and
line# ],
[ q#[img]javascript:boo()[/img]#,
q#[img]javascript:boo()[/img]# ],
[ qq#[img]javascr\tipt:boo()[/img]#,
qq#[img]javascr\tipt:boo()[/img]# ],
[ q#[frob]blubber bla[/frob]#,
q#code
\nend# ],
[ qq#start\n[quote]\ncode\n[/quote]\nend#,
qq#start\n\ncode\n
\nend#, $lf ],
);
for (@tests) {
my ($in, $exp, $parser) = @$_;
$parser ||= $p;
my $parsed = $parser->render($in);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
$in =~ s/\n/\\n/g;
cmp_ok($parsed, 'eq', $exp, "$in");
}
{
my $p = Parse::BBCode->new({
tags => {
'' => 'plain',
i => '%s',
},
}
);
my $parsed = $p->render(q#foo [i]latin[/i]#);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
my $exp = 'foo latin';
is($parsed, $exp, "empty plain text definition");
}
{
my $p = Parse::BBCode->new({
tags => {
i => '%s',
},
}
);
my $parsed = $p->render(q#foo [i]latin[/i]#);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
my $exp = 'foo latin';
is($parsed, $exp, "no plain text definition");
}
{
my $p = Parse::BBCode->new({
tags => {
'' => sub { Parse::BBCode::escape_html(undef) },
i => '%s',
},
}
);
my $parsed = $p->render(q#foo [i]latin[/i]#);
#warn __PACKAGE__.':'.__LINE__.": $parsed\n";
my $exp = '';
is($parsed, $exp, "undef plain text definition");
}
compare.html 100644 001750 001750 4322 12361234004 17045 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/examples
Module/Version
HTML::BBCode 2.06
BBCode::Parser 0.34
Parse::BBCode 0.12
HTML::BBReverse 0.07
AUBBC 4.06
Unbalanced / incorrectly nested tags
Leaves tags unparsed
Closes open tags or dies
depending on tags
Leaves tags unparsed
or closes tags if wanted
Creates invalid HTML
Leaves tags unparsed /
Creates invalid HTML
Add own tags
No
Yes
Yes
No
Some (2)
Unknown Tags
leaves unparsed
leaves unparsed
leaves unparsed
leaves unparsed
leaves unparsed
Forbidden Tags
leaves unparsed
Dies
leaves unparsed
leaves unparsed
leaves unparsed
Provides parsed tree
No
Yes
Yes
No
No
Block in Inline
Creates broken output (1)
Dies
leaves unparsed
Creates invalid HTML
depending on block tagsCreates invalid HTML
BBCode 000755 001750 001750 0 12361234004 15450 5 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/lib/Parse Tag.pm 100644 001750 001750 11272 12361234004 16704 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/lib/Parse/BBCode package Parse::BBCode::Tag;
$Parse::BBCode::Tag::VERSION = '0.15';
use strict;
use warnings;
use Carp qw(croak carp);
use base 'Class::Accessor::Fast';
__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors(qw/ name attr attr_raw content
finished start end close class single type in_url num level auto_closed /);
sub add_content {
my ($self, $new) = @_;
my $content = $self->get_content;
if (ref $new) {
push @$content, $new;
return;
}
if (@$content and not ref $content->[-1]) {
$content->[-1] .= $new;
}
else {
push @$content, $new;
}
}
sub raw_text {
my ($self, %args) = @_;
%args = (
auto_close => 1,
%args,
);
my $auto_close = $args{auto_close};
my ($start, $end) = ($self->get_start, $self->get_end);
if (not $auto_close and $self->get_auto_closed) {
$end = '';
}
my $text = $start;
$text .= $self->raw_content(%args);
no warnings;
$text .= $end;
return $text;
}
sub _init_info {
my ($self, $num, $level) = @_;
$level ||= 0;
my $name = $self->get_name;
$num->{$name}++;
$self->set_num($num->{$name});
$self->set_level($level);
my $content = $self->get_content || [];
for my $c (@$content) {
next unless ref $c;
$c->_init_info($num, $level + 1);
}
}
sub walk {
my ($self, $type, $sub) = @_;
$type ||= 'bfs';
unless ($type eq 'bfs') {
croak "walk(): $type '$type' not implemented";
}
my $result = $sub->($self);
return if $result;
my $content = $self->get_content || [];
for my $c (@$content) {
next unless ref $c;
$c->walk($type, $sub);
}
}
sub raw_content {
my ($self, %args) = @_;
my $content = $self->get_content;
my $text = '';
#warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$self], ['self']);
for my $c (@$content) {
if (ref $c eq ref $self) {
$text .= $c->raw_text(%args);
}
else {
$text .= $c;
}
}
return $text;
}
sub _reduce {
my ($self) = @_;
if ($self->get_finished) {
return $self;
}
my @text = $self->get_start;
my $content = $self->get_content;
for my $c (@$content) {
if (ref $c eq ref $self) {
push @text, $c->_reduce;
}
else {
push @text, $c;
}
}
push @text, $self->get_end if defined $self->get_end;
return @text;
}
1;
__END__
=pod
=head1 NAME
Parse::BBCode::Tag - Tag Class for Parse::BBCode
=head1 DESCRIPTION
If you parse a bbcode with L
<span style="font-style:italic"> italic </span><div class="bbcode_code_header">Code:</div><div class="bbcode_code_body">code block</div> </span>
',
'url' => 'url:%s',
'email' => 'url:%s',
'size' => '%s',
'color' => '%s',
'list' => {
parse => 1,
class => 'block',
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag) = @_;
$$content =~ s/^\n+//;
$$content =~ s/\n+\z//;
my $type = "ul";
my $style = '';
if ($attr) {
if ($attr eq '1') {
$type = "ol";
}
elsif ($attr eq 'a') {
$type = "ol";
$style = ' style="list-style-type: lower-alpha"';
}
}
return "<$type$style>$$content$type>";
},
},
'*' => {
parse => 1,
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
$$content =~ s/\n+\z//;
if ($info->{stack}->[-2] eq 'list') {
return "
\n}g;
$text;
},
'img' => '',
);
my %optional_tags = (
Parse::BBCode::HTML->optional(),
);
my %default_escapes = (
Parse::BBCode::HTML->default_escapes
);
sub defaults {
my ($class, @keys) = @_;
return @keys
? (map { $_ => $default_tags{$_} } grep { defined $default_tags{$_} } @keys)
: %default_tags;
}
sub default_escapes {
my ($class, @keys) = @_;
return @keys
? (map { $_ => $default_escapes{$_} } grep { defined $default_escapes{$_} } @keys)
: %default_escapes;
}
sub optional {
my ($class, @keys) = @_;
return @keys ? (grep defined, @optional_tags{@keys}) : %optional_tags;
}
1;
__END__
=pod
=head1 NAME
Parse::BBCode::XHTML - Provides XHTML defaults for Parse::BBCode
=head1 SYNOPSIS
use Parse::BBCode::XHTML;
my $p = Parse::BBCode::XHTML->new();
my $code = 'some [b]b code[/b]';
my $parsed = $p->render($code);
=head1 METHODS
=over 4
=item defaults
Returns a hash with default tags.
b, i, u, img, url, email, size, color, list, *, quote, code
=item default_escapes
Returns a hash with escaping functions.
html, uri, link, email, htmlcolor, num
=item optional
Returns a hash of optional tags.
html
=back
=cut
code_download.pl 100644 001750 001750 4013 12361234004 17664 0 ustar 00tina tina 000000 000000 Parse-BBCode-0.15/examples #!/usr/bin/perl5.10
# usage:
# perl examples/code_download.pl display=article
# perl examples/code_download.pl display=code article_id=23 code_id=1
use strict;
use warnings;
use Parse::BBCode;
use CGI;
my $bbcode = do { local $/; };
my $cgi = CGI->new;
my $display = $cgi->param('display');
my $p = Parse::BBCode->new({
tags => {
code => {
code => sub {
my ($parser, $attr, $content, $attribute_fallback, $tag, $info) = @_;
my $article_id = $parser->get_params->{article_id};
my $code_id = $tag->get_num;
my $code = Parse::BBCode::escape_html($$content);
my $title = Parse::BBCode::escape_html($attr);
return <<"EOM";