package daruma.xml.util;

import daruma.xml.util.DOMDocumentFactory;
import daruma.util.Pair;

import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Document;

import java.io.OutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.StringReader;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.InputSource;
import daruma.xml.handler.XSAXDOMCreateHandler;

import org.apache.xml.serializer.OutputPropertiesFactory;

import daruma.util.Itk;

public class XMLFormatConverter
{
	private	XMLFormatConverter()
	{
	}

	/* !!! [06/08/18 23:19 I.Noda] !!! 
	 * 余計な出力を削るため、オプションを増やした。
	 */
    
	public	static	void	print( Node  node ,  OutputStream  out )
	    throws TransformerException
	{
	    print(node, out, true) ;
	}
	
	public	static	void	print( Node  node ,  
				       OutputStream  out, 
				       boolean withDocDeclP)
						throws TransformerException
	{
	    convert(new DOMSource(node), out, withDocDeclP) ;
	}

	public	static	void	print( Node  node ,  
				       OutputStream  out, 
				       boolean withDocDeclP,
				       String  encoding )
					    throws TransformerException ,
						   UnsupportedEncodingException
	{
	    convert(new DOMSource(node),
		    new BufferedWriter(new OutputStreamWriter(out,
							      encoding)),
		    withDocDeclP) ;
	}

    //--------------------------------------------------
    /**
     * 汎用コンバータ
     * Source から Stream へ出力
     */
    public static void convert( Source source,
				OutputStream out,
				boolean withDocDeclP )
	throws TransformerException
    {
	convert(source, 
		new PrintWriter(out),
		withDocDeclP) ;

	/* stream では flush しないと悲しいことになる。
	 */
	try {
	    out.flush();
	} catch( IOException  e ) {
	    throw new TransformerException(e) ;
	}
    }
	
    //--------------------------------------------------
    /**
     * 汎用コンバータ
     * Reader から Writer へ出力
     */
    public static void convert( Reader in,
				Writer out,
				boolean withDocDeclP )
	throws TransformerException
    {
	convert(new StreamSource( in ), out, withDocDeclP) ;

	/* stream では flush しないと悲しいことになる。
	 */
	try {
	    out.flush();
	} catch( IOException  e ) {
	    throw new TransformerException(e) ;
	}
    }
	
    //--------------------------------------------------
    /**
     * 汎用コンバータ
     * Source から Writer へ出力
     */
    public static void convert( Source source,
				Writer out,
				boolean withDocDeclP )
	throws TransformerException
    {
	convert(source, 
		new StreamResult( out ),
		withDocDeclP) ;

	/* stream では flush しないと悲しいことになる。
	 */
	try {
	    out.flush();
	} catch( IOException  e ) {
	    throw new TransformerException(e) ;
	}
    }
	
    //--------------------------------------------------
    /**
     * 汎用コンバータ
     * Source から Result へ。
     */
    public static void convert( Source source,
				Result result,
				boolean withDocDeclP )
	throws TransformerException
    {
	TransformerFactory  factory = TransformerFactory.newInstance();
	Transformer	transformer;
	
	try {
	    transformer = factory.newTransformer();
	} catch( TransformerConfigurationException  te ) {
	    throw te;
	}

	transformer.setOutputProperty( OutputKeys.INDENT , "yes" );
	/* !!! [06/08/20 22:31 I.Noda] !!! 
	 * インデントをちゃんとするために。
	 * 正しい対処方法なのか良くわからない。
	 * 情報は以下から。
	 * http://forum.java.sun.com/thread.jspa?threadID=562510&tstart=0
	 */
	/* <<< [06/09/07 01:57 I.Noda] <<< 
	 * indent-amount の設定、より汎用の方法と思われるもの。
	 * 情報源
	 * http://xml.apache.org/xalan-j/apidocs/org/apache/xml/serializer/OutputPropertiesFactory.html
	 * ただ、現状(2006.09.06)ではうまく動作していない。理由、不明。
	 */
	//transformer.setOutputProperty
	//    ("{http://xml.apache.org/xalan}indent-amount", "2"); 
	/* === [06/09/07 01:57 I.Noda] === */
	transformer.setOutputProperty
	    (OutputPropertiesFactory.S_KEY_INDENT_AMOUNT,"2");
	/* >>> [06/09/07 01:57 I.Noda] >>> */
		
	/* !!! [06/08/18 23:19 I.Noda] !!! 
	 * Doc Decl の部分の出力、オプションで指定。
	 */
	if(!withDocDeclP) {
	    transformer.setOutputProperty
		( OutputKeys.OMIT_XML_DECLARATION , "yes" );
	}

	try {
	    transformer.transform( source , result );
	} catch( TransformerException  te ) {
	    throw te;
	}
    }

