目次
解凍~minizip
docxの正体はxmlファイル群をzipアーカイブしたもの.
まずはunzipをインストールして、これをrubyにラッパーしたライブラリminizipを使う
1 2 | sudo apt-get install unzip gem install minizip |
minizipの使い方はこんな感じで
1 2 3 | require 'minizip' Minizip::Zip.extract('./hello.docx', directory: './tmp') |
解凍後のツリー構造は下のような感じ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ tree tmp tmp ├── [Content_Types].xml ├── _rels ├── docProps │ ├── app.xml │ └── core.xml └── word ├── _rels │ └── document.xml.rels ├── document.xml ├── fontTable.xml ├── settings.xml ├── styles.xml ├── stylesWithEffects.xml ├── theme │ └── theme1.xml └── webSettings.xml |
基本的には[Contents_Type].xmlで使うコンテンツのタイプを指定して、_relファイルでファイル間の関連性を指定したりするとかあるらしいんだけど、本文はdocument.xmlに入ってるのでとりあえずはここだけ見ればいい.
ファイルを開く~File.open
rubyでファイルを開くのは標準のFileを使う
こんな感じで
1 2 3 4 5 6 7 8 | file = open('./tmp/word/document.xml') doc = '' file.each do |line| p line end file.close |
出力結果はこんな感じ
1 2 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><w:body><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr><w:t><</w:t></w:r><w:r><w:rPr></w:rPr><w:t>%= @hello %</w:t></w:r><w:r><w:rPr></w:rPr><w:t>></w:t></w:r></w:p><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr></w:r></w:p><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr><w:t>はろー>わーるど</w:t></w:r></w:p><w:sectPr><w:type w:val="nextPage"/><w:pgSz w:h="16838" w:w="11906"/><w:pgMar w:bottom="1701" w:footer="0" w:gutter="0" w:header="0" w:left="1701" w:right="1701" w:top="1985"/><w:pgNumType w:fmt="decimal"/><w:formProt w:val="false"/><w:textDirection w:val="lrTb"/><w:docGrid w:charSpace="6143" w:linePitch="360" w:type="lines"/></w:sectPr></w:body></w:document> |
document.xmlは2行のxmlファイルで1行目はxml宣言だけだからいらない.
全ファイルを解凍せずにzip内の必要なファイルだけを開く~zipruby
もっと高度なことができるziprubyというgemを使う
bitbucket.org/winebarrel/zip-ruby/wiki/Home
1 2 3 4 5 6 7 | require 'zipruby' zip = Zip::Archive.open('new_file.docx') document_file = zip.fopen('word/document.xml') document_xml = document_file.read p document_xml |
ファイルの保存は次のような感じで
1 2 3 | Zip::Archive.open('new_file.docx') do |ar| ar.replace_buffer('word/document.xml', document_xml.to_xml) end |
置換~gsub
簡単に%= @hello %の部分を適当に置き換えてみる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | f = open('./tmp/word/document.xml') doc = '' f.each do |line| doc += line end f.close @hello = "HELLOOOOOOOOOOOOOOOOO" new_doc = doc.gsub(/%= @hello %/, @hello) f = open('./tmp/word/document.xml', 'w') f.write(new_doc) f.close p new_doc ~ |
出力結果
1 2 | ruby mydocx3.rb "<?xml version="1.0" encoding="UTF-8" standalone="yes"?>n<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"><w:body><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr><w:t>{</w:t></w:r><w:r><w:rPr></w:rPr><w:t>HELLOOOOOOOOOOOOOOOOO</w:t></w:r><w:r><w:rPr></w:rPr><w:t>}</w:t></w:r></w:p><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr></w:r></w:p><w:p><w:pPr><w:pStyle w:val="style0"/></w:pPr><w:r><w:rPr></w:rPr><w:t>はろーわーるど</w:t></w:r></w:p><w:sectPr><w:type w:val="nextPage"/><w:pgSz w:h="16838" w:w="11906"/><w:pgMar w:bottom="1701" w:footer="0" w:gutter="0" w:header="0" w:left="1701" w:right="1701" w:top="1985"/><w:pgNumType w:fmt="decimal"/><w:formProt w:val="false"/><w:textDirection w:val="lrTb"/><w:docGrid w:charSpace="12082" w:linePitch="360" w:type="lines"/></w:sectPr></w:body></w:document>" |
あとはこれをzipで固めれば
うん、できるな
docxのxmlの仕組みは下記の本を参照しましたが、情報が古いのでうまくいかない部分もあります.
カスタムマークアップ機能は特許訴訟でマイクロソフトが負けて削除したみたい.
xmlの操作~nokogiri
*saxとdomに関して
www.atmarkit.co.jp/fxml/rensai/rexml10/rexml10.html
nokogiriを使う
1 2 3 4 5 6 7 8 | require 'zipruby' require 'nokogiri' zip = Zip::Archive.open('new_file.docx') document_file = zip.fopen('word/document.xml') document_xml = Nokogiri::XML document_file.read p document_xml |
xmlビューアーとしてIEが使えることに気づいた
これをみるとルートの子どもに<body>タグがあって、その子どもに<p>タグが3つと<sectPr>タグがあることがわかる
上の参考書によると
- <p>タグはパラグラフを定義して、1つまたは複数のラン<r>で構成される.
- ランは1つまたは複数のテキスト<t>を定義する.
- テキストへ適用するプロパティが異なれば別のランになる.
- ちなみに、<sectPr>はセクションのプロパティタグで、これが出てくるとページ区切りになる.
- セクションプロパティではページの幅や高さなどのレイアウトを定義できる.
ということらしい.
まずは全文を走査してテキストの値とその場所を確認したい.
テキストを取り出すのは次のような感じで
1 2 3 4 5 6 7 8 9 10 11 12 13 | require 'zipruby' require 'nokogiri' zip = Zip::Archive.open('new_file.docx') document_file = zip.fopen('tmp/word/document.xml') document_xml = Nokogiri::XML document_file.read keys = [] document_xml.xpath("//w:t").each do |t_node| keys << t_node.content end p keys |
テキストの値の置換えはつぎのようにcontentで
1 | document_xml.xpath("//w:t")[2].content = "はろーわーるど" |
クラスを作成
構想としては、
- URIを与えてインスタンス化するとそのURIのdocxから変数一覧を取得する.
- 変数一覧を問い合わせできる.
- 変数に任意のStringをいれられる.
- 任意の名前のdocxを作成できる.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #encoding: utf-8 class MyDocx require 'zipruby' require 'nokogiri' def initialize(path_to_template) @keys_index = {} @filename = File.basename(path_to_template) @dir = File.dirname(path_to_template) zip = Zip::Archive.open(path_to_template) document_file = zip.fopen('word/document.xml') @document_xml = Nokogiri::XML document_file.read i = 0 @document_xml.xpath("//w:t").each do |t_node| @keys_index[t_node.content] = i i += 1 end end def set(key, value) num = @keys_index[key] unless num == nil @document_xml.xpath("//w:t")[num].content = value true else false end end def keys array = [] @keys_index.each do |key, value| array << key end array end def generate(filename = 'output_' + @filename) Zip::Archive.open(File.join(@dir, @filename)) do |ar1| Zip::Archive.open(File.join(@dir,filename), Zip::CREATE) do |ar2| ar2.update(ar1) ar2.replace_buffer('word/document.xml', @document_xml.to_xml) end end File.join @dir, filename end end mydocx = MyDocx.new('HelloWorld.docx') p mydocx.keys p mydocx.set("Hello world", "HELLLLOOOO WORLD") p mydocx.set("nonono", 'hello') p mydocx.generate |
うんできた
やること
- 変数の仕様決定
実はerbに任せようとしたら、まれに>とかが>とかになってしまうということが判明した
よく考えたら、変数とindex(テキストの場所)の関係は1対多だった.
ランの存在が鬼門すぎる.たまに文の途中でも区切られてしまう.特に日本語の文に変数を組み込んだとき.テンプレートを記述するときは、同じフォントになるように気を使わないといけない. - テーブルに対応
ほかにもヘッダー/フッターとかも - xlsxに対応
参考書によるとxlsxとpptxも同じ調子で操作できそうな予感 - パーシャルテンプレート