いきなりはまった。select_date

かなり久しぶりにRails触って、いきなりハマった・・

<%= select_date(@selected_date, :use_month_numbers => true) %>
ActionView::TemplateError (can't convert Symbol into String)

Railsのバージョンが2.2.2でロケールをjaにしていたせいみたい。(知ったかぶり)
id:masaaki008:20090128:1233130816

ついでにはてな記法もきれいさっぱり忘れていた。
やっぱマメに書かなきゃと少し反省・・

OSXのJVMはClientとServerで違っていた・・

常識?かも知れないのですが、自分は今の今まで知らなかったのでメモ・・

昨日ちょっといろいろトラブっていて環境調査をしていて気がつきました。

OSX Tiger (PowerMac G5)での表示

$ java -version
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-241)
Java HotSpot(TM) Client VM (build 1.5.0_13-121, mixed mode, sharing)

OSX Tiger Server (XServe G5)での表示

$ java -version
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-241)
Java HotSpot(TM) Client VM (build 1.5.0_13-121, mixed mode)


よーく目を凝らしてみると、Server版にはsharingの文字がありません。
どういう事か調べてみたら、sharingがつくモードはクライアント版のJVMだと言う事が解りました。
という事は、OSX Serverで何も考えずにjavaを起動すると、それは「-server」オプションをつけて起動したのと同じ事という訳ですね。*1

いったいその違いはどこで設定されているのか調べてみると、/Library/Java/Home/lib/jvm.cfg にどのVMを起動するかの設定が書かれていました。ぜんぜん知らなかったよ!(恥)

OSX Tigerの設定

$ cat /Library/Java/Home/lib/jvm.cfg 
#
# @(#)jvm.cfg   1.6 01/12/03
# 
# Copyright 2002 Sun Microsystems, Inc. All rights reserved.
# SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
# 
# 
#
#
# List of JVMs that can be used as an option to java, javac, etc.
# Order is important -- first in this list is the default JVM.
# NOTE that this both this file and its format are UNSUPPORTED and
# WILL GO AWAY in a future release.
#
# You may also select a JVM in an arbitrary location with the
# "-XXaltjvm=<jvm_dir>" option, but that too is unsupported
# and may not be available in a future release.
#
-client KNOWN
-jvm ALIASED_TO -client
-hotspot ALIASED_TO -client
-server KNOWN
-classic WARN

OSX Tiger Serverの設定

$ cat /Library/Java/Home/lib/jvm.cfg 
#
# @(#)jvm.cfg   1.6 01/12/03
# 
# Copyright 2002 Sun Microsystems, Inc. All rights reserved.
# SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
# 
# 
#
#
# List of JVMs that can be used as an option to java, javac, etc.
# Order is important -- first in this list is the default JVM.
# NOTE that this both this file and its format are UNSUPPORTED and
# WILL GO AWAY in a future release.
#
# You may also select a JVM in an arbitrary location with the
# "-XXaltjvm=<jvm_dir>" option, but that too is unsupported
# and may not be available in a future release.
#
-server KNOWN
-jvm ALIASED_TO -client
-hotspot ALIASED_TO -client
-client KNOWN
-classic WARN

よーく目を凝らしてみると、-serverと-clientの順序が入れ替わっている・・*2
という訳で、知らず知らずのうちにServer版のJVMを使っている可能性もあるね、と言うお話でした。

*1:毎度-serverオプションつけてたよ・・

*2:Leopardに関しては未調査

ActiveRecord#to_xmlの日本語対応版(?)を作る

ActiveRecordXMLシリアライズするのは非常にお気軽で良いのですが、日本語が数値参照形式になってしまいます。

katochan = Member.create(:name => '加藤茶', :email => 'katochan@hoge.com')
puts katochan.to_xml

結果はこのとおり。

<?xml version="1.0" encoding="UTF-8"?>
<member>
  <created-at type="datetime">2008-06-02T17:46:34+09:00</created-at>
  <id type="integer">1</id>
  <mail>katochan@hoge.com</mail>
  <name>&#21152;&#34276;&#33590;</name>
  <updated-at type="datetime">2008-06-02T17:46:34+09:00</updated-at>
</member>

加藤茶になっていて欲しいのですが、&#21152;&#34276;&#33590;になっていて何とも気持ちが悪いです。これってREXMLの仕様ですか?
d:id:ehara_kw:20071221とd:id:eclipse-a:20070905を参考に、強引ですがこういう対処を取ってみました。

