tag:crieit.net,2005:https://crieit.net/tags/LibreOffice/feed 「LibreOffice」の記事 - Crieit Crieitでタグ「LibreOffice」に投稿された最近の記事 2021-12-11T12:05:57+09:00 https://crieit.net/tags/LibreOffice/feed tag:crieit.net,2005:PublicArticle/17844 2021-12-11T12:03:55+09:00 2021-12-11T12:05:57+09:00 https://crieit.net/posts/libreoffice-calc-jruby-sinatra LibreOffice Calcのfodsファイルを読み書きするサンプルをweb API化してみた <p>これは <a target="_blank" rel="nofollow noopener" href="https://adventar.org/calendars/6425">LibreOffice Advent Calendar 2021</a> の11日目の記事です。</p> <p>↓これにちょろっと付け足して web API 化してみただけの記事です。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/b88c89d763cf872db5a1">JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a></p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><code>webapi-2021</code> ブランチ</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/libreoffice-jruby-sample/tree/webapi-2021">https://github.com/sonota88/libreoffice-jruby-sample/tree/webapi-2021</a></p> <p>Java 版でやろうかと考えていたのですが、億劫になって JRuby 版でやりました。 JRuby で Sinatra 使った方が速い。</p> <p>主な部分だけ抜き出すとこんな感じ。考えるのが面倒だったので REST ではなく RPC風で。普通に <a target="_blank" rel="nofollow noopener" href="http://sinatrarb.com/">Sinatra</a> を使ってるだけですね。あんまり書くことがない…… 😓</p> <pre><code class="ruby"># app.rb require "sinatra" require_relative "libo_calc" post "/calc" do file = params["file"] sheet_name = params["sheet"] data = {} Calc.open(file) do |doc| sheet = doc.get_sheet_by_name(sheet_name) case params["command"] when "cell_get" data = cell_get(sheet, params) when "cell_set" cell_set(sheet, params) doc.save() when "dump" data = dump(sheet) else raise "unsupported command" end end content_type :json JSON.pretty_generate(data) end </code></pre> <h1 id="動かし方"><a href="#%E5%8B%95%E3%81%8B%E3%81%97%E6%96%B9">動かし方</a></h1> <p>イメージをビルド</p> <pre><code>docker build \ --build-arg USER=$USER \ --build-arg GROUP=$(id -gn) \ -t my:libo-jruby-webapi-2021 . </code></pre> <p>APIサーバ起動</p> <pre><code>./docker_run.sh ./jruby.sh app.rb -o 0.0.0.0 </code></pre> <h1 id="curl で動作確認"><a href="#curl+%E3%81%A7%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D">curl で動作確認</a></h1> <p>ファイル、シート名、コマンド、パラメータを POST で渡します。</p> <pre><code class="bash">curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=cell_set' \ -d 'col=0' -d 'row=2' -d "val=$(date "+%F_%T")" #=> {} curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=cell_get' \ -d 'col=0' -d 'row=2' #=> { "val": "2021-12-11_11:03:20" } curl -XPOST 'http://localhost:4567/calc' \ -d 'file=./sample.fods' -d 'sheet=Sheet1' \ -d 'command=dump' #=> { "rows": [ [ "(0, 0) 日本語テキスト 2021-01-02 14:34:26 +0900", "b1" ], [ "a2", "(1, 1) 2021-01-02 14:34:26 +0900" ], [ "2021-12-11_11:03:20", "12.34" ], [ "", "0" ] ] } </code></pre> <p>読み書きできてます。</p> <h1 id="Ruby + Faraday"><a href="#Ruby+%2B+Faraday">Ruby + Faraday</a></h1> <p>適当な HTTP クライアントライブラリを使って試してみました。</p> <pre><code class="ruby">require "faraday" puts "cell_set =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "cell_set", col: 0, row: 2, val: Time.now.to_s } ) puts res.body puts "cell_get =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "cell_get", col: 0, row: 2 } ) puts res.body puts "dump =>" res = Faraday.post( "http://localhost:4567/calc", { file: "./sample.fods", sheet: "Sheet1", command: "dump" } ) puts res.body </code></pre> <pre><code class="bash">$ bundle exec ruby sample_webapi_client.rb cell_set => { } cell_get => { "val": "2021-12-11 11:09:49 +0900" } dump => { "rows": [ [ "(0, 0) 日本語テキスト 2021-01-02 14:34:26 +0900", "b1" ], [ "a2", "(1, 1) 2021-01-02 14:34:26 +0900" ], [ "2021-12-11 11:09:49 +0900", "12.34" ], [ "", "0" ] ] } </code></pre> <p>大丈夫ですね。普通ですね。</p> <hr /> <p>というわけで、任意の HTTP クライアントからセルの読み書きができるようになりました。この記事は以上です。</p> sonota486 tag:crieit.net,2005:PublicArticle/16752 2021-03-16T04:50:11+09:00 2021-03-24T11:42:44+09:00 https://crieit.net/posts/LibreOffice-Draw-odg-604fba73edad8 LibreOffice Drawのodgファイルから図形の情報を抜き出して使う <p>これは <a target="_blank" rel="nofollow noopener" href="https://adventar.org/calendars/4230">LibreOffice Advent Calendar 2019</a> の 3日目の記事です!<br /> (※ <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/03/003517">2019-12-03に書いた記事</a>のクロス投稿です)</p> <h1 id="TL;DR"><a href="#TL%3BDR">TL;DR</a></h1> <ul> <li>プログラムに入力として与えるデータの編集をどうするか問題</li> <li>位置情報などはテキストで管理すると直感的に修正できなくて辛い</li> <li>LibreOffice Draw で編集して odg ファイルから情報を抜き出して使う方法を試してみた</li> </ul> <h1 id="動機"><a href="#%E5%8B%95%E6%A9%9F">動機</a></h1> <ul> <li>プログラムに入力として与えるデータを用意したい <ul> <li>ゲームのマップ、オブジェクトの配置など</li> <li>アルゴリズムや分析処理、作図ツールの検証に使うデータ</li> <li>etc.</li> </ul></li> <li>ちょっとしたものならプログラム内に直接書いたりテキストデータとして用意したり</li> <li>「ちょっとした」で済まなくなってくると辛い <ul> <li>位置情報</li> <li>構造が複雑</li> <li>データが多い</li> </ul></li> <li><p>どう辛いか</p> <ul> <li>直感的に編集できない</li> <li>一度2Dのグラフィックに変換しないと何がどうなっているのか分からない <ul> <li>配置、要素同士の位置関係、サイズ、オブジェクトの種類、属性、etc.</li> </ul></li> <li>編集→表示させて確認→編集… を繰り返さないといけなくて手数が増えてめんどくさい</li> </ul></li> <li><p>こういう場合、WYSIWYG なエディタが欲しくなる</p> <ul> <li>出来合いのツールがあればそれを使えばいいが、ない場合は……</li> <li>自作する?</li> <li>GUI自作は大変</li> <li>コピペ、D&D、アンドゥ/リドゥ、ズーム表示</li> <li>大変なので諦めてがんばりがち</li> <li>適当な可視化ツールだけ作ってお茶を濁したりしがち</li> <li>エディタがあれば作業効率上がるはずなのに……コストが見合わない</li> <li>特にすばやくプロトタイプを作りたい場合、手間をかけずにサッと使いたい</li> </ul></li> </ul> <p>そこで、LibreOffice Draw を汎用エディタとして使えないか? と考えました。</p> <h1 id="矩形"><a href="#%E7%9F%A9%E5%BD%A2">矩形</a></h1> <p>さっそくやってみましょう。まずは基本ということで、矩形の位置とサイズを odg ファイルから抜き出してみます。</p> <p>※ odg ファイルと書いてますが、以下では Flat XML な fodg ファイルを使います。odg でもだいたい同じだと思います。</p> <p>Draw でこんな図形を描きます。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20191201/20191201055353.png" alt="image" /></p> <p>fodgファイルの大まかな構造はこうなっています。</p> <pre><code class="xml"><office:document> <!-- メタデータ、スタイルの設定など --> <office:body> <office:drawing> <draw:page draw:name="page1" ... > ここに図形の記述が並ぶ </draw:page> <draw:page draw:name="page2" ... > ここに図形の記述が並ぶ </draw:page> ... </code></pre> <p>fodg ファイルには複数ページのデータが含まれていますが、今回は 1ページ目だけを使い、2ページ目以降は無視します。</p> <p>「ここに図形の記述が並ぶ」の部分を見てみましょう。</p> <pre><code class="xml"><draw:custom-shape draw:style-name="gr1" draw:text-style-name="P1" draw:layer="layout" svg:width="9.5cm" svg:height="3.8cm" svg:x="1.9cm" svg:y="2.9cm" > <text:p text:style-name="P1">box1<text:line-break/>aa</text:p> <text:p text:style-name="P1"/> <text:p text:style-name="P1">bb</text:p> <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N" /> </draw:custom-shape> <draw:custom-shape draw:style-name="gr2" draw:text-style-name="P1" draw:layer="layout" svg:width="2.5cm" svg:height="7.1cm" svg:x="13.2cm" svg:y="1.7cm" > <text:p text:style-name="P1">box2</text:p> <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N" /> </draw:custom-shape> </code></pre> <p><code>draw:type="rectangle"</code> の部分を見ることで矩形であることが判別でき、<code>svg:width</code>, <code>svg:height</code>, <code>svg:x</code>, <code>svg:y</code> の部分から位置とサイズが抽出できそうですね。あとテキストも取れそうです。</p> <hr /> <p>Ruby と、標準ライブラリ REXML を使ってスクリプトを書きます(Ruby に馴染みのない方のためにここだけ return を省略せずに書いています)。</p> <pre><code class="ruby"># coding: utf-8 require "rexml/document" def xpath_match(el, xpath) return REXML::XPath.match(el, xpath) end def extract_pages(doc) return xpath_match(doc, "//draw:page") end def extract_rectangles(page) custom_shape_els = xpath_match(page, "draw:custom-shape") rect_els = custom_shape_els.select { |el| geo_el = xpath_match(el, "draw:enhanced-geometry")[0] geo_el["draw:type"] == "rectangle" } return rect_els end # 手抜き実装。改行が失われます。 def extract_text(el) texts = [] el.each_element_with_text { |el| texts << el.texts.join(" ") } return texts.join(" ") end def print_rectangle(rect_el) print "x=" , rect_el["svg:x"] print ", y=" , rect_el["svg:y"] print ", width=" , rect_el["svg:width"] print ", height=", rect_el["svg:height"] print ", text=" , extract_text(rect_el) print "\n" end # -------------------------------- xml = File.read("sample_rectangle.fodg") doc = REXML::Document.new(xml) pages = extract_pages(doc) rect_els = extract_rectangles(pages[0]) rect_els.each { |rect_el| print_rectangle(rect_el) } </code></pre> <p>実行結果:</p> <pre><code>$ ruby extract_rectangles.rb x=1.9cm, y=2.9cm, width=9.5cm, height=3.8cm, text=box1 aa bb x=13.2cm, y=1.7cm, width=2.5cm, height=7.1cm, text=box2 </code></pre> <p>抽出できました! <code>x</code>, <code>y</code> はページ左端、上端の余白を含めた値になっているようです。</p> <h1 id="コネクタ"><a href="#%E3%82%B3%E3%83%8D%E3%82%AF%E3%82%BF">コネクタ</a></h1> <p>次の例としてコネクタです。</p> <p>Draw でこんな図を描きます。</p> <p><a href="https://crieit.now.sh/upload_images/0dee45e09dfcf9dac9478e1fb86430f6604fc4eed3ad0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0dee45e09dfcf9dac9478e1fb86430f6604fc4eed3ad0.png?mw=700" alt="image" /></a></p> <p>ここからこういう情報が抜き出せればOK。</p> <pre><code>box1 => box3 box2 => box3 box3 => box4 </code></pre> <p>XML を見るとこんな感じです。 コネクタが繋がっている場合は矩形要素に id が振られます。</p> <pre><code class="xml"><draw:custom-shape draw:style-name="gr1" draw:text-style-name="P1" xml:id="id2" draw:id="id2" draw:layer="layout" svg:width="2.6cm" svg:height="5.7cm" svg:x="9.9cm" svg:y="1.8cm" > <text:p text:style-name="P1">box3</text:p> <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:type="rectangle" draw:enhanced-path="M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z N"/> </draw:custom-shape> <draw:connector draw:style-name="gr2" draw:text-style-name="P2" draw:layer="layout" draw:type="curve" svg:x1="6.6cm" svg:y1="2.55cm" svg:x2="9.9cm" svg:y2="4.65cm" draw:start-shape="id1" draw:start-glue-point="1" draw:end-shape="id2" svg:d="M6600 2550c2475 0 825 2100 3300 2100" svg:viewBox="0 0 3301 2101" > <text:p/> </draw:connector> ... </code></pre> <p>やってみます。同様の記述が多くなるのでコードは gist に貼りました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/sonota88/4a2221def064e675cabfce1a9266d48f#file-extract_connectors-rb">https://gist.github.com/sonota88/4a2221def064e675cabfce1a9266d48f#file-extract_connectors-rb</a></p> <p>実行結果:</p> <pre><code>$ ruby extract_connectors.rb (id1) box1 => (id2) box3 (id3) box2 => (id2) box3 (id2) box3 => (id4) box4 </code></pre> <p>いけますね。</p> <h1 id="応用編"><a href="#%E5%BF%9C%E7%94%A8%E7%B7%A8">応用編</a></h1> <p>コネクタを同じ箇所に複数つなげるとこのような見た目になります。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20191201/20191201062408.png" alt="image" /></p> <p>これ、矢印が重なると分かりにくいんですよね。 この例でいえば、上から3番目のコネクタは両方向の矢印なのかな? とか、矢印が両方ともないコネクタもあるのかな? とか。</p> <p>このように矢印がはっきり見えないと困るときや、コネクタの接続箇所の位置を調整したいとき、私はよくこういう描き方をします。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20191201/20191201062442.png" alt="image" /></p> <p>ちなみに、まとめて選択すれば一緒に移動できます。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20191201/20191201062750.png" alt="image" /></p> <p>この描き方を使ってさっきのコネクタの図を描き直してみました。今度はこの図から依存関係を抜き出してみましょう。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20191111/20191111072708.png" alt="image" /></p> <p>こういうのが抜き出せればOK。上のコネクタの例と同じですね。</p> <pre><code>box1 => box3 box2 => box3 box3 => box4 </code></pre> <p>この場合は単に抜き出すだけではなく、加工が必要です。</p> <p>詳しくはコードを見ていただくとして、考え方としては</p> <ul> <li>矩形の重なりを判定して、どの矩形がどの矩形と繋がっているかを調べる</li> <li>コネクタがテキストなし矩形に繋がっている場合は、そこから辿ってテキストあり矩形を探す</li> </ul> <p>みたいな感じですね。</p> <p><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/sonota88/4a2221def064e675cabfce1a9266d48f#file-extract_connectors_2-rb">https://gist.github.com/sonota88/4a2221def064e675cabfce1a9266d48f#file-extract_connectors_2-rb</a></p> <pre><code>$ ruby extract_connectors_2.rb (id1) box1 => () box3 (id3) box2 => () box3 () box3 => (id6) box4 </code></pre> <p>いいですね。</p> <hr /> <p>もっとそれっぽい例で試してみましょう。達人プログラマー(ピアソン・エデュケーション版 p156)に載っている、ピニャ・コラーダの作り方を記述したアクティビティ図(UML の一種)です。</p> <p><a href="https://crieit.now.sh/upload_images/7766d32eba8abfbd640304af2634b8de604fba31105cc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7766d32eba8abfbd640304af2634b8de604fba31105cc.png?mw=700" alt="image" /></a></p> <p>要素は増えてますが、さっきの例と同じルールで描いているので、さっきのスクリプトで同じように抽出できるはず!</p> <p>ここから抜き出した結果が下記です。</p> <pre><code>(id1) 2_ミックスを開ける => () join1 (id3) 1_ブレンダーを開ける => () join1 () join4 => (id6) 12_サーブする (id3) 1_ブレンダーを開ける => (id7) 6_氷を2カップ入れる (id8) 11_ピンクの傘を用意する => () join4 (id7) 6_氷を2カップ入れる => () join3 () join1 => (id12) 3_ミックスを入れる (id12) 3_ミックスを入れる => () join3 (id14) 4_ラムを計る => () join2 (id16) 10_グラスの用意をする => () join4 (id18) 9_ブレンダーを開ける => () join4 (id20) 5_ラムを入れる => () join3 (id3) 1_ブレンダーを開ける => () join2 () join2 => (id20) 5_ラムを入れる (id24) 8_かき混ぜる => (id18) 9_ブレンダーを開ける (id25) 7_ブレンダーを閉める => (id24) 8_かき混ぜる () join3 => (id25) 7_ブレンダーを閉める </code></pre> <p>アクティビティ図からタスクの依存関係を抜き出すツールができていました。ちょろい!</p> <hr /> <p>というわけで、矩形とコネクタの情報を抜き出す例を紹介しました。 自分がよく使う図形と用途に合わせたやり方を把握しておくと低コストで汎用エディタが用意できそうですね(これをもっと早く思いついていればなあ〜)。</p> <p>今回は矩形とコネクタだけを扱いましたが、線や円など他の図形を使ったり、レイヤーやスタイルの情報も利用するとさまざまな活用ができそうです。</p> <h1 id="その他の図形"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E5%9B%B3%E5%BD%A2">その他の図形</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/02/08/113757">LibreOffice Drawのファイルから図形の情報を抜き出す(直線)</a></li> </ul> <h1 id="(追記 2020-05-09) 例: 回路図エディタ"><a href="#%EF%BC%88%E8%BF%BD%E8%A8%98+2020-05-09%EF%BC%89+%E4%BE%8B%3A+%E5%9B%9E%E8%B7%AF%E5%9B%B3%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BF">(追記 2020-05-09) 例: 回路図エディタ</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/03/132253">リレー式論理回路シミュレータを自作して1bit CPUまで動かした</a></p> <p>そうそう、こういうのがやりたかったんですよ、という具体例。こういうの作りたいなーと思った時にサッと作れるようにしたかったのです。</p> <p><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/s/sonota88/20200411/20200411105424.png" alt="image" /></p> <p>こういう回路図を Draw で描いて、自作の論理回路シミュレータで読み込んで動かしてみました。 見ての通りですが、使っているのは直線、矩形、矩形内のテキストだけです。</p> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <p>せっかくのアドベントカレンダーですのでいくつか宣伝ぽく LibreOffice 関連記事へのリンクを貼ってみます。</p> <ul> <li>2019-12-02 <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/02/070925">JavaでLibreOffice Calcのfodsファイルを読み書きするサンプル 2019</a></li> <li>2019-12-02 <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/02/230545">JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2019</a></li> <li>2019-11-03 <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/11/03/090019">LibreOffice Calcの入ったDockerイメージを作ってヘッドレスで動かす</a> <ul> <li>Docker べんり</li> </ul></li> <li>2014-12-16 <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20141216/1418742273">JavaScript(jrunscript/Rhino)でodsファイルからMySQLにデータ投入</a> <ul> <li>5年前の LibreOffice アドベントカレンダー向けに書いたもの</li> </ul></li> </ul> <h1 id="(追記 2019-12-07)テキスト抽出の改良"><a href="#%EF%BC%88%E8%BF%BD%E8%A8%98+2019-12-07%EF%BC%89%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E6%8A%BD%E5%87%BA%E3%81%AE%E6%94%B9%E8%89%AF">(追記 2019-12-07)テキスト抽出の改良</a></h1> <p>図形内のテキストを文字列の配列として返すメソッドを書いてみました。改行( <code>text:line-break</code> 要素)を LF に変換して段落を一つの文字列にしています。</p> <p><code>["box1\naa", "", "bb"]</code> のような配列を返すので、全部繋げて一つの文字列にしたい場合は <code>extract_paragraphs(el).join("\n")</code> のように使えばよいかと。</p> <pre><code class="ruby">def extract_paragraphs(el) para_els = xpath_match(el, "text:p") para_els.map { |para_el| para_el.children .map { |child_el| case child_el when REXML::Text child_el.value when REXML::Element if child_el.name == "line-break" "\n" else raise "unknown element" end else raise "unknown element" end } .join("") } end </code></pre> sonota486 tag:crieit.net,2005:PublicArticle/16693 2021-02-20T11:51:56+09:00 2021-02-20T11:51:56+09:00 https://crieit.net/posts/JRuby-LibreOffice-Calc-fods-2019 JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2019 <p>(2021-01-02) 2021年版書きました: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/02/171445">JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a></p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20140726/1406375184">以前 JavaScript(Rhino/jrunscript)で書いた</a>ものを今さらながら Nashorn 向けに書きなおそうとして調べたところ、非推奨になっていました。</p> <p>2018-06-07 <a target="_blank" rel="nofollow noopener" href="https://www.publickey1.jp/blog/18/javajavascriptnashornecmascriptgraalvm.html">JavaでJavaScriptを実行する「Nashorn」が非推奨に、ECMAScriptの速い進化に追いつけないと。代替案はGraalVM - Publickey</a></p> <p>去年のニュースですね。全然気づいてませんでした。GraalVM を使えとあり、それも面白そうではありますが時期尚早な感じもします。ちょっと考えて JRuby で書き直してみることにしました。</p> <p>(※ <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/02/230545">2019-12-02 に書いた記事</a>のクロスポストです)</p> <hr /> <p>sonota88/libreoffice-jruby-sample (tag: 20191202)<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/libreoffice-jruby-sample/tree/20191202">https://github.com/sonota88/libreoffice-jruby-sample/tree/20191202</a></p> <p>Ubuntu で動かす前提のサンプルになっていて、 <code>libreoffice-java-common</code> をインストールしておく必要があります。ライブラリまわりについては<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/02/070925">一つ前の記事</a>なども参考にしてください。Windows などでもライブラリのパスの修正だけで動くんじゃないかと思います。</p> <p>なんかデッドロックが発生してプログラムが終了しなかったのでサンプルスクリプトでは明示的に <code>exit</code> しています。jstack を使ってデッドロックしているなーというとこまで調べたあたりで気力が尽きました。また気が向いたら調べるかも……。</p> <hr /> <p>今回はじめて JRuby を使ってみましたが、 zip をダウンロードして展開して <code>bin/</code> にパスを通すだけで使えて、いいですね。分かりやすい。</p> <p>JRuby から Java のライブラリなどを使う場合、下記は必読でした。まずこれを読みましょう。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby">CallingJavaFromJRuby · jruby/jruby Wiki</a></p> <hr /> <p>次のようにシートをダンプしてくれる <code>dump.rb</code> もおまけで追加しました。値が入っている最大の行・列の取り方が分からなかったため、いったん100行・100列まで見るようにしました。サンプルということで許してください……。</p> <pre><code>$ jruby dump.rb foo.fods {シート名} ["a1", "b1"] ["a2", "b2"] </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/04/20/101713">もっとお手軽な機械可読テキストテーブルフォーマット - memo88</a></p> sonota486 tag:crieit.net,2005:PublicArticle/16668 2021-02-02T05:14:55+09:00 2021-02-02T05:15:39+09:00 https://crieit.net/posts/Java-LibreOffice-Calc-fods-2019 JavaでLibreOffice Calcのfodsファイルを読み書きするサンプル 2019 <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20140726/1406375184">5年前に JavaScript で書いたもの</a> を大体そのまま Java に書き直しただけです。例外のハンドリングは適当です。今では推奨されない古い書き方が残ってたりするかもしれません。</p> <p>sonota88/libreoffice-java-sample at 20191202<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/libreoffice-java-sample/tree/20191202">https://github.com/sonota88/libreoffice-java-sample/tree/20191202</a></p> <p>処理の内容的には fods ファイルを開いてセルの内容の最低限の読み書きするというもの。</p> <p>(※ <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/12/02/070925">2019-12-02 に書いた記事</a>のクロス投稿です)<br /> (※ 2021-01-01 追記: 2021年版書きました → <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/01/155345">JavaでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a>)</p> <hr /> <p>以下は今回調べたりしたことのメモです。開発環境は Ubuntu 18.04。</p> <h1 id="jar の設定を pom.xml に書く"><a href="#jar+%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92+pom.xml+%E3%81%AB%E6%9B%B8%E3%81%8F">jar の設定を pom.xml に書く</a></h1> <p>Java から LibreOffice の API を使う場合、 SDK をインストールして、それに付いてくる jar を使う、というのが普通のやり方だったと思います。たしか。</p> <p>Eclipse の場合は</p> <ul> <li>プロジェクトのプロパティ</li> <li>Java Build Path>「Libraries」タブ>Add External JARs...</li> </ul> <p>から追加します。ここで jar を追加すると、プロジェクトの <code>.classpath</code> ファイルに</p> <pre><code class="xml"><classpathentry kind="lib" path="/usr/lib/libreoffice/program/classes/juh.jar"/> </code></pre> <p>このような設定が追加されます。</p> <p>これだと Eclipse 用の設定になってしまうので、<code>pom.xml</code> に書けないんだっけと思って調べたところ、下記のように system スコープで dependency を書けばよいようでした。<br /> (groupId, artifactId、バージョンは適当です)</p> <pre><code class="xml"><dependency> <groupId>juh-g</groupId> <artifactId>juh-a</artifactId> <version>0.0.1</version> <scope>system</scope> <systemPath>/usr/lib/libreoffice/program/classes/juh.jar</systemPath> </dependency> </code></pre> <p>参考:</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Dependency_Scope">Maven – Introduction to the Dependency Mechanism: Dependency Scope</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.techscore.com/tech/Java/ApacheJakarta/Maven/3/#maven-3-1-1">3. Maven 入門 (2) | TECHSCORE(テックスコア): 3.1.1. 依存性の指定とスコープ</a></li> </ul> <p>ただ、 jar はこれでいけるんですが、共有ライブラリ <code>libjpipe.so</code> は <code>pom.xml</code> で設定できるか分からず、これだけ Eclipse 側で設定しました。<br /> (Eclipse 上でユニットテストなどで実行するときに必要で、プログラム書いてコンパイルするだけなら不要っぽいです)</p> <p>関連: <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20140723/1406123992">(solved) Exception in thread "main" java.lang.UnsatisfiedLinkError: no jpipe in java.library.path</a></p> <h1 id="Ubuntu 18.04 でのパッケージまわりのメモ"><a href="#Ubuntu+18.04+%E3%81%A7%E3%81%AE%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%BE%E3%82%8F%E3%82%8A%E3%81%AE%E3%83%A1%E3%83%A2">Ubuntu 18.04 でのパッケージまわりのメモ</a></h1> <pre><code>libreoffice-java-common ... unoil を含む libreoffice-common ... ure に依存 ure ... juh, jurt, ridl, jpipe を含む dpkg や apt-cache コマンドで調べられます: パッケージに含まれるファイル一覧 dpkg -L {パッケージ名} パッケージの依存関係 apt-cache depends {パッケージ名} apt-cache rdepends {パッケージ名} </code></pre> <p>ところで <code>libreoffice-dev</code> というパッケージもありますがこれって何でしたっけ?</p> <pre><code>$ apt-cache depends libreoffice-dev libreoffice-dev Depends: libreoffice-core Depends: libreoffice-dev-common Depends: ucpp Depends: libc6 Depends: libgcc1 Depends: libstdc++6 Depends: libx11-6 Depends: uno-libs3 Depends: ure Conflicts: libreoffice Conflicts: libreoffice-dev-doc Breaks: libreoffice-dev-common Recommends: g++ Recommends: libreoffice-java-common |Recommends: default-jre |Recommends: <sun-java6-jre> |Recommends: <java6-runtime> default-jre openjdk-11-jre openjdk-8-jre Recommends: <jre> Suggests: libmythes-dev Suggests: libreoffice-dev-doc Suggests: libreofficekit-dev Replaces: libreoffice-dev-common </code></pre> <p>なるほど。パッケージの説明は</p> <blockquote> <p>office productivity suite -- SDK -- architecture-dependent parts</p> </blockquote> <p>となっています。</p> <h1 id="Maven のリポジトリにある jar を使う"><a href="#Maven+%E3%81%AE%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AB%E3%81%82%E3%82%8B+jar+%E3%82%92%E4%BD%BF%E3%81%86">Maven のリポジトリにある jar を使う</a></h1> <p>共有ライブラリ libjpipe.so を除くと、他は jar を使っているだけといえばだけです(たぶん)。それなら、ひょっとして Maven のリポジトリから取ってきて普通の Maven プロジェクトっぽくできたりしないでしょうか?</p> <p>探したら Maven のセントラルリポジトリにありました。</p> <p>"org.libreoffice" の検索結果:<br /> <a target="_blank" rel="nofollow noopener" href="https://search.maven.org/search?q=org.libreoffice">https://search.maven.org/search?q=org.libreoffice</a></p> <p>これを使えば、 <code>libreoffice-java-common</code> をインストールしなくても必要な jar を Maven の流儀に従って使えばよく、より普通の Maven プロジェクトっぽく扱えて嬉しいような。</p> <p><code>pom.xml</code> に普通にこんな感じで書けばよいと。普通ですね。いいですね。</p> <pre><code class="xml"> <dependency> <groupId>org.libreoffice</groupId> <artifactId>ridl</artifactId> <version>6.3.2</version> </dependency> </code></pre> <p>ふむふむ、いいじゃない、となったのですが、この方法だと 5年前のこれと同じところで引っかかるのです……。</p> <p>(solved) com.sun.star.comp.helper.BootstrapException: no office executable found!<br /> <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/20140721/1405966864">https://memo88.hatenablog.com/entry/20140721/1405966864</a></p> <p>上記の記事から5年経ちましたが、 <code>Bootstrap</code> クラスが含まれている juh.jar の位置を起点にして実行ファイル soffice を探す部分は変わっていないようで、今回のサンプルでは Maven のリポジトリを利用する方向は見送りました。</p> <p>Maven でライブラリ取ってくると <code>~/.m2/</code> 以下に jar が入ったり、fat jar 作ったらその中に入ったりするので、そこから相対パスで探しても soffice が見つけられないんですよね……。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/LibreOffice/core/blob/libreoffice-6-4/javaunohelper/com/sun/star/comp/helper/Bootstrap.java">本体のコード(Bootstrap.java)</a> を借用&修正して使っても動きましたが、その場合は公開の際に本体のライセンスに従う必要があるでしょう。</p> <h1 id="Docker コンテナで実行する"><a href="#Docker+%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%81%A7%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B">Docker コンテナで実行する</a></h1> <p>先日 <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/11/03/090019">LibreOffice 本体だけ Docker で動かすメモ</a><br /> を書きましたが、ついでに SDK もイメージに入れておけば便利かも? と思いついて、これも試してみました。</p> <p>以下の3つのパッケージを入れておけば今回のサンプルは動きました。</p> <ul> <li><code>libreoffice-calc</code></li> <li><code>libreoffice-java-common</code></li> <li><code>openjdk-8-jre</code></li> </ul> <p>あとはコンテナ内で</p> <pre><code>java -cp "{ライブラリのパス}:{ビルドしたjarのパス}" \ sample.Main {残りの引数} </code></pre> <p>で実行できます。 詳しくは<a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/libreoffice-java-sample/tree/20191202">リポジトリ</a>に入っているスクリプト <code>run.sh</code> と <code>Dockerfile</code> を見てください。</p> sonota486 tag:crieit.net,2005:PublicArticle/16642 2021-01-21T04:45:48+09:00 2021-01-21T04:45:48+09:00 https://crieit.net/posts/LibreOffice-Calc-Docker LibreOffice Calcの入ったDockerイメージを作ってヘッドレスで動かす <p>とりあえず自分が使いやすいようにポータブルにしておくとよいかも? くらいの思いつきでやってみましたが、たとえば開発チームのメンバーにちょっとしたツールを渡したいとき(そのためだけに LibreOffice をインストールしてもらわなくて済む)とか、サーバの環境を汚さずに使いたいときに都合が良いかもしれませんね。</p> <p>(※ 2019-11-03 に書いた記事のクロス投稿です)</p> <hr /> <p>下記では例として Calc が入ったイメージを作って fods ファイルを ods ファイルに変換します。Writer とかでも同じようにできるのではないでしょうか。</p> <hr /> <p>Dockerfile 書く。関連しそうなパッケージはいくつかあるのですが、とりあえず <code>libreoffice-calc</code> をインストールすれば変換処理は動きました。</p> <pre><code class="sh">FROM ubuntu:18.04 RUN apt-get update \ && apt-get -y install --no-install-recommends \ libreoffice-calc \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* </code></pre> <p>イメージをビルド。</p> <pre><code>docker build -t libo_calc:test . </code></pre> <p>イメージのサイズは 426MB になりました。</p> <hr /> <p>コンテナ内で実行するシェルスクリプト <code>container_fods2ods.sh</code> を用意。汎用化は置いておいてひとまずベタ書きです。</p> <p>あと、動作する最低限のサンプルということで <code>--headless</code> だけ付けていますが、他にも <code>--nologo</code> などのオプションがあります。詳しくは <a target="_blank" rel="nofollow noopener" href="https://qiita.com/hirohiro77/items/942eb461e8f4727e4b38">LibreOfficeでドキュメントコンバータを作ろう - Qiita</a> を参照してください。</p> <pre><code class="sh">temp_fods=/tmp/temp.fods temp_ods=/tmp/temp.ods # 標準入力から受け取る cat > $temp_fods cd /tmp soffice \ --headless \ --convert-to ods \ $temp_fods \ >&2 # =&gt; /tmp/temp.ods に出力される cat $temp_ods </code></pre> <p>下記のようなメッセージが標準出力に出て都合が悪いので <code>>&2</code> で標準エラー出力にリダイレクトしています。Java の実行環境がないよと言われてますが、今やろうとしている fods → ods の変換に関しては問題ないようなのでいったん無視。</p> <pre><code>javaldx: Could not find a Java Runtime Environment! Please ensure that a JVM and the package libreoffice-java-common is installed. If it is already installed then try removing ~/.libreoffice/3/user/config/javasettings_Linux_*.xml Warning: failed to read path from javaldx convert /tmp/temp.fods -> /tmp/temp.ods using filter : calc8 </code></pre> <hr /> <p>ホスト側でコマンドとして使うシェルスクリプト <code>fods2ods.sh</code> を用意。</p> <pre><code class="sh">#!/bin/bash file_in="$1"; shift file_out="$1"; shift cat $file_in \ | docker run --rm -i \ -v "$(pwd):/root/work/" \ libo_calc:test \ bash /root/work/container_fods2ods.sh \ > $file_out </code></pre> <hr /> <p>実行。</p> <pre><code class="sh">chmod u+x fods2ods.sh ./fods2ods.sh sample.fods sample_output.ods </code></pre> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li>2019-08-29 <a target="_blank" rel="nofollow noopener" href="https://qiita.com/hirohiro77/items/942eb461e8f4727e4b38">LibreOfficeでドキュメントコンバータを作ろう - Qiita</a> <ul> <li><code>--convert-to</code> で指定するフィルタの調べ方</li> </ul></li> </ul> <hr /> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/12/25/211011">DockerでLibreOffice Basicマクロを実行する</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/01/155345">JavaでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/02/171445">JRubyでLibreOffice Calcのfodsファイルを読み書きするサンプル 2021</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/archive/category/LibreOffice">その他(LibreOffice カテゴリーの記事一覧)</a></li> </ul> sonota486