2011年11月12日土曜日

VBAでツールを作るのはもうやめようか。

業務でしばしばエクセルVBAを用いたツールを作ってきた。
簡易ツールもあるが、かなり業務を遂行する上で必須となるようなツールも作ってきた。

はじめて触れたのは新人のころで実行環境と開発環境がエクセル1個で済み、
マクロの保存なんかですぐにコードが保存できる点なども勉強しやすくこれはいいと思って
食い入るように勉強したのを覚えている。

ただ、最近はVBAでツールを作成することに少し疑問を抱いてきた。(重要度が高いツールに限る)

業務上必須となるようなツール。
例えば開発ルーチンに組み込まれるようなツール。
ソースの自動生成とか。試験ツールとか。

こういったツールは、開発の状況次第でメンテナンスが頻繁に発生したりする物だと思う。
エクセルVBAをわざわざ使うということは、そのツールの扱うデータはエクセルである場合がほとんどである。
エクセルVBAはエクセルのデータを参照する場合にはとても楽で悪いところが見当たらないように思える。
だが、メンテナンスが頻繁に発生する場合に以下の観点でエクセルVBAはとても扱いづらい。

・バージョンの管理
・使用者へのアップデート通知とその徹底
・メンテナンスを行う人間の教育


バージョンの管理について
エクセルVBAは開発環境自体がエクセルのVBエディタ機能を利用する。
そしてツールのコードは、フォーム、モジュール、クラスと分かれるがこれはエクセル形式として
内部にまとまってしまっている。
これらのコードのバージョン管理をきちんと行おうとすると?
それらをエクスポートして別管理としなければならない。
しかもそれらはまたインポートするなんて手間もかかる。

使用者へのアップデート通知とその徹底
バージョン管理ソフトにツール本体を管理させておいて、周知して最新版を使って下さい。
これならあまり問題はないのだろうが、問題なのはファイルサーバなんかにおいてしまった場合。
エクセルなんて簡単にコピーができる。
どこに派生しているかわかったもんじゃない。
そして、同じ名前が付いたツールを使用者全員が最新版かなんて意識しているほうが珍しいだろう。
最新版とそれ以前のツールを混合して使用された場合、そのツールで一定の品質なんて保てると言えるのだろうか。

メンテナンスを行う人間の教育
メンテナンスを行う人は、いつまでも初期開発者とは限らない。
僕の従事するプロジェクトは主にJavaをメインに扱うプロジェクトであったため
エクセルVBAをコーディングできる人間は割と少なかった。(ちょっと書ける程度の人間は省く)
そういったプロジェクトでメンテナンスできる人間を育てるのは面倒である。

上記のような理由で僕は最近VBAでツールを作成することに少し躊躇する。
これからはなるべくJavaを用いて業務ツールを作成しようと思う。
POI、VelocityをはじめJavaでも実現が可能だから。
あとVBAはOSによっていきなり参照が効かなくなったりするしね。

長々と講釈たれたなぁ。
何年後かに自分で見たら違う意見を持ってたりして恥ずかしくなるんだろうな。

2011年11月3日木曜日

MySQL5をMacにアンインストールとMacPortsで再インストール

MySqlはMacに前に入れたが、なんか入れ直したくなったので入れ直した。

まずアンインストール。
# rm -rf /usr/local/mysql
# rm -rf /usr/local/mysql-5.X.XXXXXXX
# rm -rf /Library/StartupItems/MYSQLCOM
# rm -rf /Library/PreferencePanes/MySQL.prefPane
# rm -rf /Library/Receipts/mysql-XXXXXXXXXXXXXX.pkg

次に、MacPortsの更新確認とか行う。
とりあえずこれは馬鹿長い。
sudo port selfupdate
sudo port upgrade outdated

MacPortsで再インストールする。
 
>sudo port install mysql5-server
 
…省略…
 
###########################################################
# A startup item has been generated that will aid in
# starting mysql5-server with launchd. It is disabled
# by default. Execute the following command to start it,
# and to cause it to launch at startup:
#
# sudo port load mysql5-server
###########################################################
--->  Installing mysql5-server @5.1.59_0
******************************************************
* In order to setup the database, you might want to run
* sudo -u _mysql mysql_install_db5
* if this is a new install
******************************************************
--->  Activating mysql5-server @5.1.59_0
--->  Cleaning mysql5-server
 

以下のコマンドを実行しないと、MySqlが動かない。
 
>sudo -u _mysql mysql_install_db5
 
…省略…

PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !
To do so, start the server, then issue the following commands:

