注目キーワード

PythonでExcelファイルを操作する!(1) /実はzip圧縮ファイルだって知ってました?

Python でExcelファイルをいじってみたらzip圧縮ファイルだった!

 オフィス文書の解析処理に詳しい方(パーサーなどの開発に携わっている方など)であれば、すでにご存じかもしれませんが、xlsxファイルって zipファイルなんですね。
(xlsxだけでなく、doxc,pptxファイルも実体はzipファイルです。zipファイルとして展開すると、xmlファイル等が飛び出してきます)

 しかも、展開するとxmlファイルが飛び出してくるので、これは解析できそう!ということと、zipファイルを扱うPythonモジュールの訓練の対象として良さげではないかと思い立ち、筆をとりました。

このエントリを読むと得られるであろうもの

 おそらく、主観ですがこのエントリを読むと次のものが得られるのではないかと思います。
 Pythonを学び始めたばかりの方、ちょうど学習中の方にとっては、程よい経験値が得られるのではないでしょうか。

  • Python でzipファイルを扱う方法の基本が学べる
  • Python でxmlを扱う方法の基本が学べる
  • xlsx ファイルの構造・仕様について入門的理解を得られる

実験環境についての説明

  • Python3.7.3 を使用
  • Windows10 上で稼働するJupyter Notebookで実験
  • 使用したサンプルのxlsxファイル
 C:\develop\jupyter\learning\parse_xlsx のディレクトリ

2020/01/29  02:04    <DIR>          .
2020/01/29  02:04    <DIR>          ..
2020/01/28  23:45    <DIR>          .ipynb_checkpoints
2020/01/29  01:54            12,309 6340E930
2020/01/29  02:02            12,311 6847E930
2020/01/29  01:55            12,301 A6F0E930
2020/01/29  02:03            45,355 parse_xlsx.ipynb
2020/01/29  02:04            12,439 sample.xlsx # <--- これがサンプル xlsx
               5 個のファイル              94,715 バイト
               3 個のディレクトリ  372,735,033,344 バイトの空き領域

今回使用するPythonモジュールを読み込む

 今回、必要とするのは zipファイルを扱うモジュールと、xml を扱うモジュールです。
 Jupyter Notebook上の処理の流れに沿って、コードブロックを記述していきたいと思います。
 まずは、以下のモジュール読み込み処理を実施します。
 Pythonの標準的なzipモジュールとxmlモジュールです。
 xml の解析の際には、xml.etree.ElementTreeを使うと、おおよその作業はできるのではないかと思います。

from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
import xml.etree.ElementTree as ET

xlsxファイルをzipファイルとして読み込んでみる

 それでは sample.xlsx を zipファイルとして読み込んでみましょう。(このsample.xlsxは、あなたご自身で作られたものでも大丈夫です!)

 zipファイルを読み込む際のポイントは次の通りです。

  • zip ファイルとして読み込むには、xlsxファイルのパスを引数として ZipFile オブジェクトを作成する。
  • zip ファイルのアーカイブを構成するファイルの情報を取得するには、infolist() メソッドを呼び出す。

 それでは以下のコードを実行してみましょう!

archive = ZipFile('./sample.xlsx')
for _i in archive.infolist():
  print('zip info: {}'.format(_i.filename))

すると、次のような出力が得られると思います。

zip info: [Content_Types].xml
zip info: _rels/.rels
zip info: xl/workbook.xml
zip info: xl/_rels/workbook.xml.rels
zip info: xl/worksheets/sheet1.xml
zip info: xl/worksheets/sheet2.xml
zip info: xl/worksheets/sheet3.xml
zip info: xl/theme/theme1.xml
zip info: xl/styles.xml
zip info: xl/sharedStrings.xml
zip info: xl/worksheets/_rels/sheet1.xml.rels
zip info: xl/printerSettings/printerSettings1.bin
zip info: docProps/core.xml
zip info: docProps/app.xml

