XMLファイルを比較

概要

  • XMLファイルを比較し、要素/属性が一致する場合は「=:」、不一致の場合は行頭に「!」を付けて表示します。
  • 他方に項目が無かった場合は「~」が表示されます。
  • XMLの要素も属性も共に配列ではなくハッシュとして取り扱います。
  • 設定を外部ファイルから読み込むようにしました。(2012/05/20)
    • Settings.yml では、PARAM要素を、NAME属性をキー、VALUE属性を値とするハッシュに変換します。
  • 3つ以上のXMLファイルも比較できるようにしました。(2012/05/20)
  • 初版(2012/05/18)

ソース

compareXML.pl

すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
-
|
|
|
!
 
 
 
 
 
 
 
 
 
-
|
!
 
-
|
|
|
!
 
 
 
 
 
-
-
|
|
-
|
!
!
 
 
 
 
 
 
 
 
 
 
-
|
|
|
|
|
|
-
-
-
|
!
!
!
-
|
|
|
-
-
|
|
-
|
!
-
|
!
!
-
|
|
-
|
-
|
|
|
|
|
-
|
-
|
|
|
|
!
!
!
!
!
 
 
 
 
 
 
-
|
|
|
|
-
-
|
|
!
!
|
!
 
 
# XMLファイルを比較
 
use strict;
use warnings;
use utf8;
use Encode;
use XML::Simple;
use YAML::Syck;
use Getopt::Long;
 
$YAML::Syck::ImplicitUnicode = 1;
 
my $charsetConsole    = 'CP932';
my $charsetFile        = 'UTF-8';
 
binmode( STDIN,  ":encoding($charsetConsole)" );
binmode( STDOUT, ":encoding($charsetConsole)" );
binmode( STDERR, ":encoding($charsetConsole)" );
 
my $configFile = "";
my $config = { 
    'Output' => "diff.txt",
    'Indent' => "\t",
    'XMLOpt' => {}, 
};
my @FileNames = ();
my $root = {};
 
@ARGV = map{ decode( $charsetConsole, $_ ); } @ARGV;
 
my $result = GetOptions(
    "config=s"    => \$configFile,
);
 
if ( @ARGV < 2 ){
    die( "usage: compareXML [-config <yml>] <xml> <xml> [<xml> ...]\n" );
}
 
if ( $configFile ){
    my $config2 = YAML::Syck::LoadFile( encode( $charsetConsole, $configFile ) )
        or die( "$configFile: $!\n" );
    $config = { %{$config}, %{$config2} };
}
my $fileOut = $config->{'Output'};
my $IndentBase = $config->{'Indent'};
 
my $xs = XML::Simple->new( %{$config->{'XMLOpt'}} );
 
foreach my $file ( @ARGV ){
    if ( -f encode( $charsetConsole, $file ) ){
        push( @FileNames, $file );
        $root->{$file} = $xs->XMLin( encode( $charsetConsole, $file ) );
    } else {
        die( "Not exist: $file\n" );
    }
}
 
open( my $fhout, ">:encoding($charsetFile)", encode( $charsetConsole, $fileOut ) )
    or die( "$fileOut: $!" );
 
#print $fhout Dump( $xml );
compareNodes( $root, 0 );
 
close( $fhout );
 
sub compareNodes
{
    my( $href, $indent ) = @_;
    my $IndentText1 = $IndentBase x $indent;
    my $IndentText2 = $IndentBase x ( $indent + 1 );
 
    # すべてのキーを列挙
    my %currentkeys = ();
    foreach my $fn ( @FileNames ){
        if ( ref( $href->{$fn} ) eq 'HASH' ){
            foreach my $key ( keys( %{$href->{$fn}} ) ){
                $currentkeys{$key} = 1;
            }
        }
    }
    foreach my $key ( sort( keys( %currentkeys ) ) ){
        printf $fhout ( "%s%s:\n", $IndentText1, $key );
        my $bHash = 0;    # どちらかがハッシュなら1
        my $child = {};
        foreach my $fn ( @FileNames ){
            if ( ref( $href->{$fn} ) eq 'HASH' ){
                my $t1 = $href->{$fn}{$key} // '~';    # undef は '~'
                $child->{$fn} = $t1;
                if ( ref( $t1 ) eq 'HASH' ){
                    $bHash = 1;
                }
            } else {
                $child->{$fn} = '~';
            }
        }
        if ( $bHash ){
            # どちらかがハッシュの場合は再帰探索
            compareNodes( $child, $indent + 1 );
        } else {
            # 両方共ハッシュでなければ値の表示
            if ( eqAll( $child ) ){
                # 一致する場合は「=:」でまとめて表示
                printf $fhout ( 
                    "%s=:%s%s\n", 
                    $IndentText2, $IndentBase, $child->{$FileNames[0]} 
                );
            } else {
                # 不一致の場合は行頭に「!」を付けて表示
                foreach my $fn ( @FileNames ){
                    printf $fhout ( 
                        "!%s%s:%s%s\n", 
                        $IndentText2, $fn, $IndentBase, $child->{$fn} 
                    );
                }
            }
        }
    }
}
 
