<

![endif]-->

fc2ブログ

Mac de Groovy

MacOSX10.6.4でgroovyを使ってみたら文字化けって、さぁたいへん。
ぐぐったら、以下のサイトに回答がありました。色々案がありますが、最後のまとめを参考にしました。
[groovy] groovyでスクリプトのエンコーディングを指定する

~/.bash_profile
に以下の記述を追加しました。
export JAVA_OPTS='-Dgroovy.source.encoding=UTF-8 -Dfile.encoding=UTF-8'

ちなみに~/.bash_profileにはエイリアスも指定しています。以前はここにエンコードを記述しましたが、今はクラスパスだけになっています。

alias g='groovy -cp ".:/Users/myself/opt/clspath"'

これで、groovyを実行する時は
g test.groovy
となります。

groovyコマンドの引数の順番で動きがおかしくなる

groovyではクラスをあらかじめ定義しておいてクラスパスが通っている場所に置いておくと、他のgroovyから利用できるようになります。

/Users/myself/clspath/Test.groovy

class Test {
def aaa() {
"bbb"
}
}

/Users/myself/App.groovy

import Test
println(new Test().aaa())


この時、カレントディレクトリを/Users/myselfの時に
groovy -cp .:/Users/myself/clspath App.groovy
で実行できます。

しかし、エンコーディングを指定した時に思わぬ落とし穴にはまりました。
本来なら以下のコマンドで実行できるはずです。
groovy --encoding UTF-8 -cp .:/Users/myself/clspath App.groovy

しかし
import Testの部分でTestが見つからないとエラーになってしまいます。
で、色々試してみた結果わかったのが、クラスパスを先にすることで避けられるということです。
以下でうまく動きます。
groovy -cp .:/Users/myself/clspath --encoding UTF-8 App.groovy

ちなみに、うまくいかなかった環境は
MacOSX10.6.4
Groovy Version: 1.7.4
JVM: 1.6.0_20
です。

Groovyでソース生成(INotifyPropertyChangedの実装)

