# mdevSGML_sc ( ver 0.12 ) : # This program compares two SGMLs and reports on the result. # Written by prepress-tips 2009.2.12 - 2009.3.9 # Contact: prepress-tips@users.sourceforge.jp # This program is under the same licensing terms as Perl # ( the Artistic License 1.0 or the GNU GPL ). # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # - 起動 # 開始メッセージを表示する。 msg( 'mdevSGML_sc ( ver 0.12 )'); $fn, $fol; { # 入力ファイル # 入力ファイル @ARGV > 0 || err( ' ファイルを指定してください。' ); my $f = $ARGV[0]; -f $f || err( ' ファイルがありません。' ); $f =~ /^((?:\\|[\00-\x7f\xa0-\xdf]|..)*\\)([^\\]+\.sgml?)$/i || err( ' sgmlファイルを指定してください。' ); ( $fol, $fn ) = ( $1, $2 ); msg( " $fn" ); } $ofn; { # 比較ファイル # 比較ファイル my $f = $fn; $f =~ s/\.sgm$/_old$&/i; -f $fol.$f || err( ' ファイルがありません。' ); $ofn = $f; msg( " vs $ofn" ); } $hfn; { # 出力ファイル # 出力ファイル my $f = $fn; $f =~ s/\.sgm$/_cmp.htm/i; $hfn = $f; } $opt; { # オプション # オプション $opt = @ARGV > 1 ? $ARGV[1] : "" ; $opt eq "" || msg( " option : $opt" ); } =pod - 起動時オプション perl mdevSGML_sc.pl 入力sgml [dscf] d : デバグ用出力あり s : 相違箇所の前後のみ表示 c : 赤・青反転 f : 差分情報を含むsgmlを作成( _diff.sgm ) =cut # - 入力ファイル・比較ファイル @sgm; # 入力sgml @old; # 比較sgml $dtd; # dtd { #+ 入力ファイル・比較ファイルを タグ+テキスト の形に分ける。 # 入力sgmlを読む。 @sgm = getF( $fol.$fn ); # dtd を調べる。 $dtd = ''; for( @sgm ) { /^<\!DOCTYPE\s/ || next; /^(\\n)+,>\\n,g; $in =~ s,(\\n)+\x0a,\x0a,g; # 半角&をエスケープする。 $in =~ s/\&/&/g; # DOCTYPE宣言をエスケープする。 while( $in =~ s/(]*)<(![^>]*)>/$1<$2>/i ) {}; $in =~ //; my ( $p, $q, $r ) = ( $`, $&, $' ); $q =~ s/<((?:.|\s)*?)>((?:\\n)*)/\x0a<$1>$2\x0a/g; $q =~ s/\x0a+/\x0a/g; $in = "$p$q$r"; # タグ+テキストの形に分ける。 $in =~ s/<[^<>]*(?=<)/$&\t/g; $in =~ s/([^\x0a\t])(<)/$1\x0a$2/g; $in =~ s/\t//g; # 結果を保存する。 @{$_[0]} = map do { [ $_ ] }, ( split "\x0a", $in ); } # - 比較ブロック @cmp; # 比較結果 @cmp_out; # 比較結果のデバグ出力 $dmax, $dsame, $dlen; # 許容相違箇所数 と 最小比較長 =pod @cmp ( \@比較ブロック, ・・・ ) @比較ブロック ( '比較状況', \@新文字列ブロック, \@旧文字列ブロック, ・・・ ) '比較状況'は 'pass' 'near' 'ins' 'del' 処理が終われば 'verified' @文字列ブロック ( \@文字列, ・・・ ) タグ+テキスト の形に分ける。 @文字列 ( '文字列', ・・・ ) =cut { #+ 入力ファイル・比較ファイルを 比較ブロックに分け 照合する。 @tags; { # 主要なタグ # dtd が 'mdev' ならば 主要なタグを 'mdevSGML_sc.txt' から読む。 if( $dtd eq( 'mdev' ) ) { getF_tags( 'mdevSGML_sc.txt' ) } # dtd が 'packins' ならば 主要なタグを 'packinsSGML_sc.txt' から読む。 if( $dtd eq( 'packins' ) ) { getF_tags( 'packinsSGML_sc.txt' ) } # 終了タグを追加する。 my @t = @tags; @tags = (); my $lv = ''; my @lv = (); for( @t, '/ ' ) { /\/\s+/ || next; my ( $sp, $tag ) = ( $`, $' ); $sp =~ /^( )*/ && ( $sp = $& ); $lv =~ /^$sp/ && ( $lv = pop @lv ); while( $lv =~ /^$sp / && @lv ) { $lv = pop @lv; push @tags, $lv; } $lv = "$sp/$tag"; push @lv, $lv; push @tags, "$sp$tag"; } pop @tags; # msg( '--- tags', @tags, '---' ); @tags = map do { /^( )*/; $'; }, @tags; } @pass; { # 読み飛ばすタグ # 読み飛ばすタグ @pass = ( 'variablelabel', 'serialno', 'item', 'detail', 'Url' ); } @atr; { # 属性タグ # 属性タグ @atr = ( 'graphic', 'br', 'han', 'gaiji', 'Link', 'chr', 'bold', 'italic', 'under', 'sup', 'sub', 'chem', 'div', 'nom', 'den', ); } my @sgm_blk; { # 入力sgmlを 主要なタグで分割する。 # 入力sgmlを 主要なタグで分割する。 sepTG( \@sgm, \@sgm_blk ); } my @old_blk; { # 比較sgmlを 主要なタグで分割する。 # 比較sgmlを 主要なタグで分割する。 sepTG( \@old, \@old_blk ); } # 比較ブロックを作る。 @cmp = (); my $n = 0; my %t = map do { $_ => ++$n }, @tags; my @s = @sgm_blk; my @o = @old_blk; my $s = 0; my $o = 0; while( @s || @o ) { my ( $sf, $of ) = ( $s <= $o, $o <= $s ); push @cmp, [ 'cmp', $sf ? shift @s : [] , $of ? shift @o : [] ]; $sf && ( $s = @s && @{$s[0][0]}[0] =~ /^<(\S+?)[\s>]/ && defined( $t{ $1 } ) ? $t{ $1 } : ( @s ? $s : $n ), # msg( "s : $s $1" ) ); $of && ( $o = @o && @{$o[0][0]}[0] =~ /^<(\S+?)[\s>]/ && defined( $t{ $1 } ) ? $t{ $1 } : ( @o ? $o : $n ), # msg( "o : $o $1", '' ) ); } # 属性タグを 前の行に連結する。 my $t = '('.( join '|', @atr ).')'; for( @cmp ) { my ( undef, $s, $o, ) = @$_; my $p = undef; for( @$s, undef, @$o ) { defined( $_ ) && ( join '', @$_ ) =~ /^<\/?$t[\s>]/ && defined( $p ) || ( $p = $_, next ); push @$p, @$_; $_ = []; } @$s = map do { @$_ ? $_ : () }, @$s; @$o = map do { @$_ ? $_ : () }, @$o; } # デバグ用出力あり ならば 'chk_cmp.txt' に比較結果を出力する。 # 比較ブロックに 'cmp' があれば 類似度の高いペアにより分離する。 を行う。 msg( ' comparing ...' ); my $n = 0; while( $n < @cmp ) { $n = 0; for( @cmp ) { $$_[0] eq 'cmp' && last; $n++; } sep_pare( $n ); } # デバグ用出力あり ならば 'chk_cmp.txt' に比較結果を出力する。 if( $opt =~ /d/i ) { putF_cmp( 'chk_cmp.txt' ) } } sub sep_pare { # 類似度の高いペアにより分離する。 my $n = $_[0]; # 比較ブロックの番号 my $cb = $cmp[ $n ]; # 対象とする比較ブロック my @pare; # 類似度の高いペアの番号 my $d_msg = ''; # '', 'A', 'B' or 'AB'; # 途中経過の確認メッセージの表示 my $d_sep = 0; # 0 or 1; # 文字への分解を残す # 1文字ずつに分けて 連続する2文字のリストを作り ソートする。 my $s = sep_chr( $$cb[1] ); get_moji2( $s ); sort_moji2( $s ); my $o = sep_chr( $$cb[2] ); get_moji2( $o ); sort_moji2( $o ); push @{$cb}, $s, $o; # 類似度の高い文字列ブロックのペアを探す。 @pare = (); my @s = @{$$cb[3]}; my @o = @{$$cb[4]}; @pare || ( $dmax = 12, $dsame = 6, $dlen = 16, get_pare( \@pare, \@s, \@o ) ); @pare || ( $dmax = 24, $dsame = 9, $dlen = 36, get_pare( \@pare, \@s, \@o ) ); @pare || ( $dmax = 40, $dsame = 12, $dlen = 70, get_pare( \@pare, \@s, \@o ) ); if( $d_msg =~ /A/ && $dsame == 3 || $d_msg =~ /B/ && $dsame != 3 ) { my @p = @pare; my $k = $dsame == 3 ? 'A' : 'B' ; while( @p ) { my ( $i, $j ) = splice( @p, 0, 2 ); msg( '', "=== b:$n s:$i o:$j $k", ( join '', @{@{$$cb[1]}[$i]} ), "---", ( join '', @{@{$$cb[2]}[$j]} ) ); } @pare && `pause`; } # 類似度の高いペアがあれば そのペアで分離する。 if( @pare ) { my @sp = (); my @s = @{$$cb[1]}; my @o = @{$$cb[2]}; while( @pare ) { my ( $i, $j, ) = splice( @pare, -2, 2 ); my @s_r = splice( @s, $i + 1 ); my @o_r = splice( @o, $j + 1 ); my $st = ! @s_r ? 'del' : ! @o_r ? 'ins' : 'cmp' ; ( @s_r || @o_r ) && unshift @sp, [ $st, [ @s_r ], [ @o_r ] ]; @s && @o && unshift @sp, [ 'near', [ pop @s ], [ pop @o ] ]; } ( @s || @o ) && unshift @sp, [ ! @s ? 'del' : ! @o ? 'ins' : 'cmp' , [ @s ], [ @o ] ]; splice( @cmp, $n, 1, @sp ); } else { $$cb[0] = ! @{$$cb[1]} ? 'del' : ! @{$$cb[2]} ? 'ins' : 'pass' ; $d_sep || splice( @{$cb}, -2 ); } } # - 照合 %v_mark; { # 挿入するマーク # 挿入するマーク %v_mark = ( 'del' => '', '/del' => '', 'ins' => '', '/ins' => '', 'same' => '', '/same' => '', ); } { #+ 比較ブロック内で 精密な照合を行う。 # 比較ブロックが 'near' のとき 一致箇所にマークを挿入する。 を行う。 msg( ' verifying ...' ); my $n = 0; while( $n < @cmp ) { $n = 0; for( @cmp ) { $$_[0] =~ /^near$/ && last; $n++; } vrf_same( $n ); } # 比較ブロックが 'pass' のとき 一致箇所にマークを挿入する。 を行う。 my $n = 0; while( $n < @cmp ) { $n = 0; for( @cmp ) { $$_[0] =~ /^pass$/ && last; $n++; } vrf_same( $n ); } # 比較ブロックの 'ins' 'del' にも 一致箇所にマークを挿入する。 を行う。 for( my $n = 0; $n < @cmp; $n++ ) { ${$cmp[$n]}[0] =~ /^(ins|del)$/ || next; vrf_same( $n ); } # デバグ用出力あり ならば 'chk_vrf.txt' に比較結果を出力する。 if( $opt =~ /d/i ) { putF_cmp( 'chk_vrf.txt' ) } } sub vrf_same { # 一致箇所にマークを挿入する。 my $n = $_[0]; # 比較ブロックの番号 my $cb = $cmp[ $n ]; # 対象とする比較ブロック my $d_vmsg = 0; # 0 or 1; # 途中経過の確認メッセージの表示 my $d_vrf = 1; # 0 or 1; # 文字への分解を残す # 文字列ブロックを1つにまとめ さらに1文字ずつに分ける。 my $s = [ map do { "\x0a", @$_ }, @{ sep_chr( $$cb[1] ) } ]; my $o = [ map do { "\x0a", @$_ }, @{ sep_chr( $$cb[2] ) } ]; shift @$s; shift @$o; push @{$cb}, [ $s ], [ $o ]; # 一致箇所を探し マークを挿入する。 my @s = @{@{$$cb[3]}[0]}; my @o = @{@{$$cb[4]}[0]}; my @mark = (); my @mark2 = (); my @check = ( [ 0, scalar @s - 1, 0, scalar @o - 1 ] ); while( @check ) { my $c = shift @check; my ( $ss, $se, $os, $oe, ) = @$c; $ss > $se && $os <= $oe && push @mark, [ $ss, 'del'], [ $ss, "o:$os-$oe"], [ $ss, '/del']; $ss <= $se && $os > $oe && push @mark, [ $ss, 'ins' ], [ $se + 1, '/ins' ]; $ss <= $se && $os <= $oe || next; my @f = find_same( \@s, \@o, @$c, $d_vmsg ); $f[0] <= $f[1] || do { push @mark, [ $ss, 'del'], [ $ss, "o:$os-$oe"], [ $ss, '/del'], [ $ss, 'ins' ], [ $se + 1, '/ins' ]; next; }; push @mark2, [ $f[0], 'same' ]; push @mark, [ $f[1] + 1, '/same' ]; ( $ss < $f[0] || $os < $f[2] ) && push @check, [ $ss, $f[0] - 1, $os, $f[2] - 1 ]; ( $f[1] < $se || $f[3] < $oe ) && push @check, [ $f[1] + 1, $se, $f[3] + 1, $oe ]; @check = sort { $$a[0] <=> $$b[0] } @check; } # @mark を @sに反映させる。 my @m = sort { $$a[0] <=> $$b[0] } @mark, @mark2; while( @m ) { my ( $p, $m ) = @{ pop @m }; $m =~ /^o:(\d+)-(\d+)$/ || ( splice( @s, $p, 0, $v_mark{ $m } ), next ); my $os = $1; my $oe = $2; my @ox = splice( @o, $os, $oe - $os + 1 ); splice( @o, $os, 0, @ox ); splice( @s, $p, 0, @ox ); } push @{$cb}, [ [ @s ] ]; # 'near' のとき 先頭の相違を 1つ前の比較ブロックに移す。 my $pb = ( 0 < $n ) ? $cmp[ $n - 1 ] : undef ; my $v = ( $$cb[0] eq 'near' ) ? @{$$cb[ @$cb - 1 ]}[0] : undef ; defined( $pb ) && $$pb[0] eq 'pass' && defined( $v ) && ( $$v[0] =~ /^$/ || $$v[0] eq '' && $$v[1] =~ /^<\/\S+>$/ && $$v[2] eq '' && $$v[3] =~ /^$/ ) && do { my $si = ''; my $sd = ''; $$v[0] eq '' && ( $si .= $$v[1], $sd .= $$v[1], splice( @$v, 0, 3 ) ); while( $$v[0] =~ /^$/ ) { my $k = $1; shift @$v; my $x = ( $k eq 'ins' ) ? \$si : \$sd ; my $u; while( ( $u = shift @$v ) ne "" ) { $$x .= $u; } } push @{$$pb[1]}, map do { [ $_ ] }, split "\x0a", $si; push @{$$pb[2]}, map do { [ $_ ] }, split "\x0a", $sd; }; # 文字列ブロックに戻す。 my $s = join '', map do { join '', @$_ }, @{$$cb[3]}; my $o = join '', map do { join '', @$_ }, @{$$cb[4]}; @{$$cb[1]} = map do { [ $_ ] }, split "\x0a", $s; @{$$cb[2]} = map do { [ $_ ] }, split "\x0a", $o; $$cb[0] = 'verified'; $d_vrf || splice( @{$cb}, -2 ); } # - 補正 @adj; # 補正結果 { #+ 比較結果を見やすく補正する。 # 比較ブロックから文字列を取り出す。 @adj = map do { @{ @{$$_[ @$_ - 1 ]}[0] } }, @cmp; # カナの相違を見やすくする。 msg( ' adjusting ...' ); my $v = \@adj; my @p; my $ns = 0; while( @p = find_K( $v, $ns ) ) { my ( $n, $m, $l ) = @p; my @v; $ns = $n; # sameの先頭のカナを delとinsの末尾に移動する。 @v = splice( @$v, $m + 2, $l ); splice( @$v, $n, 0, @v ); $n += $l; $m += $l; splice( @$v, $m, 0, @v ); $m += $l; # ・・・カナ # ↓ # カナ・・・カナ; # sameが空になる || next; $$v[ $m + 1 ] eq '' && $$v[ $m + 2 ] eq '' || next; # 空のsameを削除する。 splice( @$v, $m + 1, 2 ); # del ins del となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # del を合体する。 for( $l = $m + 2; $l < @$v && $$v[ $l ] ne ''; $l++ ) { ; } $l -= $m + 2; @v = splice( @$v, $m + 2, $l ); splice( @$v, $n, 0, @v ); $n += $l; $m += $l; splice( @$v, $m + 1, 2 ); # ・・・文字列 # ↓ # 文字列・・・; # ins ins となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # ins を合体する。 splice( @$v, $m, 2 ); # ・・・文字列 # ↓ # ・・・文字列; } # 1文字のかなの相違を見やすくする。 my $v = \@adj; my @p; my $ns = 0; while( @p = find_H( $v, $ns ) ) { my ( $n, $m, $l ) = @p; my @v; $ns = $n; # sameのかな1文字を delとinsの末尾に移動する。 @v = splice( @$v, $m + 2, $l ); splice( @$v, $n, 0, @v ); $n += $l; $m += $l; splice( @$v, $m, 0, @v ); $m += $l; # ・・・かな # ↓ # かな・・・かな; # sameが空になる || next; $$v[ $m + 1 ] eq '' && $$v[ $m + 2 ] eq '' || next; # 空のsameを削除する。 splice( @$v, $m + 1, 2 ); # del ins del となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # del を合体する。 for( $l = $m + 2; $l < @$v && $$v[ $l ] ne ''; $l++ ) { ; } $l -= $m + 2; @v = splice( @$v, $m + 2, $l ); splice( @$v, $n, 0, @v ); $n += $l; $m += $l; splice( @$v, $m + 1, 2 ); # ・・・文字列 # ↓ # 文字列・・・; # ins ins となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # ins を合体する。 splice( @$v, $m, 2 ); # ・・・文字列 # ↓ # ・・・文字列; } # 先頭の終了タグを見やすくする。 my $v = \@adj; my @p; my $ns = 0; while( @p = find_E( $v, $ns ) ) { my ( $n, $nl, $m, $ml ) = @p; my @v; $ns = $n; # sameの区切りを移動する。 @v = splice( @$v, $m, 2 ); splice( @$v, $m + $ml, 0, @v ); $m += $ml; $n || ( splice( @$v, 0, 0, '', '' ), $n = 2 ); @v = splice( @$v, --$n, 2 ); splice( @$v, $n + $nl, 0, @v ); $n += $nl; # タグ・・・タグ # ↓ # タグ・・・タグ; # sameが空になる || next; $$v[ $m + 1 ] eq '' && $$v[ $m + 2 ] eq '' || next; # 空のsameを削除する。 splice( @$v, $m + 1, 2 ); # del ins del となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # del を合体する。 for( $l = $m + 2; $l < @$v && $$v[ $l ] ne ''; $l++ ) { ; } $l -= $m + 2; @v = splice( @$v, $m + 2, $l ); splice( @$v, $n, 0, @v ); $n += $l; $m += $l; splice( @$v, $m + 1, 2 ); # ・・・文字列 # ↓ # 文字列・・・; # ins ins となる || next $$v[ $m ] eq '' && $$v[ $m + 1 ] eq '' || next; # ins を合体する。 splice( @$v, $m, 2 ); # ・・・文字列 # ↓ # ・・・文字列; } # 比較結果を確認する。 my $d = join '', map do { my $v = @$_[ scalar @$_ - 1 ]; map do { join '', @$_ }, @$v; }, @cmp; $d =~ s/<\/?my:same>//g; my $n = ''; my $o = ''; while( $d =~ // ) { $n .= $`; $o .= $`; $d = $'; my $k = $1; $d =~ /<\/my:$k>/ || last; $d = $'; if( $k eq 'ins' ) { $n .= $`; } else { $o .= $` } } $n .= $d; $o .= $d; $n =~ s/&(enter;)/&$1/g; $n =~ s/\x0a//g; $n =~ s/\\t/\t/g; $n =~ s/\\n/\x0a/g; $n =~ s/\\((\\)+[tn])/$1/g; $n =~ s/\x0a+/\x0a/g; $o =~ s/&(enter;)/&$1/g; $o =~ s/\x0a//g; $o =~ s/\\t/\t/g; $o =~ s/\\n/\x0a/g; $o =~ s/\\((\\)+[tn])/$1/g; $o =~ s/\x0a+/\x0a/g; my $ss = join '', getF( $fol.$fn ); $ss =~ s/\x0a+/\x0a/g; my $so = join '', getF( $fol.$ofn ); $so =~ s/\x0a+/\x0a/g; $n eq $ss && $o eq $so || msg( ' 比較結果を正しく作成できませんでした。' ); # デバグ用出力あり ならば unlink( 'chk_no.txt', 'chk_ni.txt', 'chk_oo.txt', 'chk_oi.txt' ); if( $opt =~ /d/i ) { unlink( 'chk_no.txt', 'chk_ni.txt', 'chk_oo.txt', 'chk_oi.txt' ); } # デバグ用出力あり && $n ne $ss ならば msg( ' output: chk_no.txt, chk_ni.txt' ); putF( 'chk_no.txt', $n ); putF( 'chk_ni.txt', $ss ); if( $opt =~ /d/i && $n ne $ss ) { msg( ' output: chk_no.txt, chk_ni.txt' ); putF( 'chk_no.txt', $n ); putF( 'chk_ni.txt', $ss ); } # デバグ用出力あり && $o ne $so ならば msg( ' output: chk_oo.txt, chk_oi.txt' ); putF( 'chk_oo.txt', $o ); putF( 'chk_oi.txt', $so ); if( $opt =~ /d/i && $o ne $so ) { msg( ' output: chk_oo.txt, chk_oi.txt' ); putF( 'chk_oo.txt', $o ); putF( 'chk_oi.txt', $so ); } # 差分情報を含むsgmlを作成 ならば 比較結果をSGML形式で出力する。 if( $opt =~ /f/i ) { my @o = map do { my $v = @$_[ scalar @$_ - 1 ]; map do { join '', @$_ }, @$v; }, @cmp; for( @o ) { s/<\/?my:same>//g; # s/<\/?my:ins>//g; s/(.|\s)*?<\/my:del>//g; # s/(.|\s)*?<\/my:ins>//g; s/<\/?my:del>//g; s/&(enter;)/&$1/g; s/\x0a//g; s/\\t/\t/g; s/\\n/\x0a/g; s/\\((\\)+[tn])/$1/g; } my $f = $fn; $f =~ s/\.sgm$/_diff.sgm/i; msg( ' output: '.$f ); putF( $fol.$f, join '', @o ); } } # - 出力 @out; { # 出力html # 出力html @out = (); my %h_mark; { # マークに対応するhtml # マークに対応するhtml my @c = ( '#6666ee', '#ee5566' ); # 赤・青反転 ならば @c = reverse @c if( $opt =~ /c/i ) { @c = reverse @c } %h_mark = ( '' => '', '' => '', '' => '', '' => '', '' => '', '' => '', ); } my $h_head; { # ヘッダ # ヘッダ @h_head = ( '', '', '', '', 'mdevSGML_sc:'.$fn, '', '', ); } my $h_exp; { # 冒頭の説明 # 冒頭の説明 $h_exp = join "\x0a", ( '
SGMLファイルの比較 ( 色の意味: '. '削除文字列 挿入文字列 )', '
  新しいSGML  '.$fn .' '.get_mt( $fol.$fn ), '
  元のSGML   '.$ofn.' '.get_mt( $fol.$ofn ), '
