📝

OpenPyXLなしでPythonでExcelファイルをいじり倒す(セル編集編)

2023/06/13に公開

はじめに

この記事の続きです。
今回は、3のセルへの値追加です。

  1. 既存の excel ファイルをテキストボックスや罫線などを破壊せずに読み込む
  2. 読み込んだシートに新しいシートを追加できる
  3. シート上のセルに値を追加できる
  4. 読み込んだシートにテキストボックスなどの新しい要素を追加できる
  5. Linux 上で動作する

解決方法:セルに値を追加するメソッド

前回のWorkbookクラスに以下のメソッドを追加すればOKです。

    def add_cellvalue(self, sheet_name, row, col, value):
        # add new shared item
        if not os.path.exists(os.path.join(self.basename, "xl", "sharedStrings.xml")):
            shutil.copy(
                self.templates["shared_strings"],
                os.path.join(self.basename, "xl", "sharedStrings.xml"),
            )

        shared_strings = ET.parse(
            os.path.join(self.basename, "xl", "sharedStrings.xml")
        )
        ss_root = shared_strings.getroot()
        shared_item = ET.SubElement(ss_root, f"{self.ns['main']}si")
        text = ET.SubElement(shared_item, f"{self.ns['main']}t")
        text.text = value
        phontic_pr = ET.SubElement(
            shared_item, f"{self.ns['main']}phoneticPr", attrib={"fontId": "1"}
        )

        current_item_num = str(int(ss_root.attrib["uniqueCount"]))
        ss_root.set("uniqueCount", str(int(ss_root.attrib["uniqueCount"]) + 1))
        ss_root.set("count", str(int(ss_root.attrib["count"]) + 1))
        self._write_xml(ss_root, os.path.join(self.basename, "xl", "sharedStrings.xml"))

        # assign new item onto specic cell
        ws = ET.parse(os.path.join(self.basename, "xl", "worksheets", sheet_name))
        ws_root = ws.getroot()

        for sheetdata in ws_root.findall(f"{self.ns['main']}sheetData"):
            r = ET.SubElement(
                sheetdata,
                f"{self.ns['main']}row",
                attrib={"r": str(row), "spans": "1:1"},
            )
            cell = ET.SubElement(
                r, f"{self.ns['main']}c", attrib={"r": f"{col}{row}", "t": "s"}
            )
            cellvalue = ET.SubElement(
                cell,
                f"{self.ns['main']}v",
            )
            cellvalue.text = current_item_num

        self._write_xml(
            ws_root, os.path.join(self.basename, "xl", "worksheets", sheet_name)
        )

Workbook クラスに対し次のように add_cellvalue メソッドを実行することでセルに値が追加されます。下記の例では新しいシート"というシートのA4に"こんにちは"という文字列が追加されます。

wb = Workbook("test.xlsx")
wb.add_sheet(sheet_name="新しいシート")
self.report.add_cellvalue(
	sheet_name="新しいシート",
	row="4",
	col="A",
	value="こんにちは",
)
wb.write_xlsx("test_modified.xlsx)

解説:add_cellvalueの中身

Excelのブックの値は実は各シートに保管されているわけではありません。各シートの値そのものはxl/sharedStrings.xml"に記録されており、それぞれのシートにおけるセルの値はこのsharedStrings.xml``の参照先が記録されています。(詳しい説明はギークフィードさんの記事に書いてあるのでこちらもぜひ。)

そのため、セルを値に追加するためには、

  1. sharedStrings.xmlに追加したい値とそのインデックスを記入
if not os.path.exists(os.path.join(self.basename, "xl", "sharedStrings.xml")):
    shutil.copy(
	self.templates["shared_strings"],
	os.path.join(self.basename, "xl", "sharedStrings.xml"),
    )

shared_strings = ET.parse(
    os.path.join(self.basename, "xl", "sharedStrings.xml")
)
ss_root = shared_strings.getroot()
shared_item = ET.SubElement(ss_root, f"{self.ns['main']}si")
text = ET.SubElement(shared_item, f"{self.ns['main']}t")
text.text = value
phontic_pr = ET.SubElement(
    shared_item, f"{self.ns['main']}phoneticPr", attrib={"fontId": "1"}
)

current_item_num = str(int(ss_root.attrib["uniqueCount"]))
ss_root.set("uniqueCount", str(int(ss_root.attrib["uniqueCount"]) + 1))
ss_root.set("count", str(int(ss_root.attrib["count"]) + 1))
self._write_xml(ss_root, os.path.join(self.basename, "xl", "sharedStrings.xml"))
  1. 対象のシート、列、行にsharedStrings.xmlの参照先を記入
# assign new item onto specic cell
ws = ET.parse(os.path.join(self.basename, "xl", "worksheets", sheet_name))
ws_root = ws.getroot()

for sheetdata in ws_root.findall(f"{self.ns['main']}sheetData"):
    r = ET.SubElement(
	sheetdata,
	f"{self.ns['main']}row",
	attrib={"r": str(row), "spans": "1:1"},
    )
    cell = ET.SubElement(
	r, f"{self.ns['main']}c", attrib={"r": f"{col}{row}", "t": "s"}
    )
    cellvalue = ET.SubElement(
	cell,
	f"{self.ns['main']}v",
    )
    cellvalue.text = current_item_num

self._write_xml(
    ws_root, os.path.join(self.basename, "xl", "worksheets", sheet_name)
)

という流れになります。xmlの読み書きが煩雑ですがほぼゼロから実装するとこんな作りになります。正直、セルに値を追加するだけならOpenPyXlなどのライブラリを使ったほうがいいですね。ミスも起きやすい(例えば、sharedStrings.xmlの要素カウントの不一致があると、破損したファイルになってしまう)ですし。

終わりに

次は本命の画像の追加です!

Discussion