package org.itscool.commons.document;

import java.io.ByteArrayOutputStream;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;

public class XmlDocument extends AbstractDocument{
    protected Document document;
    
    /**
     * メンバに登録されているドキュメントを取得します。
     * @return このオブジェクトに登録されているドキュメントを返します
     */
    public Document getDocument() {
        return document;
    }
    
    /**
     * ルート名「rootName」で指定した新しいDocumentインスタンスを
     * 作成します
     * @param rootName このドキュメントのルート要素の名前
     * @throws Exception
     */
    public XmlDocument(String rootName) throws Exception{
        
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        DOMImplementation domImpl = builder.getDOMImplementation();
        Document document = domImpl.createDocument("",rootName,null);
        init(document);
    }
    
    /**
     * 指定されたDocumentインスタンスを使います
     * @param document 既存のDocumentインスタンス
     * @throws Exception
     */
    public XmlDocument(Document document) throws Exception{
        init(document);
    }
    
    /**
     * 指定されたDocumentオブジェクトをメンバにセットします。<br>
     * 又、JDK1.5以上の環境を使用している場合は、システムプロパティ
     * javax.xml.transform.TransformerFactoryを更新します。
     * @param document メンバにセットするDocumentインスタンス
     */
    protected void init(Document document){
        String version = System.getProperty("java.version");
        if( version.indexOf("1.4") != 0 ){
            System.setProperty("javax.xml.transform.TransformerFactory",
                "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
        }
        this.document = document;
    }
    
    /**
     * TransformerFactoryクラス
     *
     * XMLをXSLTするのに、Transformer クラスを使います。
     * これは TransformerFactoryクラスから 実装を取り出せます。
     * TransformerFactory.newInstance();
     *
     * JDK1.4 では org.apache.xalan.processor.TransformerFactoryImpl JDK5.0 では
     * com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
     *
     * が返ってくるようです。JDK5.0になって com.sun パッケージ の実装になってみたいですね。
     *
     * ･･･が、JDK5.0 でも xalan.jar をクラスパスに含めて実行すると、
     *
     * org.apache.xalan.processor.TransformerFactoryImpl
     *
     * になります。 この理由は JDKのAPIDocにあります〜 APIドキュメントによると
     * 探し出す手順は以下になります。
     *
     * javax.xml.transform.TransformerFactory システムプロパティを使用する JRE
     * ディレクトリ内のプロパティファイル lib/jaxp.properties を使用する。
     * この構成ファイルは標準のjava.util.Properties 形式であり、上記のように定
     * 義されたシステムプロパティであるキーを持つ実装クラスの完全指定の名前を含む
     *
     * 可能であれば、JAR 仕様で詳細に説明されているサービス API を使用して、
     * クラス名を判定する。 サービス API は、実行時に使用できる
     * jar 内の META-INF/services/javax.xml.transform.TransformerFactory ファイルから
     * クラス名を検索する
     *
     * プラットフォームのデフォルトの TransformerFactory インスタンス
     *
     * xalan.jar の 中を見てみると･･･ ありますね、
     *
     * META-INF/services/javax.xml.transform.TransformerFactoryのファイルが。
     *
     * この中身を見てみると org.apache.xalan.processor.TransformerFactoryImpl
     *
     * と記述されてます。･･･これが有効になってたわけですね。
     *
     * Factoryクラスによって、どの実装クラスを使うか？ が指定できるのは便利な
     * 機能なんですが、それによって ハマることも。
     *
     * たとえば、 tomcat5 上で 動かす Servlet にて Transformerをつかって XSLTし
     * ている プログラム。
     *
     * JDK1.4でtomcatを動かすと、うまく動くのに、 JDK5.0でtomcatを動かすと、
     * Provider org.apache.xalan.processor.TransformerFactoryImpl not Found.
     * ってエラーがでる。
     * ････なーんて、悩んだ方いらっしゃるのでは？
     *
     * これまでの話で ピンっと来た人も多い？
     *  そぅ、この理由って tomcat自体が使っている TransformerFactoryの実装は
     * org.apache.xalan.processor.TransformerFactoryImpl
     * を使っていたからだと思います。
     * JDK1.4では、たまたま(?) 上記クラスが JDKの中に含まれていたので大丈夫でし
     * たが、JDK5.0では 含まれていないので エラーになったんですね。
     *
     * この場合の解決方法としては 大きく2種類。
     *
     * 一つ目は org.apache.xalan.processor.TransformerFactoryImpl がある
     * xalan.jar とかをtomcat上のlibにいれちゃって、
     * org.apacheパッケージを使うようにしちゃう。
     *
     * 二つ目は JDK5.0に含まれている
     * com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
     * を使ってくれるような設定にしちゃう。
     *
     * 設定方法は JDKのAPIドキュメント通りなんですが、方法によりけりtomcatに
     * 影響があるかもしんない。
     *
     * たとえば、システムプロパティに追加
     * System.setProperty("javax.xml.transform.TransformerFactory",
     * "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
     * って方法や JREのlib/jaxp.properties に追加する方法。
     *
     * 一応できるけど、システムプロパティに追加した時点で、
     * tomcatを含むそのVM上の全てのプログラムに影響がいく？
     *
     * んじゃー無難な線として 作成したJARの
     * META-INF/services/javax.xml.transform.TransformerFactory のファイルで
     * com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
     * 指定しちゃう。 JDK1.4で動かされたときには アウト ですけどもね。。
     *
     * JDK1.4、5.0両方で動作OKのものを作るためには･･･ ほんと妥協して
     * Factoryクラスを使うんじゃなくて、使いたい
     * Transformer クラスを自分でインスタンスしちゃう
     * のが一番確実かもしんないなぁー
     *
     * あー この部分ってほんと面倒です。
     *
     * 今 TomcatとJDKの組み合わせをみつつ 動作確認しているんですが、
     * なんか 異様に疲れてきた・・・・
     *
     */
    public String parseText(String encoding) throws Exception{
        DOMSource source = new DOMSource(document);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        TransformerFactory transFactory = TransformerFactory.newInstance();
        Transformer transformer = transFactory.newTransformer();
        
        if( encoding == null || encoding.length() == 0 ){
            encoding = "utf-8";
        }
        //出力のエンコーディングをセット
        Properties properties = new Properties();
        properties.setProperty("encoding", encoding);
        transformer.setOutputProperties(properties);
        transformer.transform(source, new StreamResult(os));
        
        return new String(os.toByteArray(), encoding);
    }
    
    public String parseTextIndent(String encoding) throws Exception{
        DOMSource source = new DOMSource(document);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        TransformerFactory transFactory = TransformerFactory.newInstance();
        Transformer transformer = transFactory.newTransformer();
        
        if( encoding == null || encoding.length() == 0 ){
            encoding = "utf-8";
        }
        //出力のエンコーディングをセット
        Properties properties = new Properties();
        properties.setProperty("encoding", encoding);
        transformer.setOutputProperties(properties);
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(source, new StreamResult(os));
        return new String(os.toByteArray(), encoding);
    }
    
    public String parseText() throws Exception{
        return parseText(null);
    }
}
