# mdevSGML_sc2 ( ver 0.1 ) :
# This program converts the result of comparing two SGMLs into the HTML form.
#   Written by prepress-tips 2009.3.18
#   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_sc2 ( ver 0.1 )');
        $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" );
        }
        $hfn;  { # 出力ファイル名
            # 出力ファイル名
                my $n = ( $fn =~ /\.sgml?$/ ) ? $` : $fn ;
                $hfn = $n.'_cmp2.htm';
        }
        $n_fn,  $o_fn,  $c_fn;  { # 新・旧・差分sgml ファイル名
            # 新・旧・差分sgml ファイル名
                my $n = ( $fn =~ /\.sgml?$/ ) ? $` : $fn ;
                ( $n_fn, $o_fn, $c_fn ) = ( $fn, $n.'_old.sgm', $n.'_diff.sgm' );
                -f $fol.$n_fn && -f $fol.$o_fn || do {
                    -f $fol.$n_fn || msg( '  入力SGMLファイルがありません。' );
                    -f $fol.$o_fn || msg( '  比較対象のSGMLファイルがありません。' );
                    err();
                };
        }
        $ch_fn;  { # 比較html ファイル名
            # 比較html ファイル名
                my $n = ( $fn =~ /\.sgml?$/ ) ? $` : $fn ;
                $ch_fn = $n.'_cmp.htm';
        }
        $m_fn;  { # マークsgml ファイル名
            # マークsgml ファイル名
                my $n = ( $fn =~ /\.sgml?$/ ) ? $` : $fn ;
                $m_fn = $n.'_mark.sgm';
        }
        $nh_fn, $oh_fn, $mh_fn;  { # 新・旧・マークhtml ファイル名
            # 新・旧・マークhtml ファイル名
                my $n = ( $fn =~ /\.sgml?$/ ) ? $` : $fn ;
                ( $nh_fn, $oh_fn, $mh_fn ) = ( $n.'.htm', $n.'_old.htm', $n.'_mark.htm' );
        }

        $opt;  { # オプション
            # オプション
                $opt = @ARGV > 1 ? $ARGV[1] : "" ;
                $opt eq "" || msg( "  option : $opt" );
        }
        $wfol;  { # 作業フォルダ
            # 作業フォルダ
                # 作業フォルダを作る。
                    my $sfn = ( $0 =~ /^((?:\\|[\00-\x7f\xa0-\xdf]|..)*\\)/i ) ? $' : $0 ;
                    my $sfol = ( `cd` =~ /^.*/ ) ? "$&\\" : '';
                    -f $sfol.$sfn || err( '  作業フォルダの場所がわかりません。' );
                    $wfol = "$sfol\\work\\";
                    -d $wfol || -e $wfol || mkdir $wfol;
                    -d $wfol || err( '  作業フォルダを作れません。' );
                # 作業フォルダを初期化する。
                    my @n = ( $n_fn,  $o_fn,  $c_fn, $ch_fn, $m_fn, $nh_fn, $oh_fn, $mh_fn );
                    for( @n ) { -f $wfol.$_ && unlink( $wfol.$_ ); }
                    my @r = map do { -e $wfol.$_ ? $_ : () }, @n;
                    @r && err( '  作業フォルダを初期化できません。' );
        }

=pod - 起動時オプション
        perl   mdevSGML_c2h.pl   入力sgml   [d]
            d : デバグ用出力あり
=cut

# - 処理

    $dtd;  { # dtd を調べる。
        # dtd を調べる。
            $dtd = '';
            for( getF( $fol.$fn ) ) {
                /^<\!DOCTYPE\s/ || next;
                /^<!DOCTYPE MDEVICES SYSTEM "medical_device.dtd"/ && ( $dtd = 'mdev' );
                /^<!DOCTYPE PACKINS SYSTEM "package_insert.dtd"/ && ( $dtd = 'packins' );
                last;
            }
    }
    $s2h, $s2h_fol, $s2h_exe;  { # s2h変換プログラムを探す。
        # s2h変換プログラムを探す。
            $s2h = $dtd.'SGML_s2h'; $s2h_exe = $s2h.'.exe';
            $s2h_fol = ( -e "..\\$s2h\\" )     ? "..\\$s2h\\" :
                       ( -e "..\\..\\$s2h\\" ) ? "..\\..\\$s2h\\" : '' ;
            $s2h_fol ne '' && -f $s2h_fol.$s2h_exe ||
                err( '  '.$s2h.' 変換プログラムが見つかりません。' );
    }
    $n_sgm, $o_sgm, $c_sgm, $m_sgm;  # 新・旧・差分・マークsgml 
    $n_htm, $o_htm, $m_htm;  # 新・旧・マークhtml 

    @cs_diff, $cd_no;  # 差分sgmlの相違一覧 
    @c2m;  # 差分sgmlとマークの対応 
    @atr;  { # 属性タグ
        # 属性タグ
            @atr = ( 'br', 'han', 'gaiji', 'Link',
                     'chr', 'bold', 'italic', 'under', 'sup', 'sub',
                     'chem', 'div', 'nom', 'den', );
    }

    { #+ 差分sgml → マークsgml → マークhtml
        # 新・旧・差分sgml をコピーする。
            $n_sgm = join '', getF( $fol.$n_fn ); putF( $wfol.$n_fn, $n_sgm );
            $o_sgm = join '', getF( $fol.$o_fn ); putF( $wfol.$o_fn, $o_sgm );
            $c_sgm = join '', getF( $fol.$c_fn ); putF( $wfol.$c_fn, $c_sgm );
        # 新・旧sgmlと 差分sgmlを照合する。
            my $c = $c_sgm; my $n = ''; my $o = '';
            while( $c =~ /<my:(ins|del)>/ ) {
                $n .= $`; $o .= $`; $c = $'; my $k = $1; $c =~ /<\/my:$k>/ || last;
                $c = $'; if( $k eq 'ins' ) { $n .= $`; } else { $o .= $` }
            }
            $n .= $c; $o .= $c;
            $n =~ s/\x0a+/\x0a/g;
            $o =~ s/\x0a+/\x0a/g;
            my $ss = join '', $n_sgm; $ss =~ s/\x0a+/\x0a/g;
            my $so = join '', $o_sgm; $so =~ s/\x0a+/\x0a/g;
            $n eq $ss && $o eq $so || msg( '  差分SGMLが正しく作成されていません。' );
            # デバグ用出力あり ならば 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を作る。
            # デバグ用出力あり ならば msg( "  making ... $m_fn" )
                if( $opt =~ /d/i ) {  msg( "  making ... $m_fn" ) }
            $m_sgm = $c_sgm; @c2m = ();
            # 〓のエスケープ
                $m_sgm =~ s/(?:〓)+/$&〓 /g;
            # 属性タグのエスケープ
                my $t = '(?:'.( join '|', @atr ).')';
                $m_sgm =~ s/<(\/?$t(?:\s[^>]*)?)>/&lt;$1&gt;/g;
            # タグ内の差分情報を削除
                my $m = $m_sgm; $m_sgm = ''; $cd_no = 0;
                while( $m =~ /<(\w.*?)(?=[\s>])/ ) {
                    $m_sgm .= $`; $m = $&.$'; my $t = $1;
                    $t =~ /^my:/ && do {
                        $m =~ /<\/$t>/ || last; $m_sgm .= $`.$&; $m = $';
                        push @cs_diff, $cd_no++; next;
                    };
                    $t = '';
                    while( $m =~ /(<\/?my:|>)/ && $& ne '>' ) {
                        $t .= $`.$&; $m = $'; $& eq '<my:' && $cd_no++;
                        $m =~ />/ || last; $t .= $`.$&; $m = $';
                    }
                    $m =~ />/ || last; $t .= $`.$&; $m = $';
                    if( $t =~ /^<graphic\s/ ) { $t = graphic_esc( $t ); }
                    else {
                        $t =~ s/<my:del>((?:.|\s)*?)<\/my:del>//g;
                        $t =~ s/<my:ins>((?:.|\s)*?)<\/my:ins>/$1/g;
                    }
                    $m_sgm .= $t;
                }
                $m_sgm .= $m;
                # msg_cs_diff();
            # 差分情報をマークに変換
                my $m = $m_sgm; $m_sgm = ''; my $n = 0; my @n = ();
                while( $m =~ /<my:(ins|del)>/ ) {
                    my $k = $1; $m_sgm .= $`; $m = $'; my $c2m = '';
                    $m =~ /<\/my:$k>/ || last;
                    my $s = $`; $m = $'; my $r = '〓'.$k.++$n.'〓'; $c2m .= "$n:"; push @n, $n;
                    $s =~ /^<\/\S+>/ && ( $s = $', $r = $&.$r );
                    while( $s =~ /<(\/?)(\S+?)(?:\s[^>]*)?>/ ) {
                        my $t = quotemeta( $2 ); $r .= $`; $s = $';
                        $1 eq '' && ( $r .= $&.'〓'.$k.++$n.'〓', $c2m .= "$n:", push @n, $n );
                        $1 ne '' && ( @n && ( $r .= '〓/'.$k.( pop @n ).'〓' ), $r .= $& );
                    }
                    $r .= $s; @n && ( $r .= '〓/'.$k.( pop @n ).'〓' );
                    $m =~ /^<\/\S+>/ &&
                        ( $m = $', $r .= $&, @n && ( $r .= '〓/'.$k.( pop @n ).'〓' ) );
                    $m_sgm .= $r;
                    push @c2m, [ split ':', $c2m ];
                }
                $m_sgm .= $m;
                @c2m == @cs_diff || msg( '  マーク挿入箇所と相違箇所の個数が違っています。' );
                # my $i = 0; msg( '--- c2m ---', map do { join ' ', $i++, ':', @$_ }, @c2m );
            # 不要マークの移動・削除
                my $fss = '<serialno(?=[\s>])[^>]*?>';
                my $fvs = '<variablelabel(?=[\s>])[^>]*?>';
                my $fve = '</variablelabel>';
                my $fms = '〓(?:ins|del)\d+〓';
                my $fme = '〓/(?:ins|del)\d+〓';
                $m_sgm =~ s/($fss(?:$fms)?$fvs)$fms((?:.|\s)*?)$fme($fve)/$1$2$3/g;
            
                my $mark = '〓\/?(?:ins|del)(\d+)〓';
                $m_sgm =~ s/($mark)(<variablelabel(?=[\s>])(?:.|\s)*?<\/variablelabel>)/$3$1/g;
                my @d = ( $m_sgm =~ /(<\/\w[^>]*?>\s*(?:$mark\s*)+(?=<\/?\w))/g );
                @d = map do { /(\d+)/g }, map do { /(〓\/?(?:ins|del)\d+〓)/g }, @d;
                @d = sort { $a <=> $b } @d; uniqueA( \@d );
            
                my %d = map do { $_ => 1 }, @d; delete( $d{ '' } );
                $m_sgm =~ s/$mark/ defined( $d{ $1 } ) ? '' : $& /eg;
                @c2m = map do {
                    [ map do { defined( $d { $_ } ) ? () : $_ }, @$_ ];
                }, @c2m;
                # msg( '削除されたマーク', join ' ', sort { $a <=> $b } keys %d );
                # my $i = 0; msg( '--- c2m ---', map do { join ' ', $i++, ':', @$_ }, @c2m );
            # 属性タグのアンエスケープ
                my $t = '(?:'.( join '|', @atr ).')';
                $m_sgm =~ s/&lt;(\/?$t(?=\s|&gt;).*?)&gt;/<$1>/g;
            # 画像タグのアンエスケープ
                $m_sgm =~ s/&lt;(graphic(?:\s[^"]*"[^"]*"[^>]*?)?)&gt;/<$1>/g;
            # マーク挿入の確認
                my $mark_s = '〓(?:ins|del)\d+〓'; my $mark_e = '〓/(?:ins|del)\d+〓';
                my @m = ( $m_sgm =~ /($mark_s(?:.|\s)*?$mark_e)/g );
                $mark_s = '〓(?:ins|del)(\d+)〓'; $mark_e = '〓/(?:ins|del)(\d+)〓';
                @m = map do {
                    /$mark_s((?:.|\s)*?)$mark_e/; my $sn = $1; my $r = $2; my $en = $3;
                    ( $sn ne $en || $r =~ /$mark_s/ || $r =~ /$mark_e/ ) ? $_ : () ;
                }, @m;
                @m && msg( '  挿入削除マークを正しく処理できませんでした。',
                           map do { "    $_" }, @m );
            putF( $wfol.$m_fn, $m_sgm );
        # 新・旧・マークhtmlを作る。
            # デバグ用出力あり ならば msg( "  making ... $nh_fn", "             $oh_fn", "             $mh_fn" )
                if( $opt =~ /d/i ) {  msg( "  making ... $nh_fn", "             $oh_fn", "             $mh_fn" ) }
            -f $wfol.$n_fn && -f $wfol.$o_fn && -f $wfol.$m_fn ||
                err( '  '.$s2h_exe.' の入力ファイルを準備できませんでした。' );
            select( STDOUT ); $| = 1;
            msg( '  execute '.$s2h_exe.' ...', '  しばらく お待ちください ・・・', '' );
            my $cmd = "cd \"$s2h_fol\" && $s2h_exe \"$wfol$n_fn\" |";
            open( CMD, $cmd ); while( ! eof( CMD ) ) { print getc( CMD ); }
            msg( '' );
            my $cmd = "cd \"$s2h_fol\" && $s2h_exe \"$wfol$o_fn\" |";
            open( CMD, $cmd ); while( ! eof( CMD ) ) { print getc( CMD ); }
            msg( '' );
            my $cmd = "cd \"$s2h_fol\" && $s2h_exe \"$wfol$m_fn\" |";
            open( CMD, $cmd ); while( ! eof( CMD ) ) { print getc( CMD ); }
            msg( '', '  '.$s2h_exe.' finished ...' );
            select( STDOUT ); $| = 0;
            -f $wfol.$nh_fn && -f $wfol.$oh_fn && -f $wfol.$mh_fn ||
                err( '  出力ファイルを作成できませんでした。' );
        # 新・旧・マークhtmlを読む。
            $n_htm = join '', getF( $wfol.$nh_fn );
            $o_htm = join '', getF( $wfol.$oh_fn );
            $m_htm = join '', getF( $wfol.$mh_fn );
        # 新・旧htmlと マークhtmlを照合する。
            my $m = $m_htm; my $n = ''; my $o = '';
            while( $m =~ /〓(ins|del)(\d+)〓/ ) {
                $n .= $`; $o .= $`; $m = $'; my $k = $1; my $x = $2;
                $m =~ /〓\/$k$x〓/ || last;
                $m = $';
                if( $k eq 'ins' ) { $n .= $`; $o .= '〓mark〓'; }
                else { $n .= '〓mark〓'; $o .= $`; }
            }
            $n .= $m; $o .= $m;
            # マークhtmlの削除挿入箇所を削除する。
                my $hg = quotemeta( '&lt;!-- graphic --&gt;' );
                $o =~ s/(<IMG\s[^>]*?src=")[^"]*("[^>]*>)((?:.|\s)*?)$hg/$1$3$2/g;
                $n =~ s/(<IMG(?=[\s>])[^>]*>)(?:.|\s)*?$hg/$1/g;
                my $hm = '〓mark〓';
                my @h = (
                    '<H[34](?=[\s>])[^>]*?>'.$hm.'</H[34]>',
                    '<A name=\d+></A>(\s|<P></P>)*'.$hm,
                    '<DIV(?=[\s>])[^>]*?>\(?'.$hm.'[\)\.]\s?</DIV>',
                    '<DIV(?=[\s>])[^>]*?></DIV>(\s|<P></P>)*'.$hm.'(<BR>|<P></P>|\s)*',
                    '<DIV(?=[\s>])[^>]*?>\s?('.$hm.'(<BR>|<P></P>|\s)*)+</DIV>(\s?<P></P>)?',
                );
                my $h = '(?:'.( join '|', @h ).')';
                my $b = '';
                while( $o ne $b ) { $b = $o; $o =~ s/$h/$hm/gi; $o =~ s/$hm($hm)+/$hm/g; }
                $o =~ s/$hm//g;
                $b = '';
                while( $n ne $b ) { $b = $n; $n =~ s/$h/$hm/gi; $n =~ s/$hm$hm+/$hm/g; }
                $n =~ s/$hm//g;
            # 改行等の相違を無視する。
                my $ss = join '', $n_htm;
                my $so = join '', $o_htm;
                for( $ss, $so, $n, $o ) {
                    s/(<P><\/P>\s*)+/<P><\/P>/g;
                    s/(<A name=)\d+(>)/$1$2/g;
                    s/(<DD>\**)\s?<P><\/P><DD>/$1/g;
                    s/<DD>\s?<P><\/P>(?=<A\s)/$1/g;
                    s/(<\/DIV>)(<BR>\s?)*/$1/g;
                    s/<DIV(?=[\s>])[^>]*?><\/DIV>//g;
                    s/<\/DIV>(?=<\/DIV>)/$&\x0a/g;
                    s/\x0a+/\x0a/g;
                }
            $n eq $ss || msg( '  出力HTMLの体裁が変わったところがあります。' );
            $o eq $so || msg( '  元のHTMLの体裁が変わったところがあります。' );
            # デバグ用出力あり ならば 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 ); }
    }

    @mh_mark;  # マークhtmlの マーク一覧 
    %m2a;  # マークhtmlの マークとアンカーの対応 
    $ch_htm;  # 比較html 
    @ch_diff, @ch_diff2;  # 比較htmlの ブロックごとの相違一覧 
    @ch_mark;  # 比較htmlの ブロックごとのマーク一覧 
    @ch_anc;  # 比較htmlの ブロックごとのアンカー一覧 

    %h_mark;  { # マークに対応するhtmlタグ
        # マークに対応するhtmlタグ
            my @c = ( '#6666ee', '#ee5566' );
            %h_mark = (
                'del'  => '<font color=white style="background-color:'.$c[0].'">',
                '/del' => '</font>',
                'ins'  => '<font color=white style="background-color:'.$c[1].'">',
                '/ins' => '</font>',
            );
    }
    @h_atr;  { # htmlの属性タグ
        # htmlの属性タグ
            @h_atr = (
                'b', 'u', 'i', 'sup', 'sub',
            );
    }

    { #+ リンク
        # マークhtmlのマーク一覧を作り マークの対応を補正する。
            my $fm = '〓(?:ins|del)\d+〓';
            @mh_mark = ( $m_htm =~ /($fm)/g );
            @mh_mark = map do { /(\d+)/g }, @mh_mark;
            # msg( '--- mh_mark ---', @mh_mark );
            my %mm = (); my $i = 0; for( @mh_mark ) { $mm{ $_ } = $i++; }
            @c2m = map do {
                [ map do { defined( $mm { $_ } ) ? $_ : () }, @$_ ];
            }, @c2m;
            # my $i = 0; msg( '--- c2m ---', map do { join ' ', $i++, ':', @$_ }, @c2m );
        # マークhtmlで マークとアンカーの対応を調べる。
            my $fa = '<a name=\w+></a>';
            my $fm = '〓(?:ins|del)\d+〓';
            @mh_ma = ( $m_htm =~ /($fa|$fm)/gi );
            @mh_ma = ( ( join '', @mh_ma ) =~ /($fa(?:$fm)*)/gi );
            # msg( '--- mh_ma ---', @mh_ma );
            %m2a = ();
            for( @mh_ma ) {
                /^$fa/i; my $d = $&; my $r = $';
                $d =~ s/^<a name=//i; $d =~ s/><\/a>$//i;
                my @d = ( $d, ( $r =~ /\d+/g ) );
                my $a = shift @d; for( @d ) { $m2a{ $_ } = $a; }
            }
            # msg_m2a();
        # 比較html を読む。
            $ch_htm = join '', getF( $fol.$ch_fn );
        # 比較htmlの ブロックごとの相違一覧を作る。
            @ch_diff = ();
            my $fa = '<a name=\d+></a>';
            my $ff = '<font color=white style="background-color:(?:#ee5566|#6666ee)">';
            my $ch = $ch_htm; my $s = 0; my $e = 0;
            while( $ch =~ /$fa/i ) {
                $ch = $'; my $r = ( $ch =~ /$fa/ ) ? $` : $ch ;
                my @r = ( $r =~ /($ff)/gi );
                $e = $s + @r - 1; push @ch_diff, [ $s .. $e ]; $s = $e + 1;
            }
            $s == $cd_no || msg( '  差分SGMLと比較HTMLの相違箇所数に違いがあります。' );
            # my $i = 0;
            # msg( '--- ch_diff  ---', map do { join ' ', $i++, ':', @$_ }, @ch_diff );
            # my $b = 18; msg( '--- ch_diff '.$b.' ---', join ' ', @{$ch_diff[$b]} );
        # ブロックごとの相違一覧から 削除された番号を消し 番号を変換する。
            my %cd = (); my $i = 0; for( @cs_diff ) { $cd{ $_ } = $i; $i++; }
            @ch_diff2 = map do {
                [ map do { defined( $cd { $_ } ) ? $cd { $_ } : () }, @$_ ];
            }, @ch_diff;
            # my $i = 0;
            # msg( '--- ch_diff2 ---', map do { join ' ', $i++, ':', @$_ }, @ch_diff2 );
            # my $b = 18; msg( '--- ch_diff2 '.$b.' ---', join ' ', @{$ch_diff2[$b]} );
        # ブロックごとのマーク一覧を作る。
            @ch_mark = map do {
                [ map do { @{$c2m[ $_ ]} }, @$_ ];
            }, @ch_diff2;
            # my $i = 0;
            # msg( '--- ch_mark ---', map do { join ' ', $i++, ':', @$_ }, @ch_mark );
            # my $b = 18; msg( '--- ch_mark '.$b.' ---', join ' ', @{$ch_mark[$b]} );
        # ブロックごとのアンカーの一覧を作る。
            @ch_anc = map do {
                my @anc = map do { $m2a{ $_ } }, @$_; uniqueA( \@anc );
                [ @anc ];
            }, @ch_mark;
            # my $i = 0;
            # msg( '--- ch_anc ---', map do { join ' ', $i++, ';', @$_ }, @ch_anc );
        # 比較htmlに アンカーへのリンクを追加する。
            my $fa = '<a name=\w+></a>';
            my $fl = '<input type=button value="%s" onclick="location.href=\''.
                     $hfn.'#%s\'">';
            my $ch = $ch_htm; $ch_htm = ''; my $a_no = 0;
            while( $ch =~ /$fa/i ) {
                $ch_htm .= $`.$&; $ch = $';
                my @anc = @{ $ch_anc[ $a_no++ ] };
                my $anc = join "\x0a", '', ( map do { sprintf $fl, $_, $_ }, @anc ), '';
                @anc && ( $ch_htm .= $anc );
            }
            $ch_htm .= $ch;
        # 新htmlと比較htmlを出力する。
            msg( '  update: '.$nh_fn ); putF( $fol.$nh_fn, $n_htm );
            msg( '  output: '.$ch_fn ); putF( $fol.$ch_fn, $ch_htm );
    }

    { #+ 結果出力
        # マークをhtmlタグに変える。
            my $o = $m_htm; $out = ''; my $ha = '(?:'.( join '|', @h_atr ).')';
            while( $o =~ /〓(ins|del)(\d+)〓((?:.|\s)*?)〓\/\1\2〓/ ) {
                $out .= $`; my $k = $1; my $r = $3; $o = $';
                $r =~ s/<FONT[\s>][^>]*?>//gi; $r =~ s/<\/FONT>//gi;
                $k eq 'del' && ( $r =~ s/<\/?$ha>//gi );
                $out .= $h_mark{ $k }.$r.$h_mark{ "/$k" };
            }
            $out .= $o;
            my $hg = quotemeta( '&lt;!-- graphic --&gt;' );
            $out =~ s/(<IMG(?=[\s>])[^>]*?>)(?:.|\s)*?$hg/$1/g;
            # デバグ用出力あり ならば msg( '  output: chk_out.htm' ); putF( 'chk_out.htm', $out );
                if( $opt =~ /d/i ) {  msg( '  output: chk_out.htm' ); putF( 'chk_out.htm', $out ); }
        # 結果を出力する。
            msg( '  output: '.$hfn );
            putF( $fol.$hfn, $out );
        # ! ( デバグ用出力あり ) ならば 作業フォルダを初期化する。
            if( ! ( $opt =~ /d/i )  ) { 
                my @n = ( $n_fn,  $o_fn,  $c_fn, $ch_fn, $m_fn, $nh_fn, $oh_fn, $mh_fn );
                for( @n ) { -f $wfol.$_ && unlink( $wfol.$_ ); }
                my @r = map do { -e $wfol.$_ ? $_ : () }, @n;
                @r && err( '  作業フォルダを初期化できません。' );
            }
        # ! ( デバグ用出力あり ) ならば 差分sgmlを削除する。
            if( ! ( $opt =~ /d/i )  ) { unlink( $fol.$c_fn ); }
    }

#  処理の詳細 ‥

# - 開始

# - 新・旧・差分sgml

# - マークsgml

    sub graphic_esc { # 画像タグのエスケープ 
        my $t = $_[0]; $t =~ s/^<//; $t =~ s/>$//;
        my $i = $t; my $d = $t;
        $i =~ s/<my:ins>(.*?)<\/my:ins>/$1/g;
        $i =~ s/<my:del>(.*?)<\/my:del>//g;
        $d =~ s/<my:del>(.*?)<\/my:del>/$1/g;
        $d =~ s/<my:ins>(.*?)<\/my:ins>//g;
        $d = ( $d =~ /gfname="([^"]*)"/ ) ? $1.'&lt;!-- graphic --&gt;' : '' ;
        "&lt;$i&gt;$d";
    }

    sub msg_cs_diff { # cd_diffの表示 
        my %cd = (); for( @cs_diff ) { $cd{ $_ } = 1; }
        msg( '--- cs_diff ---',
             '総数 '.$cd_no,
             '削除されたもの '.( join ' ',
                 map do { defined( $cd{ $_ } ) ? () : $_ }, ( 0 .. $cd_no - 1 )
             ),
        );
    }

# - 新・旧・マークhtml

# - リンク

    sub msg_m2a { # マークとアンカーの対応を表示する。 
        my @m = sort values %m2a; uniqueA( \@m );
        @m = map do {
            my $a = $_;
            my @m = map do { $m2a{ $_ } == $a ? $_ : () }, keys %m2a;
            [ $a, ':', sort @m ];
        }, @m;
        msg( '--- m2a ---', map do { join ' ', @$_ }, @m );
    }

=pod 
        @ch_diff : ブロックごとの相違の番号
        @cs_diff : 削除されていない相違の番号
        $cd_no   : 相違の総数
        @c2m     : 相違の番号に対応する マークの番号

        @c2m == @cs_diff
=cut

# - 出力

# - 補助の定型ルーチン

    sub uniqueA { # ( array )の重複を除去する 
        my $n = '';
        @{$_[0]} = map do { my $p = $n; $n = $_; $p eq $n ? () : $n; }, @{$_[0]};
    }

    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 = <IN>; 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" }, @_;
    }

# - 構文

# - ライセンス
# ~   スクリプトの冒頭に記述。