WPFでTreeViewでも表示しようかと思い、[C#][WPF]WPFのTreeViewコントロールの基本的な使い方というサイトを参考にしました。
ツリーのデータを定義するクラスが必要とのことで、INotifyPropertyChangedを実装するわけですが、どうにもコピペするのがめんどくさいし、実際は変数に応じて似たようなソースを書かなければならないようなので、いっそのことソースを生成することにしました。
ソースの自動生成ということで、Groovy+Velocityのおでましです。

■Groovyのソース

// 名前空間、クラス名、[型 変数名]+の順に記述
def x="""\
WpfApplication1
Node
string title
int count
bool isUpdated
"""

// GrapeでVelocityを取ってくる
@Grab(group='org.apache.velocity', module='velocity', version='1.6.4')
import org.apache.velocity.app.Velocity
import org.apache.velocity.VelocityContext
import org.apache.velocity.Template

// 変数部分切り出し
def ary = x.split("\n")
def params = []
for (i = 2; i < ary.length; i++) {
def lineAry = ary[i].split(" ").toList()
lineAry << lineAry[1].capitalize()
params << lineAry
}

// テンプレート適応
Velocity.init()
VelocityContext context = new VelocityContext()
context.put("namespace", ary[0])
context.put("classname", ary[1])
context.put("params", params)
Template template = Velocity.getTemplate("INotifyPropertyChanged.vm", "UTF-8")
Writer writer = new OutputStreamWriter(System.out)
template.merge(context, writer)
writer.close()

■テンプレート

#macro(makeProp $type $name $cname)
public ${type} ${cname}
{
get
{
return $name;
}
set
{
if (value != ${name})
{
${name} = value;
NotifyPropertyChanged("${cname}");
}
}
}

#end
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace ${namespace}
{
public class ${classname} : INotifyPropertyChanged
{
#foreach($param in $params)
private ${param.get(0)} ${param.get(1)};
#end
private ObservableCollection<${classname}> nodes = null;

public ${classname}(#foreach($param in $params)${param.get(0)} _${param.get(1)}, #end ObservableCollection<${classname}> _nodes)
{
#foreach($param in $params)
${param.get(1)} = _${param.get(1)};
#end
nodes = _nodes;
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

#makeProp("ObservableCollection<${classname}>", "nodes", "Nodes")
#foreach($param in $params)
#makeProp($param.get(0), $param.get(1), $param.get(2))
#end
}
}

■生成されたソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace WpfApplication1
{
public class Node : INotifyPropertyChanged
{
private string title;
private int count;
private bool isUpdated;
private ObservableCollection nodes = null;

public Node(string _title, int _count, bool _isUpdated, ObservableCollection _nodes)
{
title = _title;
count = _count;
isUpdated = _isUpdated;
nodes = _nodes;
}

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

public ObservableCollection Nodes
{
get
{
return nodes;
}
set
{
if (value != nodes)
{
nodes = value;
NotifyPropertyChanged("Nodes");
}
}
}

public string Title
{
get
{
return title;
}
set
{
if (value != title)
{
title = value;
NotifyPropertyChanged("Title");
}
}
}

public int Count
{
get
{
return count;
}
set
{
if (value != count)
{
count = value;
NotifyPropertyChanged("Count");
}
}
}

public bool IsUpdated
{
get
{
return isUpdated;
}
set
{
if (value != isUpdated)
{
isUpdated = value;
NotifyPropertyChanged("IsUpdated");
}
}
}

}
}

まずGroovyのソースですが、Grapeを仕組みを使って依存しているVelocityのライブラリを自動でダウンロードしています。最近、Grapeを使い始めたのですが、こいつは激しく便利ですね! いままでやっていた、ダウンロードしてファイルを展開して、適当な位置に配置して、クラスパス指定して・・・あたりの作業を一切やらなくなったので、かなりの時間短縮です。
あとは、普通にテンプレート適応しています。

テンプレートでは、マクロを使用しています。getter, setterみたいな決まりきった関数を作るのに便利です。

WPFでGroovy+Velocityとは奇妙な組み合わせですが、私の中ではソース生成では鉄板の組み合わせです。

区分値を管理するシートからINSERT文作成

区分値を管理するOpenOffice CalcのシートからテーブルへinsertするSQLを自動生成するgroovyのプログラムを紹介します。
まずシートの構造です。kbn.odsに保存されているとします。

区分分類名 区分分類コード 区分明細コード 区分明細名
性別 001 01 男
性別 001 02 女
都道府県 002 01 北海道
都道府県 002 02 青森

次に登録するテーブルです。

CREATE TABLE t_kbn (
kbn_code TEXT NOT NULL
,kbn_type_code TEXT NOT NULL
,kbn_detail_code TEXT NOT NULL
,kbn_detail_name TEXT NOT NULL DEFAULT ''
,bk TEXT NULL
,PRIMARY KEY(kbn_code)
);

区分分類名は登録しません。kbn_codeは区分分類コードと区分明細コードを結合したものを登録します。
では、本命のgroovyのプログラムです。
外部ライブラリとしてJXPathを利用しています。

import java.util.zip.*
import javax.xml.parsers.*
import org.apache.commons.jxpath.*
import org.w3c.dom.*

// ZIPファイルの指定
def zipFile = new ZipFile("kbn.ods");
def zipEntry = zipFile.getEntry("content.xml");

// ZIPの中身のXML読み込み
def factory = DocumentBuilderFactory.newInstance();
def builder = factory.newDocumentBuilder();
def document = builder.parse(zipFile.getInputStream(zipEntry));

// XML解析
def context = JXPathContext.newContext(document);
def sheet = context.selectSingleNode(
"/office:document-content/office:body/office:spreadsheet/table:table[@table:name='Sheet1']");
context = JXPathContext.newContext(sheet);
def rowList = context.selectNodes("table:table-row");
def lineList = []
for (Object row : rowList) {
def line = []
context = JXPathContext.newContext(row);
def colList = context.selectNodes("table:table-cell");
for (def col : colList) {
context = JXPathContext.newContext(col);
try {
def value = context.getValue("text:p");
line << value
} catch (JXPathNotFoundException e) {
}

}
if (line.size() != 0) {
lineList << line
}
}

// SQL生成
lineList.remove(0)
print "INSERT INTO t_kbn (kbn_code, kbn_type_code, kbn_detail_code, kbn_detail_name) VALUES \n"
def first = true
for (def line : lineList) {
print " ";
if (first) {
first = false;
print " "
} else {
print ","
}
print "('" + line[1] + line[2] + "', '" + line[1] + "', '" + line[2] + "', '" + line[3] + "')\n"
}
print ";\n"

実行結果は以下の通りです。

INSERT INTO t_kbn (kbn_code, kbn_type_code, kbn_detail_code, kbn_detail_name) VALUES
('00101', '001', '01', '男')
,('00102', '001', '02', '女')
,('00201', '002', '01', '北海道')
,('00202', '002', '02', '青森')
;

GroovyでVelocity

テンプレートエンジンのApache Velocityを使ったGroovyのサンプルです。
data.xmlにあるデータを読み込んで、result.htmlを作成します。
■データ(data.xml)

<?xml version="1.0" encoding="UTF-8"?>
<data>
<param name="data2">予定表??ハンカク</param>
<param name="data4">予定表??ハンカク</param>
</data>

■テンプレートファイル(template.vm)

<html>
<body>
予定表??ハンカク
${data2}
${data3}
${data4}
</body>
</html>

■ソース(velocity.groovy)

import org.apache.velocity.app.Velocity
import org.apache.velocity.VelocityContext
import org.apache.velocity.Template
import java.io.OutputStreamWriter
import java.io.FileOutputStream
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.XPathFactory
import javax.xml.xpath.XPathConstants

document= DocumentBuilderFactory
.newInstance()
.newDocumentBuilder()
.parse(new File("data.xml"))
xpath = XPathFactory.newInstance().newXPath()
nodes = xpath.evaluate("//param", document.getDocumentElement(), XPathConstants.NODESET)

Velocity.init()
VelocityContext context = new VelocityContext();
context.put("data3", "予定表??ハンカク")
nodes.each() {
map = it.getAttributes()
item = map.getNamedItem("name")
context.put(item.getValue(), it.getTextContent())
}

Template template = Velocity.getTemplate("template.vm", "UTF-8")
fw = new OutputStreamWriter(new FileOutputStream("result.html"),"UTF-8")
template.merge(context,fw)
fw.flush()
fw.close()

■結果(result.html)

<html>
<body>
予定表??ハンカク
予定表??ハンカク
予定表??ハンカク
予定表??ハンカク
</body>
</html>

今回のサンプルではXMLを使ってデータを読み込んでいますが、以前紹介したGroovyでDBアクセスを使えば、DBからも簡単に組み込めます。

Velocityは、テンプレートファイルで細かい制御ができるのでソース生成によく使っています。
ただ、テンプレート内のコマンドが最低限しか無いのが、ちょっとめんどくさいところもあります。

一行入魂サイトにまとめがあります。