[SikuliX-2.0.5] openpyxl を使う

## いそがしい人用（Windowsの場合）
1. **pythonモジュールを`%APPDATA%\Sikulix\Lib\site-packages`に配備**
   [openpyxl-2.6.4.tar.gz](https://pypi.org/project/openpyxl/2.6.4/) ⇒ `openpyxl`フォルダ
   [jdcal-1.4.1-py2.py3-none-any.whl](https://pypi.org/project/jdcal/1.4.1/) ⇒ `jdcal.py`ファイル
   [et_xmlfile-1.0.1.tar.gz](https://pypi.org/project/et_xmlfile/1.0.1/) ⇒ `et_xmlfile`フォルダ
  
1. **Apache Xerces XMLパーサモジュールを`%APPDATA%\Sikulix\Lib\Extensions`に配備**
   [Apache Xerces - Binary Distributions](https://xerces.apache.org/mirrors.cgi#binary) から、「Xerces2 Java 2.12.2 - zip」をダウンロード。
   圧縮ファイルから「xercesImpl.jar」ファイルを取得し、Extensionsフォルダに配備
   

**⇒ SikuliXプログラムで `import openpyxl` が可能になる**


----
## SikuliX-2.0.5 で`.xlsx`を使いたい！
SikuliXは Jython2.7 で動作しており、Python2.7 のモジュールを利用することができますが、`openpyxl`を利用する方法が見当たらなかったので、自分で書いてみました。


----
# 背景
SikuliXを利用した業務のRPA化を行うにあたり、Excelの入出力を行うことが多くあります。

2022/8現在、SikuliXの最新安定バージョンは2.0.5では、Excelの操作ができるPythonモジュール `xlrd`(読み込み専用) と `xlwt`(書き込み専用) を標準で搭載しています。
既存ファイルを更新する際は、`xlrd`で読み込んだ情報を編集しながら、`xlwt`で書き込みを行う、という手順となります。
問題は、この２つのモジュールが Excel2003 以前の拡張子`.xls`しか操作できないことです。

>　「いつまで古い拡張子で運用しているのだ！」
> と指摘してきた手前、
>　「SikuliXでRPA化するので古い拡張子でExcelを保存してください。」
> とは言い辛い…

Excelを操作できるPythonモジュールは下記で紹介されています。

https://www.python-excel.org/

今回は`.xlsx`が操作できる `openpyxl` を利用することにしました。


----
## Pythonモジュールだけ配備してみる

くどいようですが、SikuliX は Python2.7 相当の Jython2.7 で動作しており、配備するモジュールも Python2.7用を利用する必要があります。
モジュールの入手にあたり依存関係を考慮する必要がないよう、Python2.7をインストールしたうえで、`pip`コマンドで`openpyxl`をダウンロードしました。

https://www.python.org/downloads/windows/

```cmd:cmd.exe
$ c:\Python2.7\python.exe -V
Python 2.7.18
```

最終バージョンの2.7.18をインストール後、`pip`コマンドでダウンロードします。
```cmd:cmd.exe
$ c:\Python2.7\python.exe -m pip download -s src
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Collecting openpyxl==2.6.4
  Using cached openpyxl-2.6.4.tar.gz (173 kB)
Collecting jdcal
  Using cached jdcal-1.4.1-py2.py3-none-any.whl (9.5 kB)
Collecting et_xmlfile
  Using cached et_xmlfile-1.0.1.tar.gz (8.4 kB)
Saved c:\python27\src\openpyxl-2.6.4.tar.gz
Saved c:\python27\src\jdcal-1.4.1-py2.py3-none-any.whl
Saved c:\python27\src\et_xmlfile-1.0.1.tar.gz
Successfully downloaded openpyxl jdcal et-xmlfile
```
下記バージョンのファイルがダウンロードされました。（`pip`を使わず、下記からダウンロードしても同じです。）
* [openpyxl-2.6.4.tar.gz](https://pypi.org/project/openpyxl/2.6.4/)
* [jdcal-1.4.1-py2.py3-none-any.whl](https://pypi.org/project/jdcal/1.4.1/)
* [et_xmlfile-1.0.1.tar.gz](https://pypi.org/project/et_xmlfile/1.0.1/)


すべて圧縮ファイルなので、解凍して次のフォルダ・ファイルを取り出し、`%APPDATA%\Sikulix\Lib\site-packages`に配備します。
* openpyxl-2.6.4.tar.gz ⇒ `openpyxl`フォルダ
* jdcal-1.4.1-py2.py3-none-any.whl ⇒ `jdcal.py`ファイル
* et_xmlfile-1.0.1.tar.gz ⇒ 'et_xmlfile' フォルダ


これで`openpyxl`を使うための Pythonモジュールは配備されたので、さっそく import してみましょう。
```python: openpyxl_test.sikuli
import openpyxl
```
↓↓↓↓↓ 実行 ↓↓↓↓↓

:::note alert
[error] JythonRunner: runPython: (
import openpyxl
) raised: Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "%USERPROFILE%\AppData\Roaming\Sikulix\Lib\openpyxl\__init__.py", line 6, in <module>

... 略 ...

ory.loadClass(XMLReaderFactory.java:200)
	at java.xml/org.xml.sax.helpers.XMLReaderFactory.createXMLReader(XMLReaderFactory.java:191)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
org.xml.sax.SAXException: org.xml.sax.SAXException: SAX2 driver class org.apache.xerces.parsers.SAXParser not found
java.lang.ClassNotFoundException: org.apache.xerces.parsers.SAXParser
:::

----
## SikuliXに `openpyxl` モジュールだけ入れても、`org.apache.xerces.parsers.SAXParser`が見つからない、とのエラーが発生

じゃあ org.apache.xerces のクラスファイルを配備すればよい！というところですが…
[Q/Aサイト](https://answers.launchpad.net/sikuli/+question/698733) に

> 1. SikuliX-2.0.5 に Xerces プロジェクトは含んていません。
> 2. Jython2.7 で使用すると、クラスパスが衝突する可能性があるため、機能しない可能性があります
> 3. Jython2.7 では一般に、Xerces プロジェクトを使用すると問題が発生します
>    (これは、Xerces プロジェクトの SAX 実装内の基本的な問題によるものです)。

との開発者RaiMan氏のコメントを見つけてしまいました…

----
## 機能しない可能性？ … とにかく試してみる

可能性ではあきらめきれない／どんなエラーになるか気になるので、org.apache.xerces を配備しています。

1. [The Apache Xerces™ Project - Downloads](https://xerces.apache.org/mirrors.cgi#binary) へアクセスし、「Xerces2 Java 2.12.2 - zip」のリンクをクリックして`Xerces-J-bin.2.12.2.zip`ファイルをダウンロードする。
1. ダウンロードした圧縮ファイルから`xercesImpl.jar`ファイルを取り出し、
   `%APPDATA%\Sikulix\Lib\Extensions` へ配備する。
1. SikuliXが起動中の場合は再起動する。

以上でクラスパス上に jar ファイルが配備された状態となります。


----
# 結論：SikuliX-2.0.5 で openpyxl は使えます！
`import openpyxl` は問題なく動作することができ、
Excelファイルを作成・読込・更新のような簡単な操作であれば、
問題なく動作することができました。

```python:openpyxl_test.sikuli
import openpyxl

# エクセルを作成
wb = openpyxl.Workbook()

# シート名を変更し、A1セルに値を書き込み
ws = wb.worksheets[0]
ws.title = u"サンプル"
ws.cell(row=1, column=1).value = u"A1セルに値を書き込み"

# 保存
wb.save(u"テスト.xlsx")

popup(u"作成しました。")


# エクセルの更新
wb = openpyxl.load_workbook(u"テスト.xlsx")

# シートを選択して、内容編集
ws = wb[u"サンプル"]
ws.cell(row=2,column=1).value = u"値を設定"
ws.cell(row=3,column=1).value = ws.cell(row=2,column=1).value

# 更新
wb.save(u"テスト.xlsx")

popup(u"更新しました。")
```

----
## おわりに
SikuliX から拡張子`.xlsx`の読み書きを行いたい場合は、
この手法を利用することで可能になります。
高価な RPA ツールからの脱却に、オープンソースSikuliX、いかがでしょうか！