前のブログで作ったものの複数対応版です。同時に出力XMLの文字コードをUTF-8に変更。Excelファイル内に全角チルダがあるとShift JIS保存できないことが判明したので。Windows 7 32bit + Excel 2007 + Python 2.7で動作確認しました。
Contents


ファイル構成、ソース、XMLの形式
前のブログで作った時と同じで、Pythonスクリプトとバッチファイル各一つ。またPythonは、QGIS 2.6.1のポータブル版に入っている2.7をそのまま使います。最初からwin32comモジュールが同梱されていたので。それ以外のPython 2.7でも、パス・環境変数を適宜設定しwin32comを入れて、Excel 2007PCにインストールされていれば同様に動くと思います。QGIS 2.6.1ポータブル版のダウンロード先は下記。

»
Qgis - Download SW e progetti prototipali

QGIS 2.6.1ポータブル版の解凍先の一番上にバッチファイルexcel2xml.py.multi.batを作成。また同じ場所に「python_script」というフォルダを作り、その中にスクリプトexcel2xml_rev.pyを入れました。2ファイルの場所に深い意味はなく、何となく使いやすそうな所にしただけ。QGIS 2.6.1ポータブル版のフォルダはこんな感じ ↓ です。(今回と同様、自分が追加したファイル・フォルダがいくつかある)


↓ バッチファイルのソース。前は引数一つだけの想定で、それをPythonスクリプトに渡してました。今回は引数の数だけループして、同じ処理をします。
excel2xml.py.multi.batSelectRawtextBitbucket
@echo off
%~d0
cd %~p0
cd QGIS/bin
set PYTHONHOME=../apps/Python27
set PYTHONPATH=../apps/Python27/Lib
set PATH=%PYTHONHOME%
setlocal enableextensions

:repeat
set /a count=%count%+1
python "../../python_script/excel2xml_rev.py" "%1"
if "%~2"=="" goto end
shift
goto repeat
:end
endlocal

pause


Pythonスクリプトのソース。こちらは前は
前のブログで作った時とほとんど同じ。違うのは最後の方、XML出力の文字コードをUTF-8に変えたのと、処理終了後のXMLの画面出力をやめたこと。
excel2xml_rev.pySelectRawtextBitbucket
import os
import sys
import win32com.client
import codecs

fln = sys.argv[1].replace(os.path.sep, '/')
xml = fln + '.xml'

eao = win32com.client.Dispatch('Excel.Application')
eao.Visible = False
eao.DisplayAlerts = False

dic = []
wbo = eao.Workbooks.Open(fln)
for i in range(0, wbo.WorkSheets.Count) :
    wso = wbo.WorkSheets(i + 1)
    usr = wso.UsedRange
    tags = []
    for j in wso.Comments :
        t = j.Text()
        if not t : continue
        tag = ['<comment r="', str(j.Parent.Row), '"']
        tag.extend([' c="', str(j.Parent.Column), '">'])
        tag.extend([t, '</comment>'])
        tags.append(''.s.join(tag))
    tmp = {'wsname' : wso.Name}
    tmp['r_top'] = usr.Row
    tmp['c_top'] = usr.Column
    tmp['data'] = usr.Value
    if tmp['data'] is None : continue
    if len(tags) > 0 : tmp['comm'] = crf.join(tags)
    dic.append(tmp)
    if i == wbo.WorkSheets.Count : break
wbo.Close()
eao.Quit()

tags = [unicode('<b path="' + fln + '">')]
for w in dic :
    tags.append(unicode('<s name="' + w['wsname'] + '">'))
    if 'comm' in w : tags.append(w['comm'])
    nrow = w['r_top']
    for r in w['data'] :
        ncol = w['c_top']
        for c in r :
            if c is not None :
                tag = ['<w r="', str(nrow), '"']
                tag.extend([' c="', str(ncol), '">'])
                tag.extend([unicode(c), '</w>'])
                tags.append(''.join(tag))
            ncol = ncol + 1
        nrow = nrow + 1
    tags.append('</s>')