', ); $h_exp .= '
'; $h_exp =~ s/<\/?my:.*?>/$h_mark{ $& }/g; } my @h_foot; { # フッタ # フッタ @h_foot = ( '
', ); } # 一致箇所のマークを htmlタグに変える。 msg( ' converting ...' ); for( @adj ) { /<\/?my:.*?>/ && ( s/<\/?my:.*?>/$h_mark{ $& }/g, next ); s//>/g; } @out = split "\x0a", join '', @adj; # 相違箇所の前後のみ表示 ならば 相違箇所の前後のみを抽出する。 if( $opt =~ /s/i ) { my @f = map do { $h_mark{ $_ } }, ( '', '' ); uniqueA( \@f ); my $font = '('.( join '|', @f ).')'; my $font_e = ''; my @m = (); my $f = 0; my $n = -1; my $d = 3; for( @out ) { ++$n; $f || /$font/ || /$font_e/ || next; push @m, $n; /^(?:.|\s)*($font|$font_e)/ && ( $f = $1 ne $font_e ); } my $br = "
\n"; my @o = @out; @out = ( $br.$br.'相違箇所の前後のみを表示'.$br ); my $an = 1; for( $n = 0; $n < @m; $n++ ) { my $s = ( $m[ $n ] < $d ) ? 0 : $m[ $n ] - $d ; while( $n + 1 < @m && $m[ $n + 1 ] <= $m[ $n ] + $d ) { $n++; } my $e = ( $m[ $n ] + $d < @o ) ? $m[ $n ] + $d : @o ; push @out, "$br******$br"; $an++; for( my $i = $s; $i < $e + 1; $i++ ) { push @out, $o[ $i ]; } push @out, $br; } } # タブと改行をアンエスケープする。 my $atr = '('.( join '|', 'variablelabel', @atr ).')(>|\s)'; my $atr_e = '('.( join '|', @pass, @atr ).')(>|\s)'; my @f = map do { $_ eq '' ? () : $_ }, values %h_mark; uniqueA( \@f ); my $font = '(\s|'.( join '|', @f ).')'; my $out = join "\x0a", @out; $out =~ s/&enter;(\\n)?/$&
/g; $out =~ s/<(?!\/)(?!$atr)\S+(>|\s)/
$&/g; $out =~ s/<\/(?!$atr)(.|\s)*?>(\\n)?$font*(?=<\/(?!$atr_e))/$&
/g; # $out =~ s/\\t/\t/g; $out =~ s/\\n/
/g; $out =~ s/\\((\\)+[tn])/$1/g; $out =~ s/\\t/\t/g; $out =~ s/\\n//g; $out =~ s/\\((\\)+[tn])/$1/g; $out =~ s/^
//; @out = split "\x0a", $out; # htmlの体裁を整える。 unshift @out, @h_head, $h_exp; push @out, @h_foot; # 結果を出力する。 msg( ' output: '.$hfn ); putF( $fol.$hfn, join "\x0a", @out ); # デバグ用出力あり ならば 'chk_out.htm' に結果を出力する。 if( $opt =~ /d/i ) { putF( 'chk_out.htm', join "\x0a", @out ); } } # 処理の詳細 ‥ # - 開始 # - 入力ファイル・比較ファイル # - エスケープ # - 比較 sub getF_tags { # 主要なタグを( fn )から読む。 -f $_[0] || err( ' タグリストがありません。' ); @tags = getF( $_[0] ); for( @tags ) { s/\s*$//; } } sub sepTG { # ( sgml )を 主要なタグ で分割し( block )に格納する。 my ( $sgml, $block, ) = @_; my @b = (); my @t = map do { /^\// ? () : $_ }, @tags; my $p = '('.( join '|', @pass ).')'; my $r = 0; for( @{$sgml} ) { my $m = @$_[0]; $m =~ /^<\/?$p[\s>]/ && ( ( push @b, $_ ), next ); my $n = 0; for( ; $n < @t; $n++ ) { my $t = $t[ $n ]; $m =~ /^<\/?$t[\s>]/ && last; } $n < @t && ( $r = $n, @b && ( push @{$block}, [ @b ] ), @b = () ); push @b, $_; } @b && push @{$block}, [ @b ]; } sub sep_chr { # ( array )を1文字ずつに分ける。 my @m = map do { join '', @$_ }, @{$_[0]}; my @fmt = ( '<[!/]?[.0-9a-zA-Z\s="_-]+>?', '\&enter;', '\&', '(?:\\\\)+[tn]', '\*+', '\d+', '[\x00-\x7f\xa0-\xdf]', '順序番号', '項目名', '内容', '..', ); my @fmt_t = ( '<\S+', '[^="_\.\d\s]+', '\d+', '\s+', '.' ); # '"[^"]*"', my $fmt = '('.( join '|', @fmt ).')'; my $fmt_t = '('.( join '|', @fmt_t ).')'; @m = map do { [ /$fmt/g ] }, @m; @m = map do { [ map do { /^ $$b[3] } sort { $$a[4] <=> $$b[4] } sort { $$a[2] <=> $$b[2] } reverse @p; ( $i, $j, ) = @{$p[0]}; push @$pare, ( $i, $j ); $i++; $j++; } } sub get_diff2 { # 2行の類似度を調べる。 my ( $a, $b, ) = @_; my $ai = 0; my $an = @{$a}; my $ad = 0; my $bi = 0; my $bn = @{$b}; my $bd = 0; while( $bi < $bn ) { $ad < $dmax && $bd < $dmax || last; my $x = $ai < $an ? $$a[$ai] cmp $$b[$bi] : 1 ; $x == 0 && ( $ai++, $bi++, next ); $x < 0 && ( $ad++, $ai++, next ); $x > 0 && ( $bd++, $bi++, next ); } $ai < $an && ( $ad += $an - $ai, $bd += $an - $ai ); my $r = ( $an < $dlen || $an - $ad < $dlen ) ? $dmax : $ad < $bd ? $ad : $bd ; $r; } sub putF_cmp { # ( fn )に比較結果を出力する。 @cmp_out = map do { my ( $st, $s, $o, $s2, $o2, $v2, ) = @$_; ( '', '========== '.$st, ( map do { join '', @$_ }, @$s ), '----------', ( map do { join '', @$_ }, @$o ), ( ! @$s2 && ! @$o2 ? () : ( '‥‥‥‥‥', ( map do { '--- '.@$_, @$_, }, @$s2 ), '----------', ( map do { '--- '.@$_, @$_, }, @$o2 ) ) ), ( ! defined( $v2 ) || ! @$v2 ? () : ( '‥‥‥‥‥', @{@$v2[0]} ) ), ); }, @cmp; push @cmp_out, ''; putF( $_[0], join "\x0a", @cmp_out ); } # - 照合 sub find_same { # 一致箇所を探す。 my ( $s, $o, $ss, $se, $os, $oe, $d_msg, ) = @_; $d_msg && msg_pare( $s, $o, $ss, $se, $os, $oe, "====== $ss $se $os $oe" ); my $fmt = '(?:[\x80-\x9f\xe0-\xef].)$'; my $fmt2 = '(?:\x83[\x40-\x7e\x80-\x96]|\x81\x5b)'; my $fmt3 = '(?:' && 0 < $n && $$v[ $n - 1 ] =~ /^$kana$/ && $n < @$v - 1 && $$v[ $n + 1 ] eq '' || next; for( $m = $n + 2; $m < @$v && $$v[ $m ] ne ''; $m++ ) { ; } $m + 2 < @$v && $$v[ $m + 1 ] eq '' && $$v[ $m + 2 ] =~ /^$kana$/ || next; for( $l = $m + 3; $l < @$v && $$v[ $l ] =~ /^$kana$/; $l++ ) { ; } last; } $n < @$v ? ( $n, $m, $l - $m - 2 ) : () ; } sub find_H { # delとinsの後のsameが かな1文字のところ を探す。 my $v = $_[0]; my $ns = $_[1]; my $hira = '(?:\x82[\x9f-\xf1]|\x81\x5b)'; my $n, $m, $l; for( $n = $ns; $n < @$v; $n++ ) { $$v[ $n ] eq '
' && $n < @$v - 1 && $$v[ $n + 1 ] eq '' || next; for( $m = $n + 2; $m < @$v && $$v[ $m ] ne ''; $m++ ) { ; } $m + 4 < @$v && $$v[ $m + 1 ] eq '' && $$v[ $m + 2 ] =~ /^$hira$/ && $$v[ $m + 3 ] eq '' && $$v[ $m + 4 ] =~ /^$/ || next; last; } $n < @$v ? ( $n, $m, 1 ) : () ; } sub find_E { # del・ins の頭が終了タグで 直後の same の頭が同じタグのところ を探す。 my $v = $_[0]; my $ns = $_[1]; my $etag = '(?:)'; my $n, $nl = 1, $m, $ml = 1; for( $n = $ns; $n < @$v; $n++ ) { $$v[ $n ] =~ /^$/ && ! ( 0 < $n && $$v[ $n - 1 ] ne '
' ) || next; my $e = ""; my $p = ''; for( $m = $n + 1; $m < @$v && $$v[ $m ] ne $e; $m++ ) { $p .= $$v[ $m ]; } $p =~ /^\x0a?($etag)/ || next; my $t = $1; $m + 1 < @$v && $$v[ $m + 1 ] eq '' || next; $e = ""; my $l; my $q = ''; for( $l = $m + 2; $l < @$v && $$v[ $l ] ne $e; $l++ ) { $q .= $$v[ $l ]; } $q =~ /^\x0a?$t/ || next; $nl = ( $p =~ /^\x0a/ ) ? 2 : 1 ; $ml = ( $q =~ /^\x0a/ ) ? 2 : 1 ; last; } $n < @$v ? ( $n, $nl, $m, $ml ) : () ; } # - 出力 sub uniqueA { # ( array )の重複を除去する my $n = ''; @{$_[0]} = map do { my $p = $n; $n = $_; $p eq $n ? () : $n; }, @{$_[0]}; } sub get_mt { # ( fn )のタイムスタンプ my $fn = $_[0]; my ( $s, $m, $h, $d, $n, $y, ) = localtime( @{[ lstat( $fn ) ]}[9] ); $y += 1900; $n++; sprintf "%04d/%02d/%02d %02d:%02d:%02d", $y, $n, $d, $h, $m, $s; } # - 補助の定型ルーチン sub quotemeta_ja { # 日本語文字列( str )のquotemeta join '', map do{ s/(.)([\x40\x5b-\x60\x7b-\x7f])/$1\\$2/; $_ ; }, ( $_[0] =~ /([\x00-\x7f\xa0-\xdf]|..)/g ); } sub getF { # ファイル( name )を読む。 open( IN, '<'.$_[0] ) || err( 'オープンエラー:'.$_[0] ); my @buf = ; close( IN ); @buf; } sub putF { # ファイル( name )に( string )を出力する。 if( open( OUT, '>'.$_[0] ) ) { print OUT $_[1]; close( OUT ); } else { err( 'オープンエラー:'.$_[0] ); } } sub err { # メッセージ( array )を表示して エラー終了する。 msg( @_ ); exit( 1 ); } sub msg { # メッセージ( array )を表示する。 print map do { $_."\x0a" }, @_; } # - 構文 # - ライセンス # ~ スクリプトの冒頭に記述。