xml ファイルがたくさんありますね。
このようにxlsxファイルをzipファイルとして解凍すると、xl というフォルダの中に、ワークシートと思しきxmlファイルがあることが分かります。

zipファイルのアーカイブからxmlファイルを読み込んでみよう

 試しにいくつかのxmlファイルを zip ファイルのアーカイブ(以後、zipアーカイブと短縮して呼びます)から読み込んでみましょう!
(zipファイルのアーカイブとは、簡単に言うと、zipファイルを展開すると得られるファイル群のことを意味します)

 例えば、xl/workbook.xml を読み込むとしましょう。
 zipアーカイブの中からファイルを読み込む際のポイントは次の通りです。

  • zipアーカイブからファイルを読み込むには、読み込みたいファイルのパスを指定して、 open メソッドを使う。
    • 最初の引数には、infolist() で取得したアーカイブ構成ファイルの中に含まれるファイルのパスを指定する。
    • ここでは xl/workbook.xml を指定。
    • 2番目の引数は読み込みモードである。ここでは読み込むだけなので ‘r’ を指定する。
  • すると、改行無しのゴチャっとしたXMLが取得できる。
    • 取得したXMLはstr型ではなくbyte型なので注意!
      • decode('utf-8') メソッドを使って、UTF-8str型にしておこう!

 では、上記のポイントを踏まえて、次のコードを実行してみましょう!

filename = 'xl/workbook.xml'

# zip アーカイブの中から `xl/workbook.xml` をオープンする。
fp = archive.open(filename, 'r')
body = fp.read()

# body の中身は byte型 なので注意!
print(type(body))

# decode('utf-8') で UTF-8 の文字列に変換しておこう!
body = body.decode('utf-8')
print(body)

すると、次のような結果が得られます。

<class 'bytes'> # <-- bytes型ということが分かる

# 以下、改行が無く、ゴチャっと詰まったxmlが出力される
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15 xr xr6 xr10 xr2" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2"><fileVersion appName="xl" lastEdited="7" lowestEdited="6" rupBuild="22325"/><workbookPr/><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice Requires="x15"><x15ac:absPath url="C:\develop\jupyter\learning\parse_xlsx\" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac"/></mc:Choice></mc:AlternateContent><xr:revisionPtr revIDLastSave="0" documentId="8_{88FACECE-E4E4-47AC-A7CF-214DF38E3AE1}" xr6:coauthVersionLast="45" xr6:coauthVersionMax="45" xr10:uidLastSave="{00000000-0000-0000-0000-000000000000}"/><bookViews><workbookView xWindow="-120" yWindow="-120" windowWidth="20730" windowHeight="11160" xr2:uid="{00000000-000D-0000-FFFF-FFFF00000000}"/></bookViews><sheets><sheet name="最初のシート" sheetId="1" r:id="rId1"/><sheet name="二番目のシート" sheetId="2" r:id="rId2"/><sheet name="三番目のシート" sheetId="3" r:id="rId3"/></sheets><calcPr calcId="162913"/><extLst><ext uri="{140A7094-0E35-4892-8432-C4D2E57EDEB5}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"><x15:workbookPr chartTrackingRefBase="1"/></ext></extLst></workbook>