##	@function eqAll( %$refHash )
#	ハッシュの全ての値を比較する。
#	@retval	一致	1
#	@retval	不一致	0
sub eqAll
{
    my( $refHash ) = @_;
    my $bEqual = 1;
    my @Keys = keys( %{$refHash} );
    my $first = $refHash->{ shift( @Keys ) };
    foreach my $key ( @Keys ){
        if ( $first ne $refHash->{ $key } ){
            $bEqual = 0;
            last;
        }
    }
    return $bEqual;
}
 
# EOF

入力

Settings.yml

Output: diff.txt
Indent: "\t"

XMLOpt:
  ForceArray:
    - PARAM
  KeyAttr:
    PARAM:     NAME
    OPERATION: NAME
  ContentKey:
    "-VALUE"

Item1.xml

<SETTINGS NAME="Item1" VERSION="8" >
  <DOCUMENT DOCUMENT_ID="79" NAME="Doc02.indd" SRC="//localhost/Data/2/2/" TYPE="INDD" />
  <OUTPUT>
    <PARAM NAME="START_RECORD" VALUE="1" />
    <PARAM NAME="END_RECORD" VALUE="100" />
    <PARAM NAME="TYPE" VALUE="PDF" />
    <PARAM NAME="CENTER_PAGE" VALUE="0" />
  </OUTPUT>
  <ASSET_LIST Resolve="1">
    <ASSET ID="73" NAME="Assets1" PRIORITY="1" TYPE="LOCAL" >
      <PARAM NAME="BaseID" VALUE="1" />
    </ASSET>
  </ASSET_LIST>
  <DATA_SOURCES>
    <DATA_SOURCE ID="13" >
      <PARAM NAME="FILENAME" VALUE="db1.csv" />
      <PARAM NAME="TYPE" VALUE="TEXT" />
   </DATA_SOURCE>
  </DATA_SOURCES>
  <FONT_LIST>
    <FONT ID="20" NAME="Arial" OPTION="OpenType" />
  </FONT_LIST>
  <POST_PRODUCTION />
</SETTINGS>

Item2.xml

<SETTINGS NAME="Item2" VERSION="8" >
  <DOCUMENT DOCUMENT_ID="78" NAME="Doc01.indd" SRC="//localhost/Data/2/1/" TYPE="INDD" />
  <ASSET_LIST Resolve="1">
    <ASSET ID="73" NAME="Assets1" PRIORITY="1" TYPE="LOCAL" >
      <PARAM NAME="BaseID" VALUE="1" />
    </ASSET>
  </ASSET_LIST>
  <JOB ID="330" TYPE="PRINT">
    <PARAM NAME="HOST_NAME" VALUE="localhost" />
    <JOB_CONTEXT JobID="330" JobName="Doc01" JobType="1" />
  </JOB>
  <OUTPUT MEDIA="1" OUTPUT_FILE_NAME="Doc01" TYPE="PDF">
    <PARAM NAME="TYPE" VALUE="PDF" />
    <PARAM NAME="START_RECORD" VALUE="80" />
    <PARAM NAME="END_RECORD" VALUE="100" />
    <PARAM NAME="FONTS_POLICY" VALUE="1" />
    <PARAM NAME="OVERFLOW_POLICY" VALUE="0" />
  </OUTPUT>
  <FONT_LIST>
    <FONT ID="20" NAME="Arial" />
  </FONT_LIST>
  <POST_PRODUCTION JOBID="330">
    <OPERATION NAME="IMPOSITION">
      <PARAM NAME="NUPX" VALUE="1" />
      <PARAM NAME="NUPY" VALUE="2" />
    </OPERATION>
    <OPERATION NAME="DISTILLER">
      <PARAM NAME="DSTL_SETTINGS" VALUE="HighQuality" />
    </OPERATION>
    <OPERATION NAME="COPY">
      <PARAM NAME="DEST_PATH" VALUE="//localhost/Output/2/1/" />
    </OPERATION>
  </POST_PRODUCTION>
  <TRACK_INFO />