class ActiveRecord::Base
  
  def to_jp_xml(options, &block)
    return self.to_xml(options, &block).gsub(/&#(?:(\d*?)|(?:[xX]([0-9a-fA-F]{4})));/) { [$1.nil? ? $2.to_i(16) : $1.to_i].pack('U') }
  end

end

to_xmlメソッドをオーバーライドしてもいいかな?と思ったけど、小心者なのであえて別メソッドにしています・・

2行追加してSingletonにする方法

オブジェクトをSingletonにしたいケースってたまにあると思いますけど、Javaだとこんな感じ。

public class Singleton {

	private static Singleton instance;

	private Singleton() {
		super();
		System.out.println("Singletonオブジェクト生成");
	}

	public static Singleton getInstance() {
		synchronized (Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
		return instance;
	}
}

当たり前ですけど、いちいち実装しなければなりません。コンストラクタをプライベートにして、staticでインスタンスを持たせて、getInstanceメソッドでは念のためsynchronizedで囲って・・と、結構煩わしいですね。しかもそういう目に遭う度に同じようなコードを書かなくちゃならない。ただ、そう言うもんだと何の疑問も持っていませんでした。

同じような局面にRubyで出会った時、会社のRubistにあっさりと、それrequire 'singleton' で、と言われて目から鱗

require 'singleton'
class Singleton
  include Singleton
  
  def initialize
    puts 'Singletonオブジェクト生成'
  end
  
end

require 'singleton'しておいて、include Singletonすればそのクラスのコンストラクタがprivateになり、クラスメソッドの#instanceが追加されるというもの。*1実質的には2行追加するのみ。

コンストラクタに引数がある場合どうすんだ?という疑問もありますけど、こういうフレキシビリティーは感動的ですらあります(笑)

ぼちぼち再開しようかと

気がつけば、最後に更新したのが2年ほど前・・

生きてます(笑) > Blog繋がりの人たち

最近はRuby(っていうか、Rails)を勉強しているので、メモして行きます。

JavaからRubyへ ―マネージャのための実践移行ガイド

JavaからRubyへ ―マネージャのための実践移行ガイド

あとは印刷屋のdeveloper暦も長くなってきたので、その辺の戯れ言もポチポチと。

sipsコマンドは使えるかも

印刷屋としては、画像処理は避けて通れない道だったりします。
最近は何でもアプリケーションサーバで処理する傾向があって、ファイル容量も大きなものを扱う必要があったりなど、ちょっと悩みの種でもあります。
今の所、JavaJava Image I/O API を使っていますが、Javaでデカイ画像を扱うのは勇気がいります。それに、CMYKの扱いが今ひとつだったりしますし。

Photoshopを使えば簡単にできるような処理も、サーバ上でそれをやる訳にも行かず(やって出来ない事は無いだろうけど・・)、どうしたものかと考えておりました。

そこで、せっかく画像処理に強いと言われる(笑)Mac OSX を運用プラットフォームにしている強みを生かして、何か簡単で良い方法が無いかと調べていた所・・・やっぱりありました。


sipsコマンドです。なんだ、Pantherからあったのか。知らんかった(笑)

sipsで出来る事

このページ(http://developer.apple.com/jp/technotes/tn2035.html)に日本語の解説が載っていました。

  • 各種ラスタイメージ形式の読み書きと変換
  • jpegJfif および Xiff)、GIF、PNG
  • 基本的画像処理
  • 回転、反転、切り取り、パディング、再サンプリング、解像度変更
  • カラーマネージメント操作
  • プロファイルの埋め込み、抽出、調整
  • 既知のメタデータタグの読み書き
  • 可能な限りのオリジナルコンテンツの維持

印刷屋的には、おそらく一番最後の「可能な限りのオリジナルコンテンツの維持」ってのが重要な気がします。

これから書く事は、実は2003年12月のこの記事(http://journal.mycom.co.jp/column/osx/073/)と、ほとんどかぶってしまう事が判明しましたが、個人的なテストの結果としてメモしておきます。

画像のプロパティを取得する

取れるだけ取ってくる
sips -g all imageFile

$ sips -g all test.jpg
/test.jpg
pixelWidth: 3136
pixelHeight: 2352
typeIdentifier: public.jpeg
format: jpeg
formatOptions: default
dpiWidth: 314.000
dpiHeight: 314.000
samplesPerPixel: 3
bitsPerSample: 8
hasAlpha: no
space: RGB
creation: 2006:01:09 11:49:37
make: OLYMPUS IMAGING CORP.
model: E-330
software: Version 1.0
description: OLYMPUS DIGITAL CAMERA

縦横のピクセル数、解像度、カラースペースはもとより、メタデータのたぐいも拾って来れますね。

ピクセル数を取ってくる
sips -g pixelHeight -g pixelWidth imageFile

$ sips -g pixelHeight -g pixelWidth test.jpg
/test.jpg
pixelHeight: 2352
pixelWidth: 3136

不要な情報がいらない場合は、-gオプションに欲しいプロパティ名をつければOKです。

複数画像のプロパティを取ってくる
sips -g propertyName imageFile1 imageFile2...

$ sips -g pixelHeight -g pixelWidth test.jpg test2.jpg
/test.jpg
pixelHeight: 2352
pixelWidth: 3136
/test2.jpg
pixelHeight: 2304
pixelWidth: 1736

もちろん、ファイル名はワイルドカードでもOKです。例えばJPEGのみの場合は、*.jpgなどとします。

画像のフォーマットを変更する

TIFFに変換する
sips -s format tiff --out outImage imageFile

$ sips -s format tiff --out out.tiff test.jpg
/test.jpg
/out.tiff

画像のフォーマットは、jpegtiffpng、gif、jp2、pict、bmp、qtif、psdとなっています。

ちなみに、TIFF変換した画像のプロパティを見てみると、ちゃんと「可能な限りのオリジナルコンテンツの維持」ってのが果たされているのが分かります。

$ sips -g all out.tiff
/out.tiff
pixelWidth: 3136
pixelHeight: 2352
typeIdentifier: public.tiff
format: tiff
formatOptions: default
dpiWidth: 314.000
dpiHeight: 314.000
samplesPerPixel: 3
bitsPerSample: 8
hasAlpha: no
space: RGB
creation: 2006:01:09 11:49:37
make: OLYMPUS IMAGING CORP.
model: E-330
software: Version 1.0
description: OLYMPUS DIGITAL CAMERA

JPEG画像の品質を指定して保存する
sips -s format jpeg -s formatOptions default | [low|normal|high|best|] |packbits --out outImage imageFile

$ sips -s format jpeg -s formatOptions low --out outImage test.jpg
/test.jpg
/out.jpg

formatOptionは直接パーセント指定も出来るようです。

$ sips -s format jpeg -s formatOptions 50 --out outImage test.jpg
/test.jpg
/out.jpg

画像をリサイズする

幅を300ピクセルにする
sips --resampleWidth 300 --out outImage imageFile

$ sips --resampleWidth 300 --out out.jpg test.jpg
/test.jpg
/out.jpg

このリサイズは、縦横比を維持したまま、幅を300ピクセルにリサンプルします。また、高さの指定は --resampleHeight になります。

幅を300ピクセル、高さを200ピクセルにする(変形)
sips -resampleHeightWidth height width --out outImage imageFile

$ sips --resampleHeightWidth 300 200 --out out.jpg test.jpg
/test.jpg
/out.jpg

縦横比を変更したい場合はこのオプションを使います。

縦横比を変えずに、最大ピクセル数を100とする
sips --resampleHeightWidthMax 100 --out outImage imageFile

$ sips --resampleHeightWidthMax 100 --out out.jpg test.jpg
/test.jpg
/out.jpg

結構使えるオプションかも知れません。DTP系の言葉で言えば(?)縦横100ピクセルのボックスにフィットするように画像を縮小する言えばよいでしょうか。

ちなみに、これらのコマンドは実際の画像のピクセル数が指定より小さい場合、逆に拡大されてしまうので要注意です。

サーバー用途?

sipsコマンドは必要最低限な実装になっています。ただ、将来的にはフィルターやレイヤーを扱えるようにしたいとAppleさんも言ってます*1ので、そのうち何とかなるかもしれません。


アプリケーションサーバで使うとしたら、アップロードされた画像のフォーマットチェックとか、画像のリサイズ等に使えますね。
ただ、個人的に使えない理由の一つに、グレースケールに変換等の処理が出来ない事です。あー、これさえあれば今すぐにでも使うのになぁ。やっぱりQuartz2D APIを叩かないと駄目かなぁ。

(まとめ)JavaでXMLをフォーマットする

結果をまとめてみました

必要ない場合はXMLを読み込む所でDOMを作らないようにし、XMLをフォーマットするという事だけに焦点を絞ってコードを書き直してみました。

検証した環境

XSLスタイルシートでXSLする方法(id:toolkit:20060719)

備考
JDKだけで稼動します。単なるXSLです。

サンプルソース

import java.io.File;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class JDKXSLTTest01 {

	public static void main(String[] args) throws Exception {
		// フォーマットしたいXML
		File inXml = new File("in.xml");
		// フォーマットしたXML
		File outXml = new File("out.xml");
		// インデントをつけるスタイルシート
		File xsl = new File("style.xsl");

		// XSLT付きのTransformerを用意する。
		TransformerFactory transFactory = TransformerFactory.newInstance();
		Source xslSource = new StreamSource(xsl);
		Transformer transformer = transFactory.newTransformer(xslSource);

		Source source = new StreamSource(inXml);
		Result result = new StreamResult(outXml);

		// outXmlに書き出す。
		transformer.transform(source, result);
	}

}

style.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xalan="http://xml.apache.org/xslt">
	<xsl:output method="xml" encoding="UTF-8"
		indent="yes" xalan:indent-amount="2"/>
	<xsl:template match="/">
		<xsl:copy-of select="."/>
	</xsl:template>
</xsl:stylesheet>

Xercesを使って、DOM Level3 APIを使用する方法(id:toolkit:20060720)

備考
JDKの実装では、format-pretty-printerパラメータは実装されていないので、Xerces 2.8 が必要。次のJDKでは問題が解決されるみたい。

サンプルソース

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;

public class LSSerializerTest01 {

	public static void main(String[] args) throws Exception {
		// フォーマットしたいXML
		File inXml = new File("in.xml");
		// フォーマットしたXML
		File outXml = new File("out.xml");

		// フォーマットしたいXMLのDOMを作る
		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
				.newInstance();
		DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
		InputStream inStream = new FileInputStream(inXml);
		Document document = docBuilder.parse(inStream);
		inStream.close();

		// DOMImplementationLSを取得する
		DOMImplementation domImpl = document.getImplementation();
		DOMImplementationLS domImplLS = (DOMImplementationLS) domImpl
				.getFeature("LS", "3.0");

		LSOutput lsOutput = domImplLS.createLSOutput();
		LSSerializer lsSer = domImplLS.createLSSerializer();
		OutputStream out = new FileOutputStream(outXml);
		lsOutput.setByteStream(out);

		// LSSerializerのDOMConfigurationにformat-pretty-printをセットする
		lsSer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
		lsSer.write(document, lsOutput);
		out.close();
	}

}

Xalanを使って、OutputPropertiesFactory.S_KEY_INDENT_AMOUNTを指定する方法(id:toolkit:20060721)

備考
JDKの実装ではうまく行かないので、Xalanが必要。OutputPropertiesFactoryは com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory でも、 org.apache.xml.serializer.OutputPropertiesFactory でもどちらでもうまく行く。

サンプルソース

import java.io.File;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory;

public class OutputPropertiesFactoryTest01 {

	public static void main(String[] args) throws Exception {
		// フォーマットしたいXML
		File inXml = new File("in.xml");
		// フォーマットしたXML
		File outXml = new File("out.xml");

		// Transformerを用意する。
		TransformerFactory transFactory = TransformerFactory.newInstance();
		Transformer transformer = transFactory.newTransformer();
		transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		transformer.setOutputProperty(OutputKeys.METHOD, "xml");
		
		// OutputPropertiesFactory.S_KEY_INDENT_AMOUNTをセットする
		transformer.setOutputProperty(
				OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2");

		Source source = new StreamSource(inXml);
		Result result = new StreamResult(outXml);

		// outXmlに書き出す。
		transformer.transform(source, result);
	}

}


個人的な感想としては、パフォーマンスの問題を抜きにして考えると、スタイルシートを使う方法が一番良いのかな?と思います。
今後もそのまま使える方法だし、XSLT出来るし。



いやぁ、しかし、いろんな方からツッコミいただき、ありがたい次第です。いろいろ勉強になりました。
オレンジニュース(http://secure.ddo.jp/~kaku/tdiary/)からリンク張られると、アクセスが急激にアップしてちょっとビビりますけど(笑)。