💨

OpenPyXLなしでPythonでExcelファイルをいじり倒す(xlsx分解編)

2023/04/12に公開

Python で excel を扱いたいけれど・・・

Python で excel ファイルを扱おうとした場合の定番ツールに openpyxl があります。ブックの作成やワークシートの追加、編集などはサポートされています。表計算の場合にはおおよそ問題ありませんが、申請書のようなものについては問題があります。多くの excel の申請書では性別のように選択肢で入力する部分がありますが、この入力に円などの図形を充てることがよくあります。
一方で、openpyxl ではこういった図形の追加、編集がサポートされていないようです。そのため、既存の xlsx ファイルを openpyxl で読み込んだ場合、こういった図形やテキストボックスは消えてしまいます。

図形の追加等ができるライブラリとして、xlwings や XLSXwriter というものもあります。ただ、xlwings は Windows でしか動かないので、サーバサイドで動かす場合にはかなりの制約が生まれてきてしまいます。また、XLSXwriter は既存のシートの読み込みができなかったりします。

そこで今回は xlsx ファイルを直接編集することで、図形などの要素を追加してみようと思います。

xlsx の構造を分解する

xlsx ファイルをメモ帳などで開くと文字化けするので、バイナリデータなのかな?と思いますがそうではありません。xlsx ファイルを zip で保存し、その zip を解凍すると、いくつかの xml ファイルが存在することがわかります。つまり、xlsx ファイルの実態は xml ファイルで構造化されたものを zip として圧縮したもの、となります。基本的な xlsx の構造はギークフィードさんの記事が詳しく解説されているのでこ知らも参考に。

ここでは、ありがちな申請書 excel の中身を分解してみます。
こんなようなものです。実にありがちですね。
<img src="demo_sheet1.png" width="30%">
<img src="demo_sheet2.png" width="30%">

さて、このファイルの拡張子を zip に変え、解凍すると

.
├── [Content_Types].xml
├── _rels
├── docProps
│   ├── app.xml
│   └── core.xml
└── xl
    ├── _rels
    │   └── workbook.xml.rels
    ├── drawings
    │   ├── drawing1.xml
    │   └── drawing2.xml
    ├── sharedStrings.xml
    ├── styles.xml
    ├── theme
    │   └── theme1.xml
    ├── workbook.xml
    └── worksheets
        ├── _rels
        │   ├── sheet1.xml.rels
        │   └── sheet2.xml.rels
        ├── sheet1.xml
        └── sheet2.xml

という作りになっています。ブック全体の情報は workbook.xml、各ワークシートの情報は worksheets にまとめられています。図形などのワークシート上のそれぞれのパーツは drawings にまとめられています。例えば、drawing1.xml の中身は

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
    xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
    <xdr:oneCellAnchor>
        <xdr:from>
            <xdr:col>9</xdr:col>
            <xdr:colOff>123825</xdr:colOff>
            <xdr:row>4</xdr:row>
            <xdr:rowOff>209550</xdr:rowOff>
        </xdr:from>
        <xdr:ext cx="1595309" cy="328423" />
        <xdr:sp macro="" textlink="">
            <xdr:nvSpPr>
                <xdr:cNvPr id="3" name="テキスト ボックス 2">
                    <a:extLst>
                        <a:ext uri="{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}">
                            <a16:creationId
                                xmlns:a16="http://schemas.microsoft.com/office/drawing/2014/main"
                                id="{9A29B679-661D-712D-FA5B-6FD50DB23C44}" />
                        </a:ext>
                    </a:extLst>
                </xdr:cNvPr>
                <xdr:cNvSpPr txBox="1" />
            </xdr:nvSpPr>
---- 中略 ----
            <xdr:txBody>
                <a:bodyPr vertOverflow="clip" horzOverflow="clip" wrap="none" rtlCol="0" anchor="t">
                    <a:spAutoFit />
                </a:bodyPr>
                <a:lstStyle />
                <a:p>
                    <a:r>
                        <a:rPr kumimoji="1" lang="ja-JP" altLang="en-US" sz="1100" />
                        <a:t>和暦でお願いします!</a:t>
                    </a:r>
                </a:p>
            </xdr:txBody>
        </xdr:sp>
        <xdr:clientData />
    </xdr:oneCellAnchor>
</xdr:wsDr>

となっています。

xml を直接いじってテキストボックスの中身や図形の位置を変えてみる

どうやら、

<a:r>
    <a:rPr kumimoji="1" lang="ja-JP" altLang="en-US" sz="1100" />
    <a:t>和暦でお願いします!</a:t>
</a:r>

ここがテキストボックスの中身のようです。試しにメモ帳で

<a:r>
    <a:rPr kumimoji="1" lang="ja-JP" altLang="en-US" sz="1100" />
    <a:t>西暦でお願いします!</a:t>
</a:r>

としてみます。編集後再度 zip にし、拡張子を xlsx にリネームすると

<img src="demo_edit_1.png" width="50%">

文章が変わった!さらに、性別選択の円をずらしてみましょう。図形の位置は

<xdr:from>
    <xdr:col>4</xdr:col>
    <xdr:colOff>276225</xdr:colOff>
    <xdr:row>13</xdr:row>
    <xdr:rowOff>142875</xdr:rowOff>
</xdr:from>
<xdr:to>
    <xdr:col>5</xdr:col>
    <xdr:colOff>9525</xdr:colOff>
    <xdr:row>15</xdr:row>
    <xdr:rowOff>85725</xdr:rowOff>
</xdr:to>

と規定されていそうです。察すると、4 列目の 13 行目から 5 列目の 15 行目付近に図形を配置しているように見えます。
試しにxdr:colを一つずらしてみましょう。

<xdr:from>
    <xdr:col>5</xdr:col>
    <xdr:colOff>276225</xdr:colOff>
    <xdr:row>13</xdr:row>
    <xdr:rowOff>142875</xdr:rowOff>
</xdr:from>
<xdr:to>
    <xdr:col>6</xdr:col>
    <xdr:colOff>9525</xdr:colOff>
    <xdr:row>15</xdr:row>
    <xdr:rowOff>85725</xdr:rowOff>
</xdr:to>

<img src="demo_edit_2.png" width="50%">

お、横にずれましたね。これで大まかな位置調整はできそうです。xdr:colOffあたりは位置の微調整でしょうか。

終わりに

いかがだったでしょうか。xlsx ファイルを Excel を使わずにメモ帳で編集する人なんて、普通はいないと思いますが・・・。
次回はこれまでのお試しを元に、実際に python でテキストボックス等を操作してみます。

Discussion