/opt/local/lib/mysql5/bin/mysqladmin -u root password 'new-password'
/opt/local/lib/mysql5/bin/mysqladmin -u root -h PC名.local password 'new-password'

Alternatively you can run:
/opt/local/lib/mysql5/bin/mysql_secure_installation
 
…省略…
  

rootのパスワードを上記メッセージないように従って、設定する。
 
>/opt/local/lib/mysql5/bin/mysqladmin -u root password '設定するパスワード'
 

これで再インストールは終わり。
続いて設定を行う。

MySQLの設定ファイルをテンプレートからコピーする。
 
>sudo cp /opt/local/share/mysql5/mysql/my-small.cnf /opt/local/etc/mysql5/my.cnf
 

編集する。
 
>sudo vim /opt/local/etc/mysql5/my.cnf
 
 …省略…
 
# The MySQL server
[mysqld]
port            = 3306
socket          = /opt/local/var/run/mysql5/mysqld.sock
skip-locking
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
thread_stack = 128K
 
## OWNSETTINGS …書き加えたところ
default-character-set = utf8
default-storage-engine=innodb
skip-character-set-client-handshake
 

ここまでで再インストール作業は全て完了
すでにMySQLが立ち上がっているなら停止/起動を行う。

 
>sudo /opt/local/share/mysql5/mysql/mysql.server stop
 
Shutting down MySQL
... SUCCESS! 
 
>sudo /opt/local/share/mysql5/mysql/mysql.server start
 
Starting MySQL
. SUCCESS! 
 

ちなみに、MySQLの文字コードが以下のようになっていることを接続した後確認すると良いです。
 
mysql> show variables like 'char%';
+--------------------------+-----------------------------------------+
| Variable_name            | Value                                   |
+--------------------------+-----------------------------------------+
| character_set_client     | utf8                                    |
| character_set_connection | utf8                                    |
| character_set_database   | utf8                                    |
| character_set_filesystem | binary                                  |
| character_set_results    | utf8                                    |
| character_set_server     | utf8                                    |
| character_set_system     | utf8                                    |
| character_sets_dir       | /opt/local/share/mysql5/mysql/charsets/ |
+--------------------------+-----------------------------------------+
8 rows in set (0.00 sec)

 

2011年10月15日土曜日

CassandraのCQLはスーパーカラム(SuperColumn)の存在無視なのか?

ご存知、Apache Cassandraは0.8からCQLなるものをリリースしました。
こりゃ便利だと、使ってるメモです。

CQLはSQLととても似てて(つか狙って似せてるんだろう)親しみやすいです。
SELECTなんかも以下のような構文でSQL知ってりゃすぐわかる感じです。

SELECT * FROM カラムファミリ WHERE KEY = キー
なので、こんな事もできます。
SELECT * FROM カラムファミリ WHERE COL1 = 条件1 AND COL2 = 条件2
ただし、スーパーカラム(SuperColumn)をいっぱい使用している0.7時代のユーザは
このCQLのちょっと意味不明な仕様にがっかりしたんじゃないのかと思う。

SELECT文にてWHERE句に指定できるカラムは
セカンダリインデックスが付いてないとダメ!!!

ええええ?だってさ、SuperColumnってセカンダリインデックス貼れないじゃん!
これは困りますよね。。。

SuperColumnってCassandraの強みと1つに数えられるくらい大切なものじゃないの?
新しくCQLなるものを出したんならそこに対応していないってのもどうかと思う。

ここで書いてるのはあくまで自分で調べた結果であって、
もし、、いやいやこーやればできんじゃん!ってのがあれば是非ご教授下さい。

@PostConstructと@PreDestroyの悩み

アノテーションの使い方を前回で書いたのはいいんだけども。

ちょっと気になったので試したら悩みができた。。。
@Autowiredとか@Resourceとかで設定した場合でprototype指定されていたら、
ApplicationContextを直接持ってないとこでdestroyって呼べなくね?みたいな。
ApplicationContext持ち回せばいいのかな。。。。

試しに以下のようなコードを書いたが確かにdestroyは呼ばれない。(前回試してるから当たり前なんだが)
クラスを2重にするのがめんどくさかったのでJUnitで。。。。

対象Bean
package com.zomu.t.example;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("testComponent")
@Scope("prototype")
public class TestComponent {

 @PostConstruct
 public void init() {
  System.out.println("init.");
 }

 public void outputMsg() {
  System.out.println("msg....");
 }

 @PreDestroy
 public void destroy() {
  System.out.println("destroy.");
 }

}