</SETTINGS>

出力

ASSET_LIST:
	ASSET:
		ID:
			=:	73
		NAME:
			=:	Assets1
		PARAM:
			BaseID:
				=:	1
		PRIORITY:
			=:	1
		TYPE:
			=:	LOCAL
	Resolve:
		=:	1
DATA_SOURCES:
	DATA_SOURCE:
		ID:
!			Item1.xml:	13
!			Item2.xml:	~
		PARAM:
			FILENAME:
!				Item1.xml:	db1.csv
!				Item2.xml:	~
			TYPE:
!				Item1.xml:	TEXT
!				Item2.xml:	~
DOCUMENT:
	DOCUMENT_ID:
!		Item1.xml:	79
!		Item2.xml:	78
	NAME:
!		Item1.xml:	Doc02.indd
!		Item2.xml:	Doc01.indd
	SRC:
!		Item1.xml:	//localhost/Data/2/2/
!		Item2.xml:	//localhost/Data/2/1/
	TYPE:
		=:	INDD
FONT_LIST:
	FONT:
		ID:
			=:	20
		NAME:
			=:	Arial
		OPTION:
!			Item1.xml:	OpenType
!			Item2.xml:	~
JOB:
	ID:
!		Item1.xml:	~
!		Item2.xml:	330
	JOB_CONTEXT:
		JobID:
!			Item1.xml:	~
!			Item2.xml:	330
		JobName:
!			Item1.xml:	~
!			Item2.xml:	Doc01
		JobType:
!			Item1.xml:	~
!			Item2.xml:	1
	PARAM:
		HOST_NAME:
!			Item1.xml:	~
!			Item2.xml:	localhost
	TYPE:
!		Item1.xml:	~
!		Item2.xml:	PRINT
NAME:
!	Item1.xml:	Item1
!	Item2.xml:	Item2
OUTPUT:
	MEDIA:
!		Item1.xml:	~
!		Item2.xml:	1
	OUTPUT_FILE_NAME:
!		Item1.xml:	~
!		Item2.xml:	Doc01
	PARAM:
		CENTER_PAGE:
!			Item1.xml:	0
!			Item2.xml:	~
		END_RECORD:
			=:	100
		FONTS_POLICY:
!			Item1.xml:	~
!			Item2.xml:	1
		OVERFLOW_POLICY:
!			Item1.xml:	~
!			Item2.xml:	0
		START_RECORD:
!			Item1.xml:	1
!			Item2.xml:	80
		TYPE:
			=:	PDF
	TYPE:
!		Item1.xml:	~
!		Item2.xml:	PDF
POST_PRODUCTION:
	JOBID:
!		Item1.xml:	~
!		Item2.xml:	330
	OPERATION:
		COPY:
			PARAM:
				DEST_PATH:
!					Item1.xml:	~
!					Item2.xml:	//localhost/Output/2/1/
		DISTILLER:
			PARAM:
				DSTL_SETTINGS:
!					Item1.xml:	~
!					Item2.xml:	HighQuality
		IMPOSITION:
			PARAM:
				NUPX:
!					Item1.xml:	~
!					Item2.xml:	1
				NUPY:
!					Item1.xml:	~
!					Item2.xml:	2
TRACK_INFO:
VERSION:
	=:	8

添付ファイル: filecompareXML.zip 306件 [詳細]

リロード   新規 下位ページ作成 編集 凍結 差分 添付 コピー 名前変更   ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: Mon, 21 May 2012 02:34:29 JST (2167d)