人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

大きなXMLファイルを整合性を保ったまま自動分割する方法

batやvbsなど、プログラムを書いてくれる方を探しています。
謝礼は100?500pt、完全解答をお願いします。

たとえば、電話帳を記載したxmlファイルが数種類あります。このファイルを別のプログラムで処理するには重すぎるとします。
サイズだけを基準にぶった切ると処理をするときにxmlの整合性が崩れるので処理ができないという問題が出ます。そこで、整合性を維持したままxmlファイルを分割したいです。batなどのファイルで。

条件
1. 毎回処理する対象のXMLの形式はおおまかに同じ
2. XML は、次のようになっている。

addressbooktokyo.xmlの中身
<xml version="1.0" encoding="utf-8"?>
<任意の一行、定型句>
<header>
<各種ヘッダー情報、たとえば地域などファイルによって微妙に違う>
</header>
<body>
<person>
<name>山田さん</name>
<phone>0123456789</phone>
</person>
膨大な繰り返し・・・
</body>
</xml>

補足に手順イメージを記載しました。


●質問者: にぎたま
●カテゴリ:コンピュータ
○ 状態 :終了
└ 回答数 : 1/1件

▽最新の回答へ

質問者から

手順イメージ
1. ファイルの初回読み込み時に<xml宣言>プラス<header>から</header>を読み込んで一時的にどこかにキープする(これがヘッダ部分)。
2. <body>内を読み込みながら、output1.xmlファイルに各行を書き出す。
3. 繰り返しが1000行を超えて「かつ」</person>の行を読み込んだときに、</body></xml>を付け足してそのファイルは終了、が、繰り返しは継続
4. 繰り返しを継続するが、出力先はoutput2.xmlにインクリメントし、初回のみ、手順1のヘッダ部分を付け加えて、以下bodyを繰り返す

という流れにできないか、と考えています。
ファイル末尾に至るときにどう処理するかはちょっとした工夫が要るかもしれません。


1 ● Mook
●500ポイント ベストアンサー

FileSystemObject でサポートしているのは S-JIS か unicode なので、
ちょっと処理が複雑なのと、処理が遅いかもしれませんが ADODB.stream で
UTF-8 として処理しています。

また、できればきちんと XML として構文処理したほうがよいと思いますが、
例示されたサンプルは MSXML2.DOMDocument で読み込めなかったので、
文字列処理としています。

Option Explicit

'//-----------------------------------
 Const FILE_PATH = "D:\Data\Data.xml"  '// 処理ファイル名
 Const DIV_SIZE = 1000  '// 1ファイルの分割行数

'//-----------------------------------
 Dim inXML
 Set inXML = CreateObject("ADODB.Stream")
 inXML.Type = 2  '// 1:バイナリデータ 2:テキストデータ
 inXML.Charset = "UTF-8"
 inXML.Open
 inXML.LoadFromFile FILE_PATH

'// header(body まで) 読込
'//-----------------------------------
 Dim headerCount
 Dim headerPart
 Dim readOneLine 
 Do While inXML.EOS = False
 readOneLine = inXML.ReadText(-2)
 headerPart= headerPart & readOneLine & vbNewLine
 headerCount = headerCount + 1
 If InStr( UCase( readOneLine ), "<BODY>" ) > 0 Then Exit Do
 Loop

'//-----------------------------------
 Dim outXML
 Set outXML = CreateObject("ADODB.Stream")
 outXML.Type = 2  '// 1:バイナリデータ 2:テキストデータ
 outXML.Charset = "UTF-8"

'// person データ読込
'//-----------------------------------
 Dim divFileCount 
 Dim fileCount
 Dim dstXML
 Dim exportFile
 exportFile = ""
 Dim outContents
 Do While inXML.EOS = False
'// 初期化処理
 If exportFile = "" Then
 fileCount = fileCount + 1
 exportFile = "outFile" & fileCount & ".xml"
 outContents = HeaderPart
 divFileCount = headerCount
 End If

 readOneLine = inXML.ReadText(-2)
 outContents = outContents & readOneLine & vbNewLine
 divFileCount = divFileCount + 1

