HTML整形モジュール
さぼり気味ですみません。
前にもちょっと書きましたが、Perlモジュールで
ソースコードの整形/圧縮サイトを作成しておりまして。
JavascriptとSQLは簡単に出来たので次はHTML整形!だったのですが…
HTML::Tidyでハマる
HTML::Tidy
多分このライブラリを使ってるモジュール。
これを使用すれば楽ちんかなーと思ってたのですが、
まずcpanのインストールでハマる。
結局普通にインストール出来なかったので以下のURLを参考にインストール。*1
http://www.drk7.jp/MT/archives/001256.html
手順は以下の通り。
- yum install tidy libtidy libtidy-devel
- cpan -i HTML::Tidy (テストでエラー)
- cd /root/.cpan/build/HTML-Tidy-1.08
- make
- make install
一応これでインストールは出来た。
が、ここからが長かった…
まず、マニュアルを見ながらサンプル実行してみたけど
全く表示されない。
というかコンフィグファイルの設定もよくわからない。*2
ちまたのサイトのHTMLだと大抵warningが出る。
さらにwarningがある場合は整形したHTMLも出力されないらしい…
しまいには全てのwarningを取り除いた結果が、
セグメントフォールト
ここにたどり着くまで結構かかったのに、なんという仕打ち…
整形ツールとかいっぱいあるし、
別に自前でやらなくてもいいやーとか自暴自棄になる。
で、しばらく放っておいたのですが、
偶然別のHTML整形モジュールを発見。
HTML::PrettyPrinterを見つける
HTML::PrettyPrinter
KO・RE・DA!
慣れない英語マニュアルをがんばって翻訳読みつつも、
丁寧なExampleが載っていたので何とか使えるようなりました!
ただ問題が2点ほど。
- 微妙に文字化けする
- XHTMLには未対応
1点目はHTML::Entitiesの問題っぽい。
いろいろ試行錯誤したところ、オプションで何とかなりそう。*3
my $hpp = new HTML::PrettyPrinter::XHTML; $hpp->entities('<>&"');
entitiesにこの文字列を設定するだけでだいぶ改善された。
2点目ですが、タグの終りが
/>
こんな形式だと、整形されたHTMLが変なことになります。
これは元々対応していないっぽいので、モジュール書いてみました*4
XHTML.pm
package HTML::PrettyPrinter::XHTML; use base qw( HTML::PrettyPrinter ); use HTML::Entities; use HTML::Element 1.56; use HTML::Tagset; use constant ALWAYS => -1; use constant NEVER => 0; use constant DEPENDS => 1; use constant AFTER_ATTR => 1; use constant HPP => '_hpp_'; # tags where HTML::Element::as_HTML() is used %noformattags = map {$_ => 1} qw(pre xmp plaintext listing script style); sub _format { # do the actual formating my ($self, $elem, # HTML::Element to format $indent, # number of spaces for indent inside parent element $accu, # working buffer for current line $pos, # current position in line $nl, # number of newlines at current position $wsp, # whitespace at current position? (boolean) $ai, # indent at accu start $bp, # possition of last possible breakpoint $bpi # indent at last possible breakpoint ) = @_; #my $pos = length $accu; if ($self->skip($elem)) { # ignore this element return ($accu, $pos, $nl, 0, $wsp, $ai, $bp, $bpi); } # BEFORE ELEMENT my $req_nl = $self->nl_before($elem); # required newlines if ($req_nl && ($wsp || $self->allow_forced_nl() && $self->force_nl($elem))) { # line break legal; if (!$nl) { $self->_add_line($accu,$ai); $accu = ''; $ai = $indent; $nl = 1; $pos = 0; $wsp = 1; $bp = 0; } # already at a new line if ($nl < $req_nl) { # more empty lines required $self->_add_lines((' ') x ($req_nl - $nl)); $nl = $req_nl; } } # ELEMENT my $tag = $elem->tag; if ($noformattags{$tag} || $tag =~ m/^~/ ) { # use HTML::Element::as_HTML my $sav_uc = $HTML::Element::html_uc; # save data; $HTML::Element::html_uc = $self->uppercase; my $i_str = $self->_tab($indent); # indent string # get the lines my @lines = split('\n',$elem->as_HTML($self->entities, $i_str)); # append to accu my $len_l1 = length($lines[0]) - length($i_str); if (!$nl) { # still something in the accu if ($ai + $pos + $len_l1 > $self->linelength) { # linebreak at start required if ($wsp) { # whitespace at current position unshift @lines, $self->_tab($ai).$accu; } elsif ($bp) { # use last breakpoint my $last_line = substr($accu,0,$bp,''); $self->_add_line($last_line,$ai); $bp = 0; $accu =~ s/^\s//; # replace i_str by accu substr($lines[0],0,0,$self->_tab($bpi).$accu); } else { # no line break possible => replace i_str by accu substr($lines[0],0,0,$self->_tab($ai).$accu); } } # if line break required else { substr($lines[0],0,0,$self->_tab($ai).$accu.($wsp?' ':'')); } } if ($#lines) { # multiple lines => append all but the last to array $self->_add_lines(@lines[0..$#lines-1]); $bp = 0; } else { # compensate for indent now in accu $bp += length $self->_tab($ai) if $bp; } # prepare accu $accu = $lines[-1]; $pos = length($accu) - length($self->_tab($ai)); #compansate for indent $ai = 0; $wsp = 0; $nl = 0; # ready $HTML::ELement::html_uc = $sav_uc; # restore } # if handled by HTML::Element->as_HTML() else { # let PrettyPrinter do it. # START TAG my $tstr = $self->uppercase? "<\U$tag" : "<$tag"; # add to accu => wrap neccessary? ($accu, $ai, $pos, $bp, $bpi) = $self->_add2accu($tstr,$indent,$accu,$pos,$ai,$bp,$bpi,$wsp); $nl = 0; my $cin = $indent + $self->indent($elem); # ATTRIBUTES my (@attr) = $self->_attributes($elem); my $xhtml=0; foreach my $a (@attr) { if ($a eq '/="/"'){ $xhtml=1; }else{ ($accu, $ai, $pos, $bp, $bpi) = $self->_add2accu($a,$cin,$accu,$pos,$ai,$bp,$bpi,1); } } # close start tag $wsp = 0; if ( $self->wrap_at_tagend == ALWAYS || @attr && $self->wrap_at_tagend == AFTER_ATTR) { # if breakpoint at end of the start tag $bp = $pos; $bpi = $cin; } $accu .= ($xhtml==0)?'>':' />'; $pos++; $req_nl = $self->nl_inside($elem); # CONTENT foreach my $c ($elem->content_list()) { if (ref $c) { # ELEMENT => recursive call ($accu, $pos, $nl, $req_nl, $wsp, $ai, $bp, $bpi) = $self->_format($c,$cin,$accu,$pos,$nl,$wsp,$ai,$bp,$bpi); } else { # TEXT if ($req_nl && substr($c,0,1) eq ' ') { # starts with white space => can insert requested newlines $self->_add_line($accu,$ai); $self->_add_lines((' ') x ($req_nl -1)) if $req_nl> 1; $accu = ''; $ai = $cin; $pos = 0; $bp = 0; $nl = $req_nl; } encode_entities($c,$self->entities); my @words = split(/\s/,$c); foreach my $w (@words) { ($accu, $ai, $pos, $bp, $bpi) = $self->_add2accu($w,$cin,$accu,$pos,$ai,$bp,$bpi,$wsp); $wsp = 1; # add whitespace after word } # foreach word $nl = 0 if $pos; $wsp = (substr($c,-1,1) eq ' '); # whitespace at end of text segment? } # else TEXT } # foreach content # NEWLINES BEFORE END TAG my $rqnl = $self->nl_inside($elem); $req_nl = $rqnl if $rqnl > $req_nl; $req_nl -= $nl; # END TAG $ai = $indent unless $pos; # use indent outside element for end tag unless ($HTML::Element::emptyElement{$tag} || ($HTML::Element::optionalEndTag{$tag} && !$self->endtag($elem))) { # if endtag required if ($req_nl > 0 && $wsp) { # if new lines required before endtag $self->_add_line($accu,$ai); $accu = ''; $pos = 0; $bp = 0; $ai = $indent; $self->_add_lines((' ') x ($req_nl-1)) if $req_nl-1; $req_nl = 0; } my $etstr = $self->uppercase? "</\U$tag>" : "</$tag>"; ($accu, $ai, $pos, $bp, $bpi) = $self->_add2accu($etstr,$indent,$accu,$pos,$ai,$bp,$bpi,$wsp); $req_nl = 0; $nl = 0; $wsp = 0; } } # else formating by HTML::PrettyPrinter # NEWLINES AFTER ELEMENT my $rqnl = $self->nl_after($elem); $req_nl = $rqnl if $rqnl > $req_nl; if ($req_nl && $self->allow_forced_nl() && $self->force_nl($elem)) { # force newlines $self->_add_line($accu,$ai); $self->_add_lines((' ') x ($req_nl -1)); $accu = ''; $ai = $indent; $pos = 0; $bp = 0; $nl = $req_nl; $req_nl = 0; } return ($accu, $pos, $nl, $req_nl, $wsp, $ai, $bp, $bpi); } 1; __END__
はてなダイアリーにファイルのアップロードが無かったので直書き…
長ったらしく書いてますが、_formatメソッドに2、3行追加してるだけです。*5
これでほぼ理想通りになった!