xml を見やすく整形

 見やすくするため、整形したxmlを以下に記載します。
 整形したxmlを読むと、以下のことがわかります。

  • sheets タグ以下の要素で、xlsxに含まれるワークシートの名前が定義されていることが分かる。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x15 xr xr6 xr10 xr2" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2">
    <fileVersion appName="xl" lastEdited="7" lowestEdited="6" rupBuild="22325"/>
    <workbookPr/>
    <mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
        <mc:Choice Requires="x15"><x15ac:absPath url="C:\develop\jupyter\learning\parse_xlsx\" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac"/></mc:Choice>
    </mc:AlternateContent>
    <xr:revisionPtr revIDLastSave="0" documentId="13_ncr:1_{F51279F2-675D-41D8-A6A8-671B02DF6F63}" xr6:coauthVersionLast="45" xr6:coauthVersionMax="45" xr10:uidLastSave="{00000000-0000-0000-0000-000000000000}"/>
    <bookViews>
        <workbookView xWindow="-120" yWindow="-120" windowWidth="20730" windowHeight="11160" xr2:uid="{00000000-000D-0000-FFFF-FFFF00000000}"/>
    </bookViews>
    <sheets>
        <sheet name="最初のシート" sheetId="1" r:id="rId1"/>
        <sheet name="二番目のシート" sheetId="2" r:id="rId2"/>
        <sheet name="三番目のシート" sheetId="3" r:id="rId3"/>
    </sheets>
    <calcPr calcId="162913"/>
    <extLst>
        <ext uri="{140A7094-0E35-4892-8432-C4D2E57EDEB5}" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main">
            <x15:workbookPr chartTrackingRefBase="1"/>
        </ext>
    </extLst>
</workbook>

xml.etree.ElementTreeを使って xmlから構造化されたデータを取りだそう!

 このxmlから構造化されたデータを取り出すには、xml を取り扱うモジュールを使うことになります。
 早速、先ほどimportした xml.etree.ElementTree モジュールを使ってみましょう。
 xml.etree.ElementTreeには、xmlを解析するためのメソッドが複数用意されています。
 今回は、以下の二つの方法を紹介したいと思います。

  1. 読み込んだ xml 文字列を引数にして fromstring() を呼び出す。
  2. zipアーカイブの open() の戻り値を引数として parse() を呼び出す

fromstring()メソッドでxmlを解析する

 読み込んだxml(str型に変換済みのもの)を引数にしてfromstring() を呼び出すと、戻り値は、elementオブジェクト となります。

  • element オブジェクトは、プロパティ tag でタグ名が、text でタグの値を取得することができます。
    • xmlのelementのタグを画面に表示すると分かりますが、タグの先頭に {} で名前空間のURLが記述されています。そのあとに、タグ名が来ているので注意しましょう!
  • また、elementオブジェクト自体がイテレータとしての機能を持っているので、 for 文で子要素を一つずつ取り出すことができます。

 それでは上記ポイントを踏まえ、以下のコードを実行してみましょう!

# body には先ほど読み込んだ xml(str型)が格納されている
tree = ET.fromstring(body)
print("Root 要素のタグ : {tag}".format(tag=tree.tag))
for _e in tree:
    print("  |  ")
    print("  +--- 子要素のタグ : {tag}".format(tag=_e.tag))

すると次のような結果が得られます。

