#!/usr/bin/perl -w
#
# sb::Plugin::sbTextFormat - Module for sb
# == written by T.Otani ===
# == Copyright (C) 2004 SimpleBoxes/SerendipityNZ ==
# 記法のルールに関しては、末尾の「sbtext 記法について」をご覧ください。
# 当プラグインは the Perl Artistic License に基づき、配布されます。
# 参照 : http://www.perl.com/pub/a/language/misc/Artistic.html
# 0.10 [2006/08/03] changed detail to point new site address
# 0.11 [2007/04/12] changed get_footnote to fix a bug
package sb::Plugin::sbTextFormat;
# ==================================================
# // initialization for plugin
# ==================================================
use sb::Plugin ();
sb::Plugin->register_plugin(
'lang' => {
'ja' => 'euc',
'en' => 'ascii',
},
'text' => {
'type' => 'text format, cms',
'name' => 'sbtext',
'text' => 'sbtext',
'author' => 'takkyun',
'detail' => 'http://serenebach.net/',
'version' => '0.11',
},
'file' => 'sbtext_format.txt',
'data' => undef,
);
# テキストフォーマットプラグインとして登録
sb::Plugin->register_text_filter(
'name' => 'sbtext',
'callback' => \&sb::TextFormat::sbtext::format,
);
# cms用プラグインとして登録
sb::Plugin->register_content_module(
'type' => 'entry',
'callback' => \&sb::TextFormat::sbtext::content,
'field' => 'body_text',
);
package sb::TextFormat::sbtext;
use sb::Config ();
# ==================================================
# // declaration for constant value
# ==================================================
sub LINK_TARGET (){ ' target="_blank"' }; # 外部リンクターゲット設定
sub HEADING_MARK (){ '■' }; # 小見出し用マーク
sub HEADING_PREFIX (){ 'eid' }; # 小見出し用 id
sub LOWEST_HEADING (){ 2 }; # 最小の小見出しヘッディング要素
sub MAX_BLOCK_LEVEL (){ 3 }; # 最大引用ネストレベル
sub FOOTNOTE_PREFIX (){ 'note' }; # 脚注用 id
sub NOTE_BEGIN_TAG (){ '' }; # 本文内の脚注要素(開始タグ)
sub NOTE_END_TAG (){ '' }; # 本文内の脚注要素(終了タグ)
sub LINK_HATENA (){ 'http://d.hatena.ne.jp/keyword/' }; # はてなキーワードリンク
sub LINK_GOOGLE (){ 'http://www.google.com/search?lr=lang_ja&q=' }; # Google 検索リンク
sub LINK_AMAZON (){ 'http://www.amazon.co.jp/exec/obidos/ASIN/' }; # アマゾン商品リンク
# ==================================================
# // declaration for class member
# ==================================================
my @mFootNote = ();
my $mEntryId = 0;
my $mAwsId = '';
# ==================================================
# // functions for content
# ==================================================
sub content { # 本文・続き・概要
my $cms = shift;
my $entry = shift;
my %var = @_;
if ($entry->form eq 'sbtext') { # sbtext format
my ($body,$more);
@mFootNote = ();
$mEntryId = $entry->id;
$mAwsId = &_check_awsid($entry->auth) if ($mAwsId eq '');
if ($entry->body ne '') { # 本文
$body = $entry->formated_body;
$body .= '' if ($entry->more ne '' and $var{'mode'} eq 'ent');
}
if ($entry->more ne '') { # 続き
my $permalink = &sb::Content::Entry::_permalink($entry,$var{'cat'},'more',$var{'mode'});
$more = ($var{'mode'} eq 'ent')
? $entry->formated_more
: '' . $var{'lang'}->string('parts_sequel') . '';
}
my $footnote = &get_footnote($entry->id,\$body,\$more);
if ($var{'mode'} ne 'ent' and $more eq '') {
$body .= $footnote;
} else {
$more .= $footnote;
}
$cms->tag('entry_description'=>$body) if ($body ne '');
$cms->tag('entry_sequel'=>$more) if ($more ne '');
$cms->tag('entry_excerpt'=>$entry->sum);
} else { # other format
&sb::Content::Entry::_body_text($cms,$entry,%var);
}
}
sub get_footnote($$$)
{
my ($id,$body,$more) = @_;
my $footnote = '';
if (@mFootNote)
{ # 脚注処理
$footnote .= '";
}
return $footnote;
}
# ==================================================
# // private functions - other utilities
# ==================================================
sub _check_awsid { # アマゾンアソシエイト ID の読み込み
my $id = shift;
my $user = sb::Data->load('User','id'=>$id);
return ($user and $user->aws ne '') ? $user->aws : 'simpleboxes-22';
}
# ==================================================
# // functions for text format
# ==================================================
sub format { # テキストフォーマットメイン
my $text = shift; # 入力パラメータ
@mFootNote = (); # reset buffer
$text = sb::Text->entitize($text);
$text = &_shelter_letters($text);
$text = &_shelter_footnote($text);
$text = &_hatena_block($text);
$text = &_blocks($text);
$text = &_return_letters($text);
$text = &_finishing($text);
return($text);
}
# ==================================================
# // private functions - text utilities
# ==================================================
sub _finishing { # 終了処理
my $text = shift;
$text =~ s!\n!
!g;
$text =~ s!
\n!!g;
$text =~ s!\n
!
!g;
$text =~ s!\n!!g;
$text =~ s!
\n
!!g;
return($text);
}
sub _shelter_letters { # 特殊文字の退避
my $text = shift;
$text =~ s/\\\\/&sb_;/g;
$text =~ s/\\\^/&sba;/g;
$text =~ s/\\\*/&sbb;/g;
$text =~ s/\\\</&sbc;/g;
$text =~ s/\\\'/&sbd;/g; # escape '
$text =~ s/\\\(/&sbe;/g;
$text =~ s/\\\[/&sbf;/g;
$text =~ s/\\\|/&sbg;/g;
$text =~ s/\\\-/&sbh;/g;
$text =~ s/\\\+/&sbi;/g;
$text =~ s/\\\:/&sbj;/g;
$text =~ s/\\\#/&sbk;/g;
$text =~ s/\\\)/&sbl;/g;
$text =~ s/\\\]/&sbm;/g;
$text =~ s/\\\&/&sbn;/g;
return($text);
}
sub _return_letters { # 特殊文字の復帰
my $text = shift;
$text =~ s/\&sbn;/\&/g;
$text =~ s/\&sbm;/\]/g;
$text =~ s/\&sbl;/\)/g;
$text =~ s/\&sbk;/\#/g;
$text =~ s/\&sbj;/\:/g;
$text =~ s/\&sbi;/\+/g;
$text =~ s/\&sbh;/\-/g;
$text =~ s/\&sbg;/\|/g;
$text =~ s/\&sbf;/\[/g;
$text =~ s/\&sbe;/\(/g;
$text =~ s/\&sbd;/\'/g; # unescape '
$text =~ s/\&sbc;/\</g;
$text =~ s/\&sbb;/\*/g;
$text =~ s/\&sba;/\^/g;
$text =~ s/\&sb_;/\\/g;
return($text);
}
sub _shelter_footnote { # 脚注のバッファ処理
my $text = shift;
push(@mFootNote,$1) while ( $text =~ s/\(\((.*?)\)\)// );
return($text);
}
sub _hatena_block { # はてな風ブロックの処理
my $text = shift;
my @result = ();
my $quote = -1;
my $pre = -1;
$quote = 0 if ($text =~ />>.*\n<</s);
$pre = 0 if ($text =~ />\|\|\n.*\n\|\|</s);
return($text) if ($quote == -1 and $pre == -1);
my @buf = split("\n",$text);
foreach my $line (@buf) {
if ($line =~ /^<<$/) {
$quote--;
$quote = 0 if ($quote < 0);
next;
} elsif ($line =~ /^\|\|<$/) {
$pre = 0;
next;
}
if ($quote >= 0 and $line =~ /^>>(.*)$/) {
my $check = $1;
if ($check =~ /^=/ or $check eq '') {
$quote++;
$quote = MAX_BLOCK_LEVEL if ($quote > MAX_BLOCK_LEVEL);
$line = $check;
next if ($line eq '');
}
} elsif ($pre >= 0 and $line =~ /^>\|\|$/) {
$pre = 1;
next;
}
my $mark = ($quote > 0) ? '>' x $quote : ($pre > 0) ? ' ' : '';
push(@result,$mark . $line);
}
return join("\n",@result);
}
sub _blocks { # ブロック要素
# from YukiWiki http://www.hyuki.com/yukiwiki/
# Copyright (C) 2000-2004 Hiroshi Yuki
my $text = shift;
my (@result,@saved);
my $heading = 0;
my $quote_flag = 0;
my @buf = split("\n",$text);
unshift(@saved, '');
push(@result, '');
foreach (@buf) {
if (/^(\*{1,3})(.+)/) { # 見出し
my $number = (length($1) + LOWEST_HEADING - 1);
$number = 6 if ($number > 6);
my $hn = 'h' . $number;
my $id = HEADING_PREFIX . $mEntryId . '-' . $heading;
my $mark = HEADING_MARK;
$mark = '' . $mark . '' if ($mark ne '');
push(@result, splice(@saved), qq(<$hn id="$id">$mark) . &_inline($2) . qq($hn>));
$heading++;
} elsif (/^----/) { # 水平線
push(@result, splice(@saved), '
');
} elsif (/^#(.+)/ or /^\/\/(.+)/) { # 注釈文
&_back_push(
'tag' => 'p',
'level' => 1,
'saved' => \@saved,
'result' => \@result,
'attribute' => ' class="note"',
);
push(@result, &_inline($_) . '
');
} elsif (/^(-{1,3})(.+)/) { # 箇条リスト
&_back_push(
'tag' => 'ul',
'level' => length($1),
'saved' => \@saved,
'result' => \@result,
'attribute' => '',
);
push(@result, '' . &_inline($2) . '');
} elsif (/^(\+{1,3})(.+)/) { # 順列リスト
&_back_push(
'tag' => 'ol',
'level' => length($1),
'saved' => \@saved,
'result' => \@result,
'attribute' => '',
);
push(@result, '' . &_inline($2) . '');
} elsif (/^:([^:]+):(.+)/) { # 定義リスト
&_back_push(
'tag' => 'dl',
'level' => 1,
'saved' => \@saved,
'result' => \@result,
'attribute' => '',
);
push(@result, '' . &_inline($1) . '', '' . &_inline($2) . '');
} elsif (/^((>){1,3})(.*)/) { # 引用ブロック
my $attribute = '';
my $level = length($1) / 4;
my $quote = $3;
if ($quote =~ /^=(.*?):(s?https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)?/) { # '
$attribute .= ' title="' . $1 . '"' if ($1 ne '');
$attribute .= ' cite="' . $2 . '"' if ($2 ne '');
}
&_back_push(
'tag' => 'blockquote',
'level' => $level,
'saved' => \@saved,
'result' => \@result,
'attribute' => $attribute,
);
next if ($attribute ne '');
push(@result, '' . &_inline($quote) . '
'); # [0.09] changed
# push(@result, ($quote ne '') ? &_inline($quote) . '
' : '');
} elsif (/^$/) { # 空行(パラグラフの終了)
push(@result, splice(@saved));
unshift(@saved, '
');
push(@result, '');
} elsif (/^(\s)(.*)$/) { # 整形済みテキスト
&_back_push(
'tag' => 'pre',
'level' => 1,
'saved' => \@saved,
'result' => \@result,
'attribute' => '',
);
push(@result, $2);
} elsif (/^\|(.*?)$/) { # 表組み
my $tmp = $1;
&_back_push(
'tag' => 'table',
'level' => 1,
'saved' => \@saved,
'result' => \@result,
'attribute' => '',
);
my @elems = ($tmp =~ /(.*?)\|/g);
my $value = '';
foreach my $elem (@elems) {
if ($elem =~ /^\*(.*)/) {
$value .= '
' . $1 . ' | '
} else {
$value .= '' . $elem . ' | ';
}
}
push(@result, '' . $value . '
');
} else { # 通常行
push(@result, &_inline($_) . '
');
}
}
push(@result, splice(@saved));
return join("\n",@result);
}
sub _back_push { # ブロック配列処理
# from YukiWiki http://www.hyuki.com/yukiwiki/
# Copyright (C) 2000-2004 Hiroshi Yuki
my %param = (
'tag' => undef,
'level' => undef,
'saved' => undef,
'result' => undef,
'attribute' => undef,
@_
);
my $bgn_tag = '<' . $param{'tag'} . $param{'attribute'} . '>';
my $end_tag = '' . $param{'tag'} . '>';
if ($param{'tag'} ne 'blockquote' and $param{'attribute'} ne '') {
$param{'attribute'} =~ s/\-/_/g;
$end_tag .= '';
}
while (@{$param{'saved'}} > $param{'level'}) {
push(@{$param{'result'}}, shift(@{$param{'saved'}}));
}
if ($param{'saved'}->[0] ne $end_tag) {
push(@{$param{'result'}}, splice(@{$param{'saved'}}));
}
while (@{$param{'saved'}} < $param{'level'}) {
unshift(@{$param{'saved'}}, $end_tag);
push(@{$param{'result'}}, $bgn_tag);
}
}
sub _inline { # インライン要素
my $text = shift; # 入出力パラメータ
$text =~ s/''(.*?)''/$1<\/strong>/g; # 強い強調 (Wiki表記)
$text =~ s/\*\*(.*?)\*\*/$1<\/strong>/g; # 強い強調
$text =~ s/\*(.*?)\*/$1<\/em>/g; # 弱い強調
my $srvbase = sb::Config->get->value('conf_srv_base');
my $srv_cgi = sb::Config->get->value('conf_srv_cgi');
while ($text =~ /\^(.*?)\((.*?)\)/) { # ルビとリンク
my $check = $2;
if ($check =~ /(s?https?:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)/) { # '
my $target = (index($check,$srvbase) > -1 or index($check,$srv_cgi) > -1) ? '' : LINK_TARGET;
$text =~ s/\^(.*?)\((.*?)\)/$1<\/a>/;
} elsif ($check =~ /mailto:([\w=+\$%*-]+\@[^\s()\[\]{}!\"\'<>:,\x7f-\xff]+\.\w+)/) {
$text =~ s/\^(.*?)\((.*?)\)/$1<\/a>/;
} else {
$text =~ s/\^(.*?)\((.*?)\)/$1<\/rb>