実行クラス
package com.zomu.t.example;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:ApplicationContext.xml" })
public class TestInitDestroy2 {

 @Resource(name = "testComponent")
 TestComponent tc = null;

 @Test
 public void main() {
  tc.outputMsg();
 }
}


んーどうすりゃいいんだ。
思いつくのはこれしかない。

実行クラス
package com.zomu.t.example;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:StoreManager-Common-ApplicationContext.xml" })
public class TestInitDestroy2 {

 @Resource(name = "testComponent")
 TestComponent tc = null;

 @Autowired
 ConfigurableApplicationContext appContext = null;

 @Test
 public void main() {
  tc.outputMsg();

  appContext.getBeanFactory().destroyBean("testComponent", tc);
 }
}
やってることは、前回と変わりはない。ApplicationContextと手を握り合って「バルス」って唱えただけ。

これだと当たり前にdestroyも呼ばれるんだが。
他にいい方法ってないの?

@PostConstructと@PreDestroy

Springの@PreDestroyと@PostConstructについて。
これはBean定義時代の「init-method」と「destroy-method」のアノテーション。

Scopeをprototypeで作成したBeanについて@PreDestroyはいつ呼ばれんだ?
って気になったので、サンプルを作成して確かめたんでメモる。

まずは、prototypeで作成しない場合の例

対象のComponent
package com.zomu.t.example;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.stereotype.Component;

@Component("testComponent")
public class TestComponent {

 @PostConstruct
 public void init() {
  System.out.println("init.");
 }

 public void outputMsg() {
  System.out.println("msg....");
 }

 @PreDestroy
 public void destroy() {
  System.out.println("destroy.");
 }

}


実行クラス
package com.zomu.t.example;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestInitDestroy {

 public static void main(String[] args) {

  ConfigurableApplicationContext appContext = new ClassPathXmlApplicationContext(
    new String[] { "ApplicationContext.xml" });

  TestComponent tc = (TestComponent) appContext.getBean("testComponent");

  tc.outputMsg();

  appContext.close();

 }
}


結果
init.
msg....
destroy.

おお。ちゃんと呼ばれてますね。
では、prototypeで作成した場合。

対象のComponent
package com.zomu.t.example;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("testComponent")
@Scope("prototype")
public class TestComponent {

 @PostConstruct
 public void init() {
  System.out.println("init.");
 }

 public void outputMsg() {
  System.out.println("msg....");
 }

 @PreDestroy
 public void destroy() {
  System.out.println("destroy.");
 }

}

さっきと違うのは、@Scopeでprototype指定しているだけ。
実行すると、、、

結果
init.
msg....

あれ、destroyが呼ばれない。
なんじゃこりゃ。
プロトタイプだから、もうBeanFactoryの知ったこっちゃねぇって事?
じゃー実行クラスで実験してみる。

package com.zomu.t.example;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestInitDestroy {

 public static void main(String[] args) {

  ConfigurableApplicationContext appContext = new ClassPathXmlApplicationContext(
    new String[] { "ApplicationContext.xml" });

  TestComponent tc = (TestComponent) appContext.getBean("testComponent");

  tc.outputMsg();

  appContext.getBeanFactory().destroyBean("testComponent", tc);

  appContext.close();

 }
}


結果
init.
msg....
destroy.

なるほど。
ちゃんとインスタンスと登録名さえ渡してくれりゃケツ拭きまっせ。ってことっぽい。
めんどいから拭いてくれよー。って思いました。

2011年10月5日水曜日

Castorの使い方「Unmarshal」

Castorを使い始めて間もないが、基本的にネット上に転がっている情報ではUnmarshalについて詳しく載っていなくて
英語もあまり得意でない僕にとっては、苦戦を強いられた。。。

まずUnmarshalは、XML→Objectのこと。
おいおいそこからかよ。。。っていう気持ちを抑えて無駄にメモることにする。

使い道はいろいろあるだろうけど、
自分の場合はWebAPIの返却をオブジェクトとして扱いたかったから。
(XSDとか提供されていない場合に)

Unmarshalを行うコードは以下のようになる。
public class CastorTest {

 private static Mapping mapping;
 private static Unmarshaller unmar;
 private static CategoryList categoryList;

 public CastorTest() {

 }

 private void initialize() {
  mapping = new Mapping();
  try {
   mapping.loadMapping("xml/categoryList_mapping.xml");
   System.out.println("categoryList_mapping succeeded.");
  } catch (Exception e) {

  }
 }

