NPOIを調べていたのにApache POIでxlsx形式ファイルがつくれなくて悩んだ件
先日からお盆のあたりでNPOIを引き続き調べていますが、「Apache POIを移植したものがNPOIなんだったら、本家POIでもやってみよう」と思い立って同じようなコードを書いたのですが… ハマってしまいました。
でも、大丈夫。とりあえず本家POIのほうは一昨日解決したので覚えているうちにエントリーします。
Apache POIでxlsxを作ることができない
Javaは面倒に感じてしまうので、PowerShellと同じ感覚でgroovyで書き始めました。
@Grab("org.apache.poi:poi-ooxml:3.+") import org.apache.poi.xssf.usermodel.* import org.apache.poi.ss.usermodel.* xls = new XSSFWorkbook() ws = xls.createSheet("hello") r = ws.createRow(0) c = r.createCell(0) c.setCellValue("hello") new File("hello.xlsx").withOutputStream{ xls.write(it) }
うん、NPOIのときとだいたい同じですね。 しかし、これがうまくいきません。
Caught: java.lang.NoClassDefFoundError: org/openxmlformats/schemas/spreadsheetml/x2006/main/CTExtensionList java.lang.NoClassDefFoundError: org/openxmlformats/schemas/spreadsheetml/x2006/main/CTExtensionList at excel.run(excel.groovy:9) Caused by: java.lang.ClassNotFoundException: org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExtensionList ... 1 more
クラスが見つからない?ということで調べ始めました。
ハマってしまった根本的な原因
ググっていくと、Apache POI本家のFAQサイトにたどり着きました。
Frequently Asked Questions http://poi.apache.org/faq.html
ここをきちんと読まなかったことが今回ハマってしまった根本的な原因だったのですが、NoClassDefFoundErrorでページ内検索をしてしまったので、FAQの18番を読んでしまったのです。 あとからよくよく考えたら全然関係なかったのですが。
18.Why do I get a java.lang.NoClassDefFoundError: javax/xml/stream/XMLEventFactory.newFactory()
http://poi.apache.org/faq.html#faq-N1017E
ここでは古いjarを読み込んでしまっているので、というようなことが記載してあってですね。 「ああ、除外すればいいのか」と勘違いしてしまったのです。 そして先ほどのコードに@GrabExclude()を記述してうまくいくかな?とやってみてしまったのです。
もちろん、無関係なのでまったく結果は変わりません。うげーっとなってしまいました。
Apache POIを読む
オープンソースなのでソースコードはダウンロードしてくることができます。 だからせっかくなので「本当にそのクラスがないのかみてみよう」ということでさらに調べ始めました。 そうすると、他にもjarがあることに気づきました。
どうやらxlsx形式のときは「poi-ooxml-schema.jar」というのが必要なようなのです。 そこで、ソースコードを読む前に@Grab("org.apache.poi:poi-ooxml-schema:3.+")というのを追記しました。 これでうまくいくかと思いきや、これまたエラーになりました。
そこで、今度こそこの「poi-ooxml-schema」の中身をみてみたくなってソースコードをみてみることにしました。
エラーになっている箇所をおさらいすると、setCellValueメソッドです。ここからみていくことにしました。
ダウンロードしてきたのは3.13-betaです。これを解凍してみると、srcの中にooxmlというものがあったので、これでしょう。
「org/apache/poi/xssf/usermodel/XSSFCell.java」が該当するソースです。 今回は"hello"と文字列を渡してあるので、いくつもあるメソッドのオーバーライドのうち、文字列を引数に取るものが該当のメソッドですね。330行目です。
@Override public void setCellValue(String str) { setCellValue(str == null ? null : new XSSFRichTextString(str)); }
これをみると、どうやら「XSSFRichTextString」というクラスに与えられた文字列を渡してインスタンス化しようとしています。ではそちらも調べてみることにします。
こちらも該当のコンストラクタは89行目ですね。
public XSSFRichTextString(String str) { st = CTRst.Factory.newInstance(); st.setT(str); preserveSpaces(st.xgetT()); }
ここではさらに「CTRst」クラスのFactoryを使って新しいインスタンスを作成しようとしています。 クラス名の先頭が「CT」で始まっているので、なんとなく例のエラーにあった「CTExtensionList」に近づいてきた雰囲気があります。
では、この「CTRst」クラスも見てみましょう。
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst;
importからクラスがわかったので、このソースも探してみることにします。しかしこのソースはApach POIのtar ballには含まれていないようでした。
行き詰まってしまったので、今度はGrabがダウンロードしてきているjarファイルから探すことにしました。
jarファイルから調べる
ここまでで「CT」で始まるクラスからなんらかのインスタンスを生成するときにエラーの原因になっている「CTExtensionList」が見つからないのだろう、というところまで推測はつきました。
というわけで、Grabがダウンロードしてきているjarを直接見てみることにしました。
「~/.groovy/grapes/org.apache.poi/poi-ooxml-schemas/jars」にダウンロードされていた「poi-ooxml-schemas-3.12.jar」を解凍して中身を見ていきます。
unzip poi-ooxml-schemas-3.12.jar
そうすると、解凍されてきた中から「org/openxmlformats/schemas/spreadsheetml/x2006/main」がありました。この中に「CTExtensionList」が存在しなければアウトです。
見てみると、存在しません。
なるほど、そりゃエラーになりますよね。ソースをみるまでもなかったかもしれませんでした。
CTExtensionListはどこにある
ここで、一番最初のFAQに戻ってみることにしました。冒頭にFAQのリストがあるので眺めていると、怪しいものがすぐにありました。どうして見落としたんでしょう…
3.I'm using the poi-ooxml-schemas jar, but my code is failing with "java.lang.NoClassDefFoundError: org/openxmlformats/schemas/something" http://poi.apache.org/faq.html#faq-N10025
ほんとに何故こんな「そのまんま」なものを見落としていたのでしょう? 「poi-ooxml-schemasを使ってるけど、org/openxmlformats/schemas/の何かが見つからなくて失敗する」ですって。悲しい。
内容を簡単にいうと、以下のとおり
POIでooxmlファイル形式のものを扱うにはXSDというファイル形式が含まれたjarが必要
このjarにはサイズの大きいものと小さくまとめたものの2種類ある
今回の用途に必要なのはサイズの大きい「ooxml-schemas-1.1.jar」というやつ
ということで、コードを少しだけ書き直しました。
@Grapes([ @Grab("org.apache.poi:poi-ooxml:3.+"), @Grab("org.apache.poi:ooxml-schemas:1.1") ]) import org.apache.poi.xssf.usermodel.* import org.apache.poi.ss.usermodel.* xls = new XSSFWorkbook() ws = xls.createSheet("hello") r = ws.createRow(0) c = r.createCell(0) c.setCellValue("hello") new File("hello.xlsx").withOutputStream{ xls.write(it) }
これだけで、エラーなくxlsxファイルが作成できました。 わかってしまえばハマる要素は皆無でした…
Apache POIを調べた結果wwwwwww
NPOIで作成したxlsxファイルがNumbersで開くことができない件を調査していたのに脱線した上に大幅にハマってしまいました。 しかも、こうしてApache POIから生成できたxlsxファイルはNumbersでも開くことができました。
もはや悲しいというよりアレですね。
ということで、NPOIのほうはもう少し続きそうです。