'// ファイル出力
 If divFileCount > DIV_SIZE Then
 If InStr( UCase( readOneLine ), "</PERSON>" ) > 0 Then
 outContents = outContents & "</body>" & vbNewLine
 outContents = outContents & "</xml>" & vbNewLine
 outXML.Open
 outXML.WriteText outContents, 1
 outXML.SaveToFile exportFile, 2
 outXML.Close
 exportFile = ""
 End If
 End If
 Loop

'// 最後のファイル出力
 If divFileCount <> headerCount Then
 outXML.Open
 outXML.WriteText outContents, 1
 outXML.SaveToFile exportFile, 2
 outXML.Close
 End If
 inXML.Close

http://trwtnb.blogspot.jp/2009/10/vbscriptutf-8.html
http://www.atmarkit.co.jp/fxml/rensai/msxml01/msxml03.html


にぎたまさんのコメント
ありがとうございます。 これは、.vbsにして保存すればよいのですよね? 原因がわからないのですが、 readOneLine = inXML.ReadText(-2) この部分で、どうやらファイルすべてを読み込もうとしているようで、うまく動作しません。エクセルのVisual Basicに貼り付けてウォッチしてみたところ、XMLファイルの末尾まで格納されていました。あまりプログラムに明るくないのですが、<BODY>が登場するまで繰り返す命令ですよね? あと、VBAに貼り付けただけなので実際にエラーかどうかわかりませんが、VBAに貼り付けたところ最後の方の処理 outXML.SaveToFile exportFile, 2 ここで、アプリケーションの定義がされていない、とかでエラーになりました。 これはおそらく、 exportFile = "" ここを exportFile = "D:\Data\hoge.tmx" など、任意の出力先を指定すればよい、ということでしょうか。 ここに出力先を指定したところ、先ほどのエラーは表示されなくなりました。 おそらく問題は readOneLine にあると思います。ここで一気に最後まで読み込んでいるようです。 お手数ですが、一緒にトラブルシューティングまでおつきあい願えますでしょうか?

Mookさんのコメント
ファイルは VBS にして実行ください。 小さいファイルでですが、こちらでは実際に動作確認しているので 動かないのは何か問題があるのだと思いますが、対象のファイルの ファイルフォーマットは UTF-8 でしょうか。 文字コードが異なる場合、文字列が一致しませんので何も動かない ように見えます。 メモ帳で開いて、名前を付けて保存とすると下のリストボックスに 文字コードが出ていますので、確認ください。 そこが UNICODE 等になっていた場合は一度 UTF-8 で保存し直して どうでしょうか。 オブジェクトの挙動としては、Load 時に全部を読み込んでいるかも しれませんが、メモリさえ足りれば動くかと思います。 もし文字コードが別のもの(Shift-JIS か UNICODE)であれば FileSystemObject が使用できるので、もう少しパフォーマンスはよくなる とは思いますが。

Mookさんのコメント
それからもう一つ可能性がありました。 一つのタグの後ろに改行があるでしょうか。 IE では階層に分かれて見えていても、実際にはXML内に改行がない場合もあります(XML 内では改行は意味がないので。その場合メモ帳などで開けば全部が一行につながって見えていると思います)。 その場合は今回のスクリプトでは処理できないですね。 タグの後ろに改行コードを入れるか、最初に書いたようにDOMなどで構造を使用して解析する必要があります(↓こういうのですね)。 http://ash.jp/xml/wsh/index.htm このあたりは文字列で解析する弱さですね。 なので、まずはデータの文字コード、文字書式を確認いただければと思います。