 private void HandleCategoryList() {
  try {
   unmar = new Unmarshaller(mapping);
   categoryList = (CategoryList) unmar.unmarshal(new InputSource(
     new FileReader("xml/categoryList.xml")));
   Category[] categories = new Category[categoryList.getCategories().getCategories().size()];
   categories = (Category[]) categoryList.getCategories().getCategories().toArray();

   for (int i = 0; i < categories.length; i++) {
    Category category = categories[i];
    System.out.print(category.getName());
   }

  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static void main(String[] args) {
  CastorTest test = new CastorTest();
  test.initialize();
  try {
   test.HandleCategoryList();
  } catch (Exception e) {
   System.out.println(e);
  }

 }
}
僕がつまづいたのは、Mapping定義を示すXMLファイルの書き方。。。 Unmarchalを行うXMLとそのMapping定義のXMLを続けて以下に示す。 ▼変換したいXML


 
  
   002
   名前1
   キー1
   親1
  
  
   001
   名前2
   キー2
   親2
  
 

▼Mapping定義


 
  
  
   
  
 
 
  
   
  
 
 
  
   
  
  
   
  
  
   
  
  
   
  
 


はまったところだけを書いておくと、、、、

->変換するXMLはエンコーディング指定をSJISにしないとだめ!
->ArrayListへ詰めるには上記のようにしなきゃだめ!
->基本的にclassタグにはtypeを書いておけ!
->たぶんclassは入れ子になっちゃダメ?

こんくらいかな。
誰かが救われれば本望です。

2011年10月1日土曜日

JSFの限界点 - イントロ -

JSF(JavaServerFaces)はSunが作ったフレームワーク。
MVC2を採用していてるJEEに準拠したもの。

導入はとても簡単でっていっても書かないけど、
GlassFishと併せて使ってみて相性がとてもいいんではないかと感じた。


基本的に製造順序としては以下のようになるのでは?と考えている。

1.画面(XHTML)の作成(ただし、ManagedBeanがなくてもいいようなプロト状態)
2.ManagedBeanの作成
  〜ビジネスロジック等も製造するけどまー省略〜
3.画面のManagedBeanへの適用


何回かに分けてこれから書こうかと思うが、
実際に使うことを検討したが、辞めた理由についてメモしていく。

2011年9月30日金曜日

Hectorを使ったCassandraへのアクセス ~CQL~

CassandraへHectorを使用してアクセスを行う。
今回は、CQLを使ったアクセスを試みます。

package com.zomu.t;

import java.nio.charset.CharacterCodingException;

import me.prettyprint.cassandra.model.CqlQuery;
import me.prettyprint.cassandra.model.CqlRows;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.Row;
import me.prettyprint.hector.api.exceptions.HectorException;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.query.QueryResult;

/**
 * CQLアクセステスト
 * @author nozomu
 */
public class AccessTestCql {

	/** 文字列シリアライザ */
	private static StringSerializer stringSerializer = StringSerializer.get();

	/**
	 *
	 * @param args
	 * @throws CharacterCodingException
	 */
	public static void main(String[] args) throws CharacterCodingException {

		// クラスタ
		Cluster cluster = HFactory.getOrCreateCluster("TestCluster",
				"localhost:9160");
		// 使用するKeySpace
		Keyspace keyspaceOperator = HFactory.createKeyspace("Giraffe", cluster);

		try {

			// CQLクエリを発行するクラス
			CqlQuery<String, String, String> cqlQuery = new CqlQuery<String, String, String>(
					keyspaceOperator, stringSerializer, stringSerializer,
					stringSerializer);
			// CQLを設定
			cqlQuery.setQuery("select * from CF where key = 'key'");

			// 取得する
			QueryResult> result = cqlQuery
					.execute();

			if (result != null) {
				CqlRows rows = result.get();

				if (rows != null) {
					for (Row row : rows.getList()) {
						for (HColumn col : row.getColumnSlice()
								.getColumns()) {
							System.out.println(col.getName() + " : "
									+ col.getValue());
						}
					}
				}
			}
		} catch (HectorException he) {
			he.printStackTrace();
		}
		cluster.getConnectionManager().shutdown();
	}
}


CQLはこれからCassandraを使用する場合には必須となりそうな予感がする。
ObjectMapper等もHectorには用意してあるみたいなので、併せてこちらも試す事にしようと思う。
それはまた次回にでも。

Hectorを使ったCassandraへのアクセス

Cassandraの高度クライアントであるHectorを使用したアクセス方法。
まずは、チュートリアルレベルのものを試してみた。
ちなみに、Cassandraは0.8.4を使用している。

今回はCQLではなく昔からあったアクセス方法みたいなやり方。
ソース貼付けてさぼります。


package com.zomu.t;

import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.OrderedRows;
import me.prettyprint.hector.api.beans.Row;
import me.prettyprint.hector.api.exceptions.HectorException;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.QueryResult;
import me.prettyprint.hector.api.query.RangeSlicesQuery;

public class AccessTest {

	private static StringSerializer stringSerializer = StringSerializer.get();

	public static void main(String[] args) {

		Cluster cluster = HFactory.getOrCreateCluster("TestCluster",
				"localhost:9160");

		Keyspace keyspaceOperator = HFactory.createKeyspace("KeySpace", cluster);

		try {
			Mutator mutator = HFactory.createMutator(keyspaceOperator,
					stringSerializer);

			RangeSlicesQuery rangeSlicesQuery = HFactory
					.createRangeSlicesQuery(keyspaceOperator, stringSerializer,
							stringSerializer, stringSerializer);
			rangeSlicesQuery.setColumnFamily("CF");
			rangeSlicesQuery
					.setColumnNames(new String[] { "col1", "col2" });
			rangeSlicesQuery.setKeys("key", "");

			rangeSlicesQuery.setRowCount(5);
			QueryResult> result = rangeSlicesQuery
					.execute();
			OrderedRows orderedRows = result.get();

			Row lastRow = orderedRows.peekLast();

			System.out.println("Contents of rows: \n");
			for (Row r : orderedRows) {
				System.out.println("   " + r);
			}

		} catch (HectorException he) {
			he.printStackTrace();
		}
		cluster.getConnectionManager().shutdown();
	}
}

Mac OSX のデフォルトのJava

Macを買ってJavaがらみのなにかをなにもしていなければ、
デフォルト状態のJavaの設定になっているはず。

ターミナルを起動し、以下で確認が可能。
>which java
/usr/bin/java
>
>java -version
java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04-307-10M3261)
Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03-307, mixed mode)