    /* !!! [06/09/07 01:57 I.Noda] !!! */
    //--------------------------------------------------
	public	static	void	printElement( Node  element ,
					      OutputStream  out )
						throws TransformerException

	{
	    convert(new DOMSource(element), out, false) ;
	}

    /* !!! [06/09/07 01:57 I.Noda] !!! 
     * 以下はいずれ消去
     */
    //--------------------------------------------------
	public	static	void	obsolute_printElement( Node  element ,
					      OutputStream  out )
						throws TransformerException

	{
		TransformerFactory  factory = TransformerFactory.newInstance();
		Transformer	transformer;
		try
		{
			transformer = factory.newTransformer();
		}
		catch( TransformerConfigurationException  te )
		{
			throw te;
		}

		transformer.setOutputProperty
			( OutputKeys.INDENT , "yes" );
		transformer.setOutputProperty
			( OutputKeys.OMIT_XML_DECLARATION , "yes" );
		/* !!! [06/08/20 22:31 I.Noda] !!! 
		 * インデントをちゃんとするために。
		 * 正しい対処方法なのか良くわからない。
		 * 情報は以下から。
		 * http://forum.java.sun.com/thread.jspa?threadID=562510&tstart=0
		 */
		transformer.setOutputProperty
		    ("{http://xml.apache.org/xalan}indent-amount", "2"); 

		DOMSource	source = new DOMSource( element );
		StreamResult	result = new StreamResult
						( new PrintWriter( out ) );

		try
		{
			transformer.transform( source , result );

			try
			{
				out.flush();
			}
			catch( IOException  e )
			{
			}
		}
		catch( TransformerException  te )
		{
			throw te;
		}
	}


    /* !!! [06/09/07 01:57 I.Noda] !!! */
    //-------------------------------------------------
	public	static	void	printTextWithoutXMLDeclaration
				( Reader  in ,  Writer  out )
						throws TransformerException
	{
	    convert(in, out, false) ;
	}


    /* !!! [06/09/07 01:57 I.Noda] !!! 
     * 以下はいずれ消去
     */
    //-------------------------------------------------
	public	static	void	obsolute_printTextWithoutXMLDeclaration
				( Reader  in ,  Writer  out )
						throws TransformerException
	{
		TransformerFactory  factory = TransformerFactory.newInstance();
		Transformer	transformer;
		try
		{
			transformer = factory.newTransformer();
		}
		catch( TransformerConfigurationException  te )
		{
			throw te;
		}

		transformer.setOutputProperty
			( OutputKeys.INDENT , "yes" );
		transformer.setOutputProperty
			( OutputKeys.OMIT_XML_DECLARATION , "yes" );

		StreamSource	source = new StreamSource( in );
		StreamResult	result = new StreamResult( out );

		try
		{
			transformer.transform( source , result );

			try
			{
				out.flush();
			}
			catch( IOException  e )
			{
			}
		}
		catch( TransformerException  te )
		{
			throw te;
		}
	}


    /* !!! [06/09/07 01:57 I.Noda] !!! */
    //--------------------------------------------------
	public	static	void	readNode( javax.xml.transform.Source  in ,
					  Node  node )
						throws TransformerException
	{
	    convert(in, new DOMResult(node), false) ;
	}

    /* !!! [06/09/07 01:57 I.Noda] !!! 
     * 以下はいずれ消去
     */
    //--------------------------------------------------
	public	static	void	obsolute_readNode( javax.xml.transform.Source  in ,
					  Node  node )
						throws TransformerException
	{

		TransformerFactory  factory = TransformerFactory.newInstance();
		Transformer	transformer;
		try
		{
			transformer = factory.newTransformer();
		}
		catch( TransformerConfigurationException  te )
		{
			throw te;
		}

		transformer.setOutputProperty
			( OutputKeys.INDENT , "yes" );
		transformer.setOutputProperty
			( OutputKeys.OMIT_XML_DECLARATION , "yes" );

		javax.xml.transform.Source	source = in;
		DOMResult			result = new DOMResult( node );

		try
		{
			transformer.transform( source , result );
		}
		catch( TransformerException  te )
		{
			throw te;
		}
	}

	public	static	void	readNode( InputStream  in ,
					  Node  node )
						throws TransformerException
	{
		XMLFormatConverter.readNode( new StreamSource( in ) , node );
	}

	public	static	void	readNode( Reader  in ,
					  Node  node )
						throws TransformerException
	{
		XMLFormatConverter.readNode( new StreamSource( in ) , node );
	}


    /* !!! [06/08/17 15:14 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * XML Node の文字列を生成する。
     */
    public static String toString(Node node)
	throws TransformerException
    {
	return toString(node, true) ;
    }

