Word+XPathでソースコードを生成

ウェブロケッツマガジンに

めんどうな作業がわずか数秒に!新人デザイナーが 知らないと一生後悔するExcelを使ったHTML生成 WEB ROCKETS ウェブロケッツマガジン


と言う記事があって、それに対しての反応を見かけた



うんうん




うんう・・・・・?!


なるほどその手があったか(ぽむ



と言うわけで実践してみる。

違いは入力がWordファイルであるという点、生成するのがC言語の関数宣言/定義のひな形と言う点だ。

Wordファイルの解析をすれば、あとは出力がHTMLだろうが他言語のソースコードだろうがイッツオーケー。


背景としては、

面倒だからWordで作られた仕様書をソースコードにしたい!

XPathRubyで使うよ。


XPath概要

XMLの位置を指定するための構文。
例えば

<a>
    <b bb="xxx">TEXT1</b>
    <b bb="yyy">TEXT2</b>
</a>

のようなXMLがあった場合

TEXT1を指定するには

/child::a/child::b[1]/child::text()

の用にURIライクな記述で指定できる。
/から始まると絶対パスだが、相対パスも指定できる。

ノード名の先頭についているchildはパスの方向を表現している。
子ノードのこれを探せという意味。

省略構文というのがあって上記のTEXT1の指定は簡易に

/a/b[1]/text()

とできる。

属性の指定は@で、属性bbを取得するには

/a/b/@bb

と書く。
この場合述語(角括弧)を指定していないのでbb='xxx'とbb='yyy'の2つを指す。

例として

適当に作ったのはこんなドキュメント。

章立てされていて、テーブルで記述されている。


XPathで処理するにあたり、現在Wordで使われている.docx形式は展開すると文章の入ったdocument.xmlがそのまま解析に使える。

ただ、うちにあるマシンは.docxを出力できないOffice2003までしか入っていない:p 


だから今回は「ファイルを別名で保存」から.xmlで保存を選択する。

後の手順はほとんど同じ。

まずは対象の構造を知る事。

XMLファイルとして覗いてみよう。


一般的なドキュメントだと図や画像も含まれていることと思う。
今から行う作業の前にそれらを削除した別のファイルを用意するといい。


保存しただけのXMLファイルは整形されていないためビューアによっては非常に読みづらい。

いきなり作業環境がMacに移ったけど気にしないでください。


構造を調べるためにまずは整形。
xmllintを使う。


Windowsの場合はここからlibxml2-*.*.*.win32.zip(なるべく新しいの)をダウンロードして展開し、binフォルダ以下にあるxmllint.exeを使う。

Macの場合は最初から入っています。(たぶん)

コマンドプロンプトまたはターミナルを起動し
Windows

type Wordファイル.xml | xmllint.exe -format - > 整形済みXMLファイル.xml

Mac/Linux/Unix

cat Wordファイル.xml | xmllint -format - > 整形XMLファイル.xml

で整形したxmlファイルを作る。

整形されて読みやすくなったらどういうXPathの記述で解析するかを調べる。


必要ない要素を取り除くとこんなかんじ

<w:wordDocument ... >
  <w:body>
    <wx:sect>
      <wx:sub-section>
        <w:p>
          <w:r>
            <w:t>構造体</w:t>
          </w:r>
        </w:p>
        <wx:sub-section>
          <w:p>
            <w:r>
              <w:t>student_t</w:t>
            </w:r>
          </w:p>
           <w:tbl>
            <w:tr>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>名前</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t></w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>備考</w:t>
                  </w:r>
                </w:p>
              </w:tc>
            </w:tr>
            <w:tr>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>first_name</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>char*</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>名前</w:t>
                  </w:r>
                </w:p>
              </w:tc>
            </w:tr>
            <w:tr>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>last_name</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>char*</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>苗字</w:t>
                  </w:r>
                </w:p>
              </w:tc>
            </w:tr>
            <w:tr>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>numbar</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>int</w:t>
                  </w:r>
                </w:p>
              </w:tc>
              <w:tc>
                <w:p>
                  <w:r>
                    <w:t>出席番号</w:t>
                  </w:r>
                </w:p>
              </w:tc>
            </w:tr>
          </w:tbl>
        </wx:sub-section>
      </wx:sub-section>
      <wx:sub-section>
        <w:p>
          <w:r>
            <w:t>関数</w:t>
          </w:r>
        </w:p>
        <wx:sub-section>
          <w:p>
            <w:r>
              <w:t>get_number</w:t>


    ・・・・・・・



要約すると

<w:wordDocument ... ><w:body><wx:sect>
 ・・・
</wx:sect></w:body></w:wordDocument>

に括られた中に

<wx:sub-section>・・・</wx:sub-section>

の階層で章立てされている。

<w:p>・・・</w:p>

が一つの行

<w:r><w:t>・・・</w:t></w:r>

に文字列がある。

ちなみにタブは

<w:r><w:tab/></w:r>

であらわされる。

フォントが違っていると一つの行中でも複数の

<w:r><w:t>・・・</w:t></w:r>

に分けられることに注意。


テーブルは

<w:tbl><!-- テーブル開始 -->
    <w:tr><!-- 1行目 -->
        <w:tc>・・・</w:tc><!-- 1行目 1列目 -->
        <w:tc>・・・</w:tc><!-- 1行目 2列目 -->
    </w:tr>
    <w:tr><!-- 2行目 -->
        <w:tc>・・・</w:tc><!-- 2行目 1列目 -->
        <w:tc>・・・</w:tc><!-- 2行目 2列目 -->
    </w:tr>
</w:tbl>

といった感じ。

構造が分かれば

後はXPath式にしてREXMLでパース。

今回必要なのは

  • 構造体名 = 1.x章のタイトル
  • 構造体メンバ名 = 1.x章のテーブルの2行目以降の1列目
  • 構造体メンバ型 = 1.x章のテーブルの2行目以降の2列目
  • 構造体メンバ説明 = 1.x章のテーブルの2行目以降の3列目
  • 関数宣言 = 2.x章のテーブルの1行目の2列目
  • 関数引数と説明 = 2.x章のテーブルの2行目の2列目
  • 関数戻り値の説明 = 2.x章のテーブルの3行目の2列目
  • 関数の説明 = 2.x章のテーブルの4行目の2列目

それぞれをXPathで指定してテンプレート(ERB)で出力する。

#!/opt/local/bin/ruby

require 'rexml/document'
require 'erb'

#構造体用XPath
STRUCTURES_XPATH = '/w:wordDocument/w:body/wx:sect/wx:sub-section[1]/wx:sub-section'
STRUCTURE_NAME_XPATH = 'w:p/w:r/w:t/text()'
STRUCTURE_MEMBERS_ROW_XPATH = 'w:tbl/w:tr[position()>1]'
STRUCTURE_MEMBERS_COL_XPATH = 'w:tc'
STRUCTURE_MEMBERS_TEXT_XPATH = 'w:p/w:r/w:t/text()'

#関数用XPath
FUNCTIONS_XPATH = '/w:wordDocument/w:body/wx:sect/wx:sub-section[2]/wx:sub-section/w:tbl'
FUNCTION_ROWS_XPATH = 'w:tr'
FUNCTION_TEXT_XPATH = 'w:tc[2]/w:p/w:r/w:t/text()'

#構造体用テンプレート
STRUCTURE_TEMPLATE = <<"EOS"
<% structures.each do |st| %>
/**
 * 
 */
typedef struct <%= st[:name] %>_ {
<%-  st[:members][0..-1].each do |mbr| -%>
<%="\t"%><%=mbr[:type]%> <%=mbr[:name]%>;<%="\t"%>/**< <%=mbr[:description]%> */
<%-  end -%>
} <%= st[:name] %>;
<%- end -%>
EOS

#関数用テンプレート
FUNCTION_TEMPLATE = <<"EOS"
<%  functions.each do |fn| %>
/**
<%-     fn[:description].split("\n").each do |line| -%>
 * <%=line%>
<%-     end -%>
 * 
<%-     fn[:args].split("\n").each do |line| -%>
 * @param <%=line %>
<%-     end -%>
 * @return <%=fn[:return] %>
 */
<%= fn[:prototype] %> {
}
<%- end -%>
EOS


File.open ARGV[0], "rb" do |fr|

    doc = REXML::Document.new fr
    
    #構造体
    structures = REXML::XPath.match(doc, STRUCTURES_XPATH).map do |node|
        #構造体名
        structure = {}
        structure[:name] = REXML::XPath.first(node, STRUCTURE_NAME_XPATH)
        #メンバ
        structure[:members] = REXML::XPath.match(node, STRUCTURE_MEMBERS_ROW_XPATH).map do |row|
            member = {}
            member[:name], member[:type], member[:description] =
                REXML::XPath.match(row, STRUCTURE_MEMBERS_COL_XPATH).map do |col|
                    #分割対策
                    REXML::XPath.match(col, STRUCTURE_MEMBERS_TEXT_XPATH).join
                end
            member
        end
        structure
    end

    #関数
    functions = REXML::XPath.match(doc, FUNCTIONS_XPATH).map do |node|
        #関数
        function = {}
        function[:prototype], function[:args],
            function[:return], function[:description] = 
            REXML::XPath.match(node, FUNCTION_ROWS_XPATH).map do |row|
                #分割対策
                REXML::XPath.match(row, FUNCTION_TEXT_XPATH).join
            end
        function
    end
    


    File.open ARGV[1], "wb" do |fw|
        #構造体書き込み
        fw.puts ERB.new(STRUCTURE_TEMPLATE, nil, '-').result(binding)

        #関数書き込み
        fw.puts ERB.new(FUNCTION_TEMPLATE, nil, '-').result(binding)
    end
end

複数行の対応はいろいろしてないです。

結果

/**
 * 
 */
typedef struct student_t_ {
	char* first_name;	/**< 名前 */
	char* last_name;	/**< 苗字 */
	int numbar;	/**< 出席番号 */
} student_t;

/**
 * 出席番号を取得する
 * 
 * @param st :  student_t構造体
 * @return 出席番号
 */
int get_number(const struct student_t st) {
}


XPathの柔軟さを生かせばもっと細かく制御できる。
こんな感じでWordのドキュメントからいろんな物を生成できるね。


うあ値渡しになってる


(追記)
こんなのがw
僕たちプログラマーは、プログラミングに、Excelを使います!

MacOSXでlibdaemonのbuild

ダウンロード

libdaemonのサイト

http://0pointer.de/lennart/projects/libdaemon/

git cloneする

git clone git://git.0pointer.de/libdaemon

Opointer(オー)ではなく0pointer(ゼロ)であることに注意。

ビルド

ディレクトリに入って

$./bootstrap.sh

するんだけど、このままではエラー

./bootstrap.sh: line 57: libtoolize: command not found


MacOSXではlibtoolizeはglibtoolizeとして提供されている。
解決策としてはglibtoolizeのシンボリックリンクをlibtoolizeとして作成するか、
bootstrap.shを編集してglibtoolizeを使用するように書き換える。


コマンド周りをあまりいじりたくないので、後者を行う。

bootstrap.shの53行目

LIBTOOLIZE=libtoolize

をglibtoolizeにするだけの簡単なお仕事。


あとは

$./bootstrap.sh
$./configure
$make
($make install)

でビルドされる。

コメント欄が荒れているのを見て気持ち悪くなった理由

いっしょに仕事をしたいプログラマ 5つの特徴 - tagomorisのメモ置き場
http://d.hatena.ne.jp/tagomoris/20110608/1307502071

コメント欄荒れてる。
何故こんなにバージョン管理の話題でいい加減なことを言うと一気に荒れるのかについて考えてみた。


これは、それだけ綺麗なコードやバージョン管理によって「優秀なエンジニア」の仲間入りをしたい人が多いという事なんだと思う。
つまり、差を付けたい。


「一緒に仕事をしたい」という著者のタイトルには納得。でも、外野がそれを「優秀なエンジニア」の条件として持ち上げ過ぎかな。


もっと嫌な言い方をすると、高い問題解決能力や独創性を持たなくても争える土俵として利用されているんじゃないかな。


その参入のしきいの低さと勝敗(またはルール)のわかりやすさがここまで叩き合いの加熱する理由なのかもしれません。



熱くなるのもいいけど、
綺麗なコードやバージョン管理を徹底するのはリーダーのお仕事というのも忘れちゃだめだよね。

javascriptでつくられたHaskellインタプリタ

なんぞこれw

http://github.com/johang88/haskellinjavascript

こいつはホントにパーサだったけど、
こんなのがあるから↓
404 Blog Not Found "javascript - λ表記をDSLに"
実行環境として作っても相性いいのかもしれない

UIScrollViewのイベント処理が摩訶不思議な件

UIScrollViewにaddSubViewした透明なレイヤーをフリックでスクロールできるようにしつつ、
ピンチ時にはUIScrollViewの背後に有るレイヤー(UIImageView)を拡大縮小してみたかったけど、
これがなかなかの難物でうまくいかない。(現在未解決)


UIScrollViewのタッチイベントを上書きして無理矢理にイベントを全部背後のレイヤーに送ろうとしても、
スクロールできる方向に動かすとタッチイベントをUIScrollView自身でハンドルできない!
(から送れなくなる)


で、
イベントがどうなっているのかいろいろ試す過程で
UIScrollViewの前方にカスタムした透明なUIViewを置き、
そのUIViewのタッチイベントをすべてUIScrollViewに送るのを試してみたところ、
これではスクロールしなくなった。


スクロールのイベント処理はどこで行われているんだろう??

UIImage.size

UIImageのサイズを得るのに
UIImage.sizeを使っちゃっているのをちらちら見るけど、
こいつはiOS4.0以降ピクセルじゃなくポイントを返す。

ピクセル数を得るには

UIImage.CGImageから

CGImageGetWidth()
CGImageGetHeight()

で取得するのが正しい方法。

というtip

UIScrollViewで無限ループになっちゃう


UIScrollViewの

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;

メソッドを実装した時に

scrollView.zoomScaleプロパティをつかったら、
setter、getter関係なく
再びviewForZoomingInScrollView:scrollViewが呼ばれて無限ループになってしまいました。

viewForZoomingInScrollView:scrollViewが呼ばれた時のスケールの値をログに出力しようとしたときに発生しました。