にぎたまさんのコメント
丁寧にありがとうございます。 まず、文字コードはUTF-8でした。 テキストエディタ(TeraPadと秀丸)を使って確認しましたが、改行も入っています。 VBAに貼り付けたところ、次のような流れで動いています。 '// header(body まで) 読込のループの挙動 1回目のループで readOneLine に <body> が含まれているため、ループを抜けます。 outXML は正常に作成できていると思います。 Do While inXML.EOS = False のループにさしかかったときにすぐ、 ループを実行せずに一気に '// 最後のファイル出力 まで飛んでしまいます。 exportFileは""のまま、ファイル名が与えられていません。 exportFIleが""のままなので、ここでエラーが出てしまいます。 (vbaに貼り付けても、vbsをダブルクリックしてもここでエラーです) 2個所目のループ Do While inXML.EOS = False このループに1度も入っていないことになります。 しかし、最初のループで inXML.EOS = False ではなくなってループを外れたので、2つめのループで同じDo While inXML.EOS = Falseを指定しても、ループに入らないのは当然、なのでしょうか。 用意したファイルは、300行程度で、DIV_SIZEを100にしています。

にぎたまさんのコメント
訂正 1回目のループで readOneLine に <body> が含まれているため、ループを抜けます。 ↓ 1回目のループで、ループ内容を1回だけ実行して、2回目にさしかかったときにDo While inXML.EOS = Falseの条件ではじかれてループを抜けているように見えます。

Mookさんのコメント
推測ですが、改行が CR(0x0D) がなく LF(0x0A) だけなのではないでしょうか(Windows では CR+LF が標準ですが、稀にLFだけのものが見受けられます)。 エディタでは改行があるように見えますが、メモ帳などでは一行になってしまう場合がそのようなケースです。 この場合、ReadOneLine で全部が読み込まれてしまいますので、 何も出力されないで終わってしまいます。 初期化処理内の readOneLine = inXML.ReadText(-2) の後ろに WScript.Echo readOneLine として確認してみて、一行ずつ表示されずに全部が表示されてしまった場合は そのケースです。 もしこれが一行ずつ表示されるようでしたら、再度コメントいただけるでしょうか。 exportFile は "" の状態でループに入ると、最初にファイル名を生成しますので、Loop を通らないと生成されません。

にぎたまさんのコメント
ありがとうございます! どういうわけか、改行がLFになっていました。もともとCR+LFのファイルのはずなんですが・・・・ 改行を元に戻したところきちんと一行ずつ読み込むようになりました。 まだ実験中ですが、100MBを超えるファイルでもきれいに分割していってくれています!自動化すごいですね!

Mookさんのコメント
無事動いたようで何よりです。 なぜ改行コードが変わってしまったかは確認できると良いとは思いますが、 実運用のファイルが処理できる形式で作成されるのであれば、一応は問題はなさそうでしょうか。

にぎたまさんのコメント
はい。とりあえず動作の確認は取れました。 今、2.3GBのファイルでテストをしていますが・・・数時間無反応ですね(笑) どのくらいのサイズまで分割すれば実用的になるのかはまだテスト中です。 ちなみにファイルをUnicodeに変換したところ、後工程のプロセスで問題なく処理ができました。 Unicode対応でできれば、処理が早くなるのでしょうか?(Unicode = UTF-16 と同じですよね?) ただ、エンコーディングをUTF-8からUTF-16にしたら、ファイルサイズが倍に跳ね上がりましたが・・・

Mookさんのコメント
上記のコメントで書いた UNICODE にすれば・・・、という話は単にデータの文字コードを変えるだけでなく、処理を FSO を使うように変更すればという意味で書きました。 ただそれでも2.3GBというファイルを処理するというのは、想像していませんでしたが。 この規模のファイル処理を頻繁にするのであれば、スクリプト処理ではちょっと限界がありそうです。 データの運用フロー(入力から出力まで)が不明ですが、その規模のファイル(データ)を処理するのであれば、XML ではなくODBS を使いたい気がしました。

にぎたまさんのコメント
600GBのファイルはなんとか処理できているのが確認できました。 さすがにこの規模のファイルを扱うことは多くないので、許容範囲かと思います。現状、2.3GBのファイルは機械的に分割して、手動でヘッダとフッタをつけて処理しました。 はてなで別の質問をするかもしれませんが、最初に機械的な分割をして、連番の前と後ろのファイルを参照しながらxmlの整合性をとっていく方法が処理スピードが早いかもしれません。 今回の質問はこれにて終了しようと思います。ぶっちぎりのベストアンサーですありがとうございました。
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