    public static String toString(Node node, boolean withDocDeclP)
	throws TransformerException
    {
	ByteArrayOutputStream ostrm = new ByteArrayOutputStream() ;
	print(node, ostrm, withDocDeclP) ;
	return ostrm.toString() ;
    }


	public	static	Element	stringToDOMElemnt( String  str )
						throws TransformerException
	{
		return( XMLFormatConverter
			.stringToDOMElement( str , (Document)null ) );
	}

	public	static	Element	stringToDOMElement( String  str ,
						    Document  doc )
						throws TransformerException
	{
		return( XMLFormatConverter.streamToDOMElement
			( new StringReader( str ) , doc ) );
	}

	public	static	Element	streamToDOMElement( InputStream  in )
						throws TransformerException
	{
		Document	doc;

		try
		{
			doc = DOMDocumentFactory.create();
		}
		catch( ParserConfigurationException  e )
		{
			throw new TransformerException( e );
		}

		return( XMLFormatConverter.streamToDOMElement( in , doc ) );
	}

	public	static	Element	streamToDOMElement( InputStream  in ,
						    Document  doc )
						throws TransformerException
	{
		//
		// create parser
		//
		SAXParserFactory	f = SAXParserFactory.newInstance();
		f.setNamespaceAware( true );

		XMLReader		parser;
		XSAXDOMCreateHandler	h;

		try
		{
			parser = f.newSAXParser().getXMLReader();

			h = new XSAXDOMCreateHandler
				( new ByteArrayOutputStream() ,
				  parser , true , doc );

			parser.setContentHandler( h );
			parser.setErrorHandler( h );
		}
		catch( ParserConfigurationException  e )
		{
			throw new TransformerException
				  ( "Can't create SAX parser" , e );
		}
		catch( SAXException  e )
		{
			throw new TransformerException
				  ( "Can't create SAX parser" , e );
		}


		//
		// parse
		//
		try
		{
			try
			{
				parser.parse( new InputSource( in ) );
			}
			catch( EndOfDocumentException  e )
			{
				// OK, expected
			}
		}
		catch( SAXParseException  e )
		{
			throw new TransformerException( e );
		}
		catch( SAXException  e )
		{
			throw new TransformerException( e );
		}
		catch( IOException  e )
		{
			e.printStackTrace();

			throw new TransformerException( e );
		}

		return( h.getDocumentElement() );
	}

	public	static	Element	streamToDOMElement( Reader  reader ,
						    Document  doc )
						throws TransformerException
	{
		//
		// create parser
		//
		SAXParserFactory	f = SAXParserFactory.newInstance();
		f.setNamespaceAware( true );

		XMLReader		parser;
		XSAXDOMCreateHandler	h;

		try
		{
			parser = f.newSAXParser().getXMLReader();

			h = new XSAXDOMCreateHandler
				( new ByteArrayOutputStream() ,
				  parser , true , doc );

			parser.setContentHandler( h );
			parser.setErrorHandler( h );
		}
		catch( ParserConfigurationException  e )
		{
			throw new TransformerException
				  ( "Can't create SAX parser" , e );
		}
		catch( SAXException  e )
		{
			throw new TransformerException
				  ( "Can't create SAX parser" , e );
		}


		//
		// parse
		//
		try
		{
			try
			{
				parser.parse( new InputSource( reader ) );
			}
			catch( EndOfDocumentException  e )
			{
				// OK, expected
			}
		}
		catch( SAXParseException  e )
		{
			throw new TransformerException( e );
		}
		catch( SAXException  e )
		{
			throw new TransformerException( e );
		}
		catch( IOException  e )
		{
			e.printStackTrace();

			throw new TransformerException( e );
		}

		return( h.getDocumentElement() );
	}

	public	static	Element	stringToElement( String  str ,  Document  doc )
						  throws TransformerException
	{
		return stringToDOMElement( str , doc ) ;
	}


    /* !!! [06/08/18 23:19 I.Noda] !!! */
    //------------------------------------------------------------
    /**
     * あるノードを書き出した際に、特定の文字列で前後をわけ、
     * その結果をペアにして返す。
     * もし特定の文字列が見付からない場合、後者が null となる。
     */
    
    public static Pair<String, String> 
	splitToHeaderTailerStrBy(Node node, String separator,
				 boolean withDocDeclP) 
	throws TransformerException
    {
	String nodeStr = toString(node, withDocDeclP) ;
	int sepIndex = nodeStr.indexOf(separator) ;

	String header = null;
	String tailer = null;

	if(sepIndex < 0) {
	    header = nodeStr ;
	    tailer = null ;
	} else {
	    int sepLength = separator.length() ;
	    header = nodeStr.substring(0,sepIndex) ;
	    tailer = nodeStr.substring(sepIndex + sepLength) ;
	}
	
	return new Pair<String,String>(header,tailer) ;
    }
    

}