CassandraでCQLを使う

少し前からCassandraに興味を持って、使い始めてます。
最初にインストールしてから0.6→0.7→0.8とめまぐるしくリリースされて困りますね。
特に0.8でCQLの登場にはちょっと愕然としました。
なんだよー。そーゆーことやるんなら最初から出せよー。ってな具合で。。。

もう古いバージョンのメモを残してもしゃーないので、CQLを使ったアクセスからメモをしていこうと思っとる次第です。

package com.zomu.t.cql;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.Compression;
import org.apache.cassandra.thrift.CqlResult;
import org.apache.cassandra.thrift.CqlResultType;
import org.apache.cassandra.thrift.CqlRow;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;

public class CassandraAccessSample {

 public static void main(String[] args) {
  try {
   TSocket transport = new TSocket("localhost", 9160);
   TFramedTransport framedTransport = new TFramedTransport(transport);
   TProtocol protocol = new TBinaryProtocol(framedTransport);
   Cassandra.Client client = new Cassandra.Client(protocol);
   transport.open();

   client.set_keyspace("TestKeySpace");
   CqlResult result = client
     .execute_cql_query(
       ByteBufferUtil
         .bytes("select * from CF where key = key_1;"),
       Compression.NONE);
   if (CqlResultType.ROWS.equals(result.getType())) {
    System.out.println("HIT->"+result.rows.size()+"\n");
    for (CqlRow row : result.rows) {
     for (Column col : row.columns) {
      System.out.println(ByteBufferUtil.string(col.name) + "="
        + new String(col.getValue()));
     }
     System.out.println("-----------------");
    }
   }
   transport.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}
 

今回はCassandraのサンプルで一般的にあるThriftを利用したクライアントで接続する方法です。
Webアプリケーション等でCassandraを使用する場合にはこのような接続方法は推奨されません。
僕はしてて、かなりひどい状態になりました。。。。

CassandraのWikiを見ると、高度なクライアントなんて感じで紹介されているので使っていこうかと思います。
予定ではHector

2011年9月29日木曜日

メモをコードも一緒に残したい

大変恐縮ながら、僕もWEB上にメモをしようかと。
なので、コードも載せれるように準備しました。

// ほげ
String hoge = new String();


こんな感じで書く訳ですね。

2011年9月15日木曜日

Castor

Castorを使用した、Javaオブジェクト→XMLをやってみた。
とても簡単にできるのでとてもおすすめ。

今回は、XSLTによる変換を行いたいため使用した。
つか、このブログってソース載せることできるかな。。。
なんだっけ。Wikiとかでもソース載せるのに便利な…あれ。忘れちゃった。。。