注目キーワード

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:developjupyterlearningparse_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:developjupyterlearningparse_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:developjupyterlearningparse_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の一端を担うプロセスを開発できそうな気がしてきますね。

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

こちらのエントリも併せてどうぞ!

目次 1 まじめにコーディング練習1.1 秘書問題とは1.1.1 代表的な例1.2 数学的に証明されている最適解1.3 まずは数学的な最適解を確かめる2 実験2.1 応募者サンプルデータを作る関数2.2 自然対数 e を取得する2.3 秘書問題を解く関数を実装する3 実験を実施4 採用結果の期待値( […]

目次 1 概要2 命名規則(Naming)2.1 避けるべき名前2.2 命名規則(Naming Conventions)2.2.1 Protected/private メソッド・プロパティ2.2.2 ダンダー(__)を使ったprivate化の注意点2.2.3 モジュール化:クラスや関数のまとめ方2. […]

目次 1 Python でExcelファイルをいじってみたらzip圧縮ファイルだった!2 このエントリを読むと得られるであろうもの3 実験環境についての説明4 今回使用するPythonモジュールを読み込む5 xlsxファイルをzipファイルとして読み込んでみる6 zipファイルのアーカイブからxml […]

目次 1 Pythonでtar.gzアーカイブを扱う方法2 Python の tarfile モジュール3 tarfile モジュールのインポート4 tar.gz アーカイブを読み込む5 tar.gz アーカイブを出力するコード6 まとめ等 Pythonでtar.gzアーカイブを扱う方法  備忘録& […]

目次 1 Apache Beam チュートリアル2 Apache Beam 概要2.0.1 (補足) Apache Beam のリリース状況3 概念3.1 Runner(実行環境)3.2 パイプライン処理を構成する概念4 基本的なパイプラインの開発の流れ4.1 パイプライン処理内の各工程の詳細5 動 […]

目次 1 長期保有シミュレーション1.1 背景2 シミュレーションの準備2.1 データファイルの設置場所3 シミュレーション処理3.1 使用するモジュール群の読み込み3.2 視覚化処理の設定3.3 価格データの読み込みと前処理3.3.1 前処理3.3.1.1 カンマ区切りのOHLCの処理3.3.1. […]

目次 1 概要2 Anacondaで開発環境を構築2.1 自然言語処理用の開発環境を conda で構築3 mecab 本体のインストール4 neologd のインストール&設定4.1 ビルド環境の整備4.2 neologd のインストール4.3 mecab の辞書を neologd に変更する4. […]

目次 1 概要2 感染フェーズの数値化とは2.1 Phase Position の分布から分かること2.2 Phase Position 上位国の顔ぶれ3 序盤から感染爆発に襲われた欧米先進国4 感染者の重症化率から分かること4.1 重症化率の高い国は医療崩壊のリスクに晒されている5 医療崩壊リスク […]

目次 1 秘書問題をもうちょっとだけ掘り下げる2 おさらい:シミュレーションの定義3 今回の評価基準:評価期待値4 実験5 Python コード実装!5.1 序盤定義部分5.2 応募者サンプルデータを作る関数5.3 秘書問題を解く関数を実装する6 実験6.1 実験結果を描画する7 レビューまとめ8 […]

目次 1 新型コロナ・致死率の時系列推移を分析した結果分かったこと1.1 新型コロナのニュースで頻繁に見かける国々と日本の比較:1.2 ヨーロッパにおける致死率の二極化:1.3 新型コロナ対策に成功している国?での比較 新型コロナ・致死率の時系列推移を分析した結果分かったこと  先日から使い始めたジ […]

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