2016-03-09T21:12:32+09:00
Perl の HTML::TreeBuilder で元の HTML を出力する(DOCTYPE も出力する)
先日、「Perl の HTML::TreeBuilder で元の HTML を出力する」方法を紹介したが、不十分であることが判明した。先日の Perl スクリプトでは DOCTYPE 宣言が抜け落ちてしまう。
どうやら HTML::TreeBuilder では DOCTYPE 宣言は他の要素から切り離されて、ルートオブジェクトの_decl 属性として保持しているらしい。したがって、guts の中に含まれず、完全に元の位置に戻せない。仕様がないから、processing instruction および DOCTYPE 宣言とそれ以外を分けて出力することにした。すなわち、processing instruction 以外のタグが現れたら DOCTYPE 宣言を出力する。
HTML::TreeBuilder は、XHTML の処理は想定していないんだろうなと思う。
あーだこーだと、かなり試行錯誤した修正版を以下に。
#!/usr/bin/env perl use strict; use warnings; use HTML::TreeBuilder; use HTML::Element; use Data::Dumper; my $tree = HTML::TreeBuilder->new; my %opts = ('store_comments'=>1 , 'store_pis'=>1 , 'no_expand_entities'=>1 , 'ignore_unknown'=>0 , 'no_space_compacting'=>1 , # pis, <html> 直下のコメントの順番維持 'implicit_tags'=>0, 'ignore_ignorable_whitespace'=>0); while (my ($k, $v) = each(%opts)) { $tree->$k($v); } $tree->parse_file(*DATA); $tree->eof(); %HTML::Element::optionalEndTag = (); # end tag 省略しない。 my @guts = $tree->guts; if ($tree->attr('_decl')) { # DOCTYPE 宣言が有る場合 while (my ($n, $c) = each(@guts)) { if (ref $c ) { unless ($c->attr('_tag') eq '~pi') { # processing instruction 以外のタグの出現で DOCTYPE 宣言を出力 print $tree->attr('_decl')->as_HTML, "\n"; print $c->as_HTML('', ' '); last; } print $c->as_HTML, "\n"; } } } while (my ($n, $c) = each(@guts)) { print ref $c ? $c->as_HTML('', ' ') : $c; # 2番目の引数をしてないと、改行が無い HTML が出力される。 } __DATA__ <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <!-- comment1 --> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8" /> <title>page title</title> </head> <body> <p>paragraph1</p> <ul> <li>item1</li> </ul> </body> </html>