Root 要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}workbook
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}fileVersion
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}workbookPr
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/markup-compatibility/2006}AlternateContent
  |  
  +--- 子要素のタグ : {http://schemas.microsoft.com/office/spreadsheetml/2014/revision}revisionPtr
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}bookViews
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}sheets
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}calcPr
  |  
  +--- 子要素のタグ : {http://schemas.openxmlformats.org/spreadsheetml/2006/main}extLst

xml を解析して得られた elementオブジェクトですが、次のようにインデックスを指定して、子要素を参照することもできます。これは便利ですね!


print(tree[0].tag)
print(tree[1].tag)

# 以下、実行結果
{http://schemas.openxmlformats.org/spreadsheetml/2006/main}fileVersion
{http://schemas.openxmlformats.org/spreadsheetml/2006/main}workbookPr

xml要素の属性値はプロパティattribを見れば良い!

 xml文書を解析する際には、要素が持つ属性値を参照する場面も多いと思います。
 そんな時には、elementオブジェクトが持つプロパティattribを参照しましょう。
 該当するxml要素が持つ属性値が、辞書形式で格納されています。

 例えば、先ほど読み込んだxmlの子要素の一つfileVersion の場合、attrib は次のようになっています。

xml はこちら:

  <fileVersion appName="xl" lastEdited="7" lowestEdited="6" rupBuild="22325"/>

tree[0].attrib

# 出力結果
{'appName': 'xl', 'lastEdited': '7', 'lowestEdited': '6', 'rupBuild': '22325'}

視認性を向上すべく名前空間を表示から削除する機能を用意する

 名前空間(xlsxの場合、http://schemas.openxmlformats.org/spreadsheetml/2006/main)は、大事な情報ですが、デバッグ情報の出力の度に表示するとさすがに目が疲れて来ますね。
そこで、名前空間の部分は表示から除去する機能を用意しましょう!

  • trim_ns() という関数を実装する。
  • 第一引数にelementオブジェクトのタグを指定すると、名前空間の部分を削除して、タグを返す。
    • 例 : fileVersion , sheets など。だいぶスッキリする。
NS_XLSX = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'

def trim_ns(tag, namespace=NS_XLSX):
    return str(tag).replace('{%s}' % NS_XLSX, '')

 この後は、trim_ns() を使って、xmlの解析結果を画面に出力していきます。

parse()を使ってワークシートのxmlを解析してみよう

それは続いて、ワークシートのxmlを解析してみましょう。
 例として、xl/worksheets/sheet1.xml を解析にかけましょう。
fromstring()を使った解析は先ほど試したので、xml.etree.ElementTreeparse()メソッドに、open() の戻り値を指定して、解析してみましょう。

parse()メソッドを使ってxmlを解析する際のポイントは、次の通りです。

  • この場合、戻り値が xml.etree.ElementTree.ElementTree となるため、fromstring()を使った場合とは異なるクラスのオブジェクトが返ってくるのでご注意を。 (参照)
    • 戻り値をそのまま、イテレータとしては使えない。
    • そこで getroot() でルート要素(elementオブジェクト)を取得しよう。そうすれば、イテレータとして使うことができる。

 それでは上記ポイントを踏まえ、以下のコードを実行してみましょう!

tree = ET.parse(archive.open('xl/worksheets/sheet1.xml'))

# ルート要素(elementオブジェクト)を取得しておくこと!
root_elem = tree.getroot()

print("ルート要素の tag : {tag}".format(tag=trim_ns(root_elem.tag)))
for _e in root_elem:
    print("  |  ")
    print("  +--- 子要素のタグ : {tag}".format(tag=trim_ns(_e.tag)))

すると次のような実行結果が得られます。


ルート要素の tag : worksheet
  |  
  +--- 子要素のタグ : dimension
  |  
  +--- 子要素のタグ : sheetViews
  |  
  +--- 子要素のタグ : sheetFormatPr
  |  
  +--- 子要素のタグ : cols
  |  
  +--- 子要素のタグ : sheetData
  |  
  +--- 子要素のタグ : phoneticPr
  |  
  +--- 子要素のタグ : pageMargins
  |  
  +--- 子要素のタグ : pageSetup

 trim_ns()関数を使っているので、タグの表記がスッキリしましたね!

セルの値が記入されている範囲は dimension タグの情報を見れば良い

 そのシートでは、どこからどこまでのセルに値が入っているのかが、dimension タグに記載されています。
 dimensionタグのelement要素のattrib プロパティを見てみると、以下のような値が入っています。

root_elem[0].attrib

# 以下、出力結果
{'ref': 'A1:A12'}

 これはセルA1 から A12 の範囲に値が入っていることを意味しています。

sheetData タグを解析してワークシートの中身を見てみよう

 それでは、各ワークシートの中身が記録されている sheetData タグの要素を見てみましょう。
root_elem[4]sheetDataのxml要素が格納されています。
 確認のため、以下のコードを実行してみましょう!

trim_ns(root_elem[4].tag)

# 以下、実行結果
'sheetData'

 ちゃんと、sheetData タグであることがわかります。

sheetData 要素の構造

 sheetData 要素は、次の構造のxmlで記述されます。

  • row → 列c → 値v の順番の階層で記述されている。
  • c 要素の属性 ts となっているが、これはセルの値が文字列であることを表している。
  • v 要素の値は、後述する共有文字列(sharedStrings)のインデックスである。
    • 正規化された形でセルの値を記録していることがわかる。
sheetData 要素のxml例

 sheetDataタグの子要素は、row タグであり、各行ごとのデータを記述する要素が子要素に来ていることがわかります。

<sheetData>
    <row r="1" spans="1:5" ht="15.75" customHeight="1" thickBot="1" x14ac:dyDescent="0.5">
        <c r="A1" s="30" t="s">
            <v>39</v>
        </c>
        <c r="B1" s="31" t="s">
            <v>210</v>
        </c>
        <c r="C1" s="31" t="s">
            <v>211</v>
        </c>
        <c r="D1" s="32" t="s">
            <v>212</v>
        </c>
        <c r="E1" s="32" t="s">
            <v>213</v>
        </c>
    </row>
</sheetData>

sheetData要素を解析して値を取り出してみよう!

 それでは、これまで挙げたポイントを踏まえ、以下のコードを実行してみましょう!

sheet_elem = root_elem[4]
for _e_row in sheet_elem:
    print('-' * 70)
    print("行(tag = {tag}) / {row}行目".format(tag=trim_ns(_e_row.tag), row=_e_row.attrib['r']))
    print("   attrib : {}".format(_e_row.attrib))
    print('')
    for _pos_col, _e_col in enumerate(_e_row):
        print("    列(tag = {tag}) / {col}列目".format(tag=trim_ns(_e_col.tag), col=_pos_col))
        print("       attrib : {}".format(_e_col.attrib))
        # v タグの要素を表示する
        for _e_v in _e_col:
            print("        value = {}".format(_e_v.text))

すると、以下のような結果が得られます。

----------------------------------------------------------------------
行(tag = row) / 1行目
   attrib : {'r': '1', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A1', 's': '1', 't': 's'}
        value = 0  # <--- 後述する sharedStrings のインデックス
----------------------------------------------------------------------
行(tag = row) / 2行目
   attrib : {'r': '2', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A2', 's': '6', 't': 's'}
        value = 40
----------------------------------------------------------------------
行(tag = row) / 3行目
   attrib : {'r': '3', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A3', 's': '7', 't': 's'}
        value = 41
----------------------------------------------------------------------
行(tag = row) / 4行目
   attrib : {'r': '4', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A4', 's': '2', 't': 's'}
        value = 1
----------------------------------------------------------------------
行(tag = row) / 5行目
   attrib : {'r': '5', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A5', 's': '2', 't': 's'}
        value = 2
----------------------------------------------------------------------
行(tag = row) / 6行目
   attrib : {'r': '6', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A6', 's': '2', 't': 's'}
        value = 3
----------------------------------------------------------------------
行(tag = row) / 7行目
   attrib : {'r': '7', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A7', 's': '2', 't': 's'}
        value = 4
----------------------------------------------------------------------
行(tag = row) / 8行目
   attrib : {'r': '8', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A8', 's': '2', 't': 's'}
        value = 5
----------------------------------------------------------------------
行(tag = row) / 9行目
   attrib : {'r': '9', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A9', 's': '2', 't': 's'}
        value = 6
----------------------------------------------------------------------
行(tag = row) / 10行目
   attrib : {'r': '10', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A10', 's': '2', 't': 's'}
        value = 7
----------------------------------------------------------------------
行(tag = row) / 11行目
   attrib : {'r': '11', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A11', 's': '2', 't': 's'}
        value = 8
----------------------------------------------------------------------
行(tag = row) / 12行目
   attrib : {'r': '12', 'spans': '1:1'}

    列(tag = c) / 0列目
       attrib : {'r': 'A12', 's': '2', 't': 's'}
        value = 9

 xlsx のワークシートから値を取り出すにあたり、残るは sharedStrings の解析となります。

共有文字列 sharedStrings のxmlを解析しよう

xl/sharedStrings.xml というxmlで定義されているのが共有文字列(sharedStrings)です。
 ワークシートのセルの値の実体は、こちらに記録されています(文字列型の場合)
 sharedStrings解析に当たってのポイントは次のようになります。

  • 共有文字列(sharedStrings)の XMLの仕様について、Microsoft が公開している。

    • 各sharedStrings の要素は si タグで定義されている。

    • si は、 Shared string Item の略語(出展)

    • si は、t タグで定義される通常テキスト、r タグで定義される RichTextRun要素、読み仮名を定義するrPhで構成されている。

    • r 要素において、書式はrPr(RunPropertiesの略) タグで定義される で定義される。

    • rPh 要素において、読み仮名はタグが持つテキスト、開始位置&終了位置(=読み仮名を振る場所)は、属性sbebで定義する。

    • si 要素の定義例:

  <si>
    <r>
      <t>わが社のすごーいプロダクト</t>
    </r>
    <r>
      <rPr>
        <vertAlign val="superscript"/>
        <sz val="11"/>
        <color theme="1"/>
        <rFont val="MS ゴシック"/>
        <family val="3"/>
        <charset val="128"/>
      </rPr>
      <t>®</t>
    </r>
  <phoneticPr fontId="1"/>
  </si>
  • rPr で定義できるプロパティの種類は、以下のようになっている。
    • name
    • charset
    • family
    • b
    • i
    • strike
    • outline
    • shadow
    • condense
    • color
    • extend
    • sz
    • u
    • vertAlign
    • scheme

xl/sharedStrings.xmlを読み込んでparse()で解析してみよう!

 ポイントを押さえたところで、いざ、実物のxl/sharedStrings.xmlを読み込んで、parse()で解析してみましょう!
 ちゃんと、si 要素が下にぶら下がっているのでしょうか?
 以下のコードを実行して確かめてみましょう!

tree = ET.parse(archive.open('xl/sharedStrings.xml'))
root_elem = tree.getroot()
print("ルート要素の tag : {tag}".format(tag=trim_ns(root_elem.tag)))
for _e in root_elem:
    print("  |  ")
    print("  +--- 子要素のタグ : {tag}".format(tag=trim_ns(_e.tag)))

実行結果は、次のようになります。


ルート要素の tag : sst
  |  
  +--- 子要素のタグ : si
  |  
  +--- 子要素のタグ : si
  |  
  +--- 子要素のタグ : si
  |  
  +--- 子要素のタグ : si
  |  
  +--- 子要素のタグ : si
  |  
  +--- 子要素のタグ : si
  |  
(・・・中略・・・)

 ちゃんと仕様通り、si 要素が子要素としてぶら下がっていることがわかります。
 それでは続いて、si要素の中を解析する処理を実装してみましょう。
 
少し長いですが、以下のコードを実行してみましょう!

for _si_idx, _e in enumerate(root_elem):
    print("-" * 70)
    print("No : {no} の共有文字列".format(no=_si_idx))

    # 各 si 要素の中のタグを取り出す
    for _chunk in _e:
        print("tag = {tag}".format(tag=trim_ns(_chunk.tag)))
        if trim_ns(_chunk.tag) == 't':
            # テキスト要素ならば、その中身を取り出すだけでよい
            print("value = {value}".format(value=_chunk.text))
        elif trim_ns(_chunk.tag) == 'r':
            # リッチテキスト形式の場合
            _text = ''
            _attrib = {}
            for _sub_chunk in _chunk:
                if trim_ns(_sub_chunk.tag) == 't':
                    # テキスト部分を取り出す
                    _text = _sub_chunk.text
                elif trim_ns(_sub_chunk.tag) == 'rPr':
                    # リッチテキスト情報を取り出してゆく
                    for _eprop in _sub_chunk:
                        _attrib_key = trim_ns(_eprop.tag)
                        if len(_eprop.attrib) > 0:
                            _attrib[_attrib_key] = _eprop.attrib
                        else:
                            # 属性値を持たないタグ i, b の場合はTrueをセット
                            _attrib[_attrib_key] = True
            print("value = {value}".format(value=_text))
            print("書式設定 {attrib}".format(attrib=_attrib))
        elif trim_ns(_chunk.tag) == 'rPh':
            # 読み仮名の場合
            for _kana_chunk in _chunk:
                print("   読み仮名 : {kana} 場所({pos_start} ... {pos_end})".format(kana=_kana_chunk.text,
                                                                                    pos_start=_chunk.attrib['sb'],
                                                                                    pos_end=_chunk.attrib['eb']))

 実行すると以下のような結果が得られます。


----------------------------------------------------------------------
No : 0 の共有文字列
tag = t
value = プログラミング言語の名前
tag = rPh
   読み仮名 : ゲンゴ 場所(7 ... 9)
tag = rPh
   読み仮名 : ナマエ 場所(10 ... 12)
tag = phoneticPr
----------------------------------------------------------------------
No : 1 の共有文字列
tag = t
value = scala
tag = phoneticPr
----------------------------------------------------------------------
No : 2 の共有文字列
tag = t
value = lua
tag = phoneticPr
----------------------------------------------------------------------
No : 3 の共有文字列
tag = t
value = php
tag = phoneticPr
----------------------------------------------------------------------
No : 4 の共有文字列
tag = t
value = java
tag = phoneticPr
----------------------------------------------------------------------
No : 5 の共有文字列
tag = t
value = C#
tag = phoneticPr
----------------------------------------------------------------------
No : 6 の共有文字列
tag = t
value = C++
tag = phoneticPr
----------------------------------------------------------------------
No : 7 の共有文字列
tag = t
value = C
tag = phoneticPr
----------------------------------------------------------------------
No : 8 の共有文字列
tag = t
value = COBOL
tag = phoneticPr
----------------------------------------------------------------------
No : 9 の共有文字列
tag = t
value = FORTRAN
tag = phoneticPr
----------------------------------------------------------------------
No : 10 の共有文字列
tag = t
value = 通貨の種類
tag = rPh
   読み仮名 : ツウカ 場所(0 ... 2)
tag = rPh
   読み仮名 : シュルイ 場所(3 ... 5)
tag = phoneticPr
(・・・中略・・・)
----------------------------------------------------------------------
No : 40 の共有文字列
tag = r
value = P
書式設定 {}
tag = r
value = ython
書式設定 {'b': True, 'sz': {'val': '11'}, 'rFont': {'val': 'Yu Gothic'}, 'family': {'val': '3'}, 'charset': {'val': '128'}, 'scheme': {'val': 'minor'}}
tag = phoneticPr
----------------------------------------------------------------------
No : 41 の共有文字列
tag = r
value = g
書式設定 {'b': True, 'sz': {'val': '11'}, 'color': {'theme': '1'}, 'rFont': {'val': 'Yu Gothic'}, 'family': {'val': '3'}, 'charset': {'val': '128'}, 'scheme': {'val': 'minor'}}
tag = r
value = o
書式設定 {'sz': {'val': '11'}, 'color': {'theme': '1'}, 'rFont': {'val': 'Yu Gothic'}, 'family': {'val': '2'}, 'scheme': {'val': 'minor'}}
tag = phoneticPr

セルの中のテキストに、書式設定をすると、リッチテキスト形式で記録されます。
実際に試してみるとお分かりいただけると思います。
セル全体の装飾だと、セルの中にリッチテキスト形式で記録される事は無いようです。

  • セル事の装飾設定がある

だいぶ長くなってしまったので、とりあえずこのあたりで

 エントリの内容がだいぶ長くなってしまいました。
 xlsxの中身の解析は、まだ先があるのですが、今回はいったんこのあたりで締めくくりたいと思います。
 しかし、普段仕事で使うオフィス文書がこうしてxml形式で扱えることがわかってくると、Pythonで文書操作を自動化して、RPAの一端を担うプロセスを開発できそうな気がしてきますね。

 次回は、セルの内容を取ってくる部分の処理や、セル事の装飾設定について触れたいと思います!

最新情報をチェックしよう!