tags.append('</b>')
output = '\n'.join(tags)

f = codecs.open(xml, 'w', 'utf8') # change from 'sjis'
f.write(output)
f.close


出力されるXMLの形式は
前のブログで作った時と同じで、入力されている全セルの「行番号、列番号、データ」が<w r="行番号" c="列番号">データ</w>という単純な要素の集合になります。これをシートごと、さらにブック(ファイル)ごとにツリー化するのが基本。あとコメントは別のツリーになります。

このXMLから必要なデータを抽出したり元の表構造を再現するには、セルの位置だけが頼りで、場合によっては手間がかかりますが、Excelファイルがどんな複雑な構造でも「データと位置関係だけは押さえられる」のが利点。行・列の項目が何層にもなっていたり、複数の表が一つのシートに混在するなど頭の痛いExcelファイルとつき合うため、試行錯後の結果たどりついた方法です。


実行例(動画)
動画形式はWebMです。ダウンロードは動画下のリンクから、プレーヤについてはこちら。動画では最初に一つのExcelファイルを開いて中身を確認し、次に10ファイルまとめてバッチにドラッグ&ドロップ。自動的にXMLに変換され、Excelファイルと同じ場所に出力されます。
≫ Link : demo_excel2xml.webm


実行例(説明)
今回使ったExcelファイルは、下記から20052014年の各ページに行って「市町村別年齢別人口」をダウンロードしたもの。年によっては表記や中身が若干違いますが、今日の本題ではないので省略。

»
政府統計の総合窓口:住民基本台帳に基づく人口、人口動態及び世帯数調査 > 調査の結果 > 年次

↓ ファイルはこんな感じで日本語がいっぱい。また年齢階級を表すのに全角チルダ「~」が使われてます。これがあるとPythonではShift JISファイルに出力できないらしいので(Python3でも同じ現象)スクリプトを改修することにし、ついでに複数ファイルに対応させたというのが今日のきっかけ。


Excelファイルは「日本語や半角空白を含まないパス」に置きます。まだそこまで対応してないので。ファイルをまとめて選択しバッチにドラッグ&ドロップすると、複数の引数で各ファイルが渡されたことになり、一つずつPythonスクリプトで処理されます。


普通はコマンドプロンプト(DOS窓、黒い画面)でバッチが実行されると思います。下の画像はDOS窓の替わりにConEmuになっていますが、終了まで何も出ない点は同じ。裏でPythonwin32comモジュールがExcelを非表示で起動し、全セルを読み取り。一ファイルの処理終了ごとに、新しいXMLが出力されます。同じファイル名のXMLが既存なら上書き。


バッチの最後にPAUSEコマンドがあるので、終わると ↓ このメッセージになります。


元のExcelファイルが概ね1.4
MBで、結果のXML46MBと結構大きいサイズ。エディタで開く分には余裕ですが、ブラウザだと意外に厳しい。Chrome43では辛うじて表示されたものの ↓ すごく時間がかかり、Firefox39では余りに時間かかるので途中終了しました。

画像の下の方を見ると、数値がxxxxx.0となってます。元は整数だったのが(人口なので当然)Pythonで処理する過程で実数型か何かに変わる模様。詳しく言うと、ExcelUsedRangeオブジェクトを使ってシート内の全データを一括取得してPythonのオブジェクトにする際、数値の型が自動的に決まってしまうような。回避策がないか以前も調べましたが、まだ見つかっていません。


制約・注意点
記事の中に書いた点も含め、まとめておきます。

こんな感じで一般的な手段ではないと思うけど、自分としてはPostgreSQLXML処理と組み合わせて便利に使ってます。今日のきっかけになった「全角チルダ入りのExcelファイル」は、実際ある用件でデータ処理が必要になって遭遇したもの。可能なら、この後の作業をXML処理の一例として書くかも。