/*
 * @(#)main.java	1.1 99/01/06
 *
 * Copyright (c) 1999 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.io.*;
import java.net.URL;
import org.xml.sax.InputSource;
import com.sun.xml.parser.Resolver;
import com.sun.xml.util.XmlChars;

public class main
{
    //
    // how many characters of pushback to accomodate when scanning
    // the XML declaration -- enough for the entire XML (or text)
    // declaration, except when excessive whitespace is used.
    //
    static final int	PUSHBACK_SIZE = 1024;

    //
    // Simple demonstration of character set transcoding, where a
    // document in one character set is converted into one in
    // another character set.
    //
    // Argument 0:	encoding to use for output
    // Argument 1:	file or URI to transcode
    //
    // Output is on System.out, in the specified encoding.  This
    // must be a Java encoding name; unfortunately at this time
    // the IANA standard encoding names aren't all accepted, even
    // when the encoding is itself supported.  (JDK 1.2 is better
    // than JDK 1.1 in this respect.)
    //
    // NOTE:  JDK 1.1 and JDK 1.2 do not expose the capability to
    // cause an I/O exception when writing characters which can't
    // be expressed in the output encoding.  This means that the
    // transcoding performed by this program may cause SILENT (!!)
    // errors; the non-encodable characters will be rendered as
    // ASCII question marks ("?") in the output.  (This should get
    // fixed in a future java release.)
    //
    // For example, attempting to transcode a document with Chinese,
    // Japanese, Korean, or Vietnamese text into US-ASCII will as
    // a rule produce lots of "????" text rather than reporting any
    // error in the transcoding.  However, EUC-JP can be readily
    // transcoded to ISO-2022-JP or UTF-8.
    //
    // All input encodings can be transcoded to UTF-8 or UTF-16.
    //
    public static void main (String argv [])
    {
	if (argv.length != 2) {
	    System.err.println ("Usage:  transcode encoding [file|uri]");
	    System.exit (1);
	}

	try {
	    InputSource		inSource;
	    PushbackReader	in;
	    Writer		out;

	    out = new OutputStreamWriter (System.out, argv [0]);
	    inSource = createInputSource (argv [1]);
	    in = new PushbackReader (inSource.getCharacterStream (),
		PUSHBACK_SIZE);

	    // Write the XML declaration, optionally followed
	    // by a new end-of-line if there was none already...
	    out.write ("<?xml version=\"1.0\" encoding=\"");
	    out.write (argv [0]);
	    out.write ("\"");
	    if (maybeStandaloneDecl (in, out))
		out.write ("?>");
	    else
		out.write ("?>"
		    + System.getProperty ("line.separator"));

	    // ... then transcode everything (modulo the errors
	    // that are silently swallowed by java.io) 
	    char		buf [] = new char [16 * 1024];
	    int			len;

	    for (;;) {
		len = in.read (buf, 0, buf.length);
		if (len < 0)
		    break;
		out.write (buf, 0, len);
	    }

	    out.close ();

	} catch (Throwable t) {
	    t.printStackTrace (System.err);
	}
	System.exit (0);
    }


    static private InputSource createInputSource (String fileOrUrl)
    throws IOException
    {
	File f = new File (fileOrUrl);

	if (f.exists ())
	    return Resolver.createInputSource (f);

	return Resolver.createInputSource (new URL (fileOrUrl), true);
    }


    // returns true if saw an XML decl -- avoid adding extra whitespace
    static private boolean maybeStandaloneDecl (
	PushbackReader	in,
	Writer		out
    ) throws IOException
    {
	char	xmlDecl [] = new char [PUSHBACK_SIZE];
	int	len = in.read (xmlDecl, 0, xmlDecl.length);

	for (;;) {
	    // 
	    // Must start with "<?xml" whitespace
	    //
	    if (len < 6)
		break;
	    if (xmlDecl [0] != '<'
		    || xmlDecl [1] != '?'
		    || xmlDecl [2] != 'x'
		    || xmlDecl [3] != 'm'
		    || xmlDecl [4] != 'l')
		break;
	    if (!XmlChars.isSpace (xmlDecl [5]))
		break;
	    
	    //
	    // OK, scan for "standalone" pseudo-attribute
	    //
	    int	cursor;

	    for (cursor = 6; cursor < len; cursor++) {
		if (XmlChars.isSpace (xmlDecl [cursor]))
		    continue;

		// assume legal XML 1.0 -- shortcuts the parsing!

		// terminate only at "?>"
		if (xmlDecl [cursor] == '?')
		    break;
		
		// only "standalone=" starts with 's'
		boolean	isStandalone = (xmlDecl [cursor++] == 's');

		// ... all values are singly or doubly quoted
		while (!(xmlDecl [cursor] == '\'' || xmlDecl [cursor] == '\"')
			&& cursor < len)
		    cursor++;
		cursor++;

		// ... 'standalone="no"' can be dropped; only
		// the 'yes' needs to be repeated...
		if (isStandalone && xmlDecl [cursor] == 'y')
		    out.write (" standalone=\"yes\"");
		
		// ... skip to the terminating quote
		while (!(xmlDecl [cursor] == '\'' || xmlDecl [cursor] == '\"')
			&& cursor < len)
		    cursor++;

		// continue till last "attribute"

		// XXX note:  extremely long XML declarations won't all
		// be buffered ... this happens when unhealthy amounts
		// of whitespace get used.  In such cases this routine
		// will fail by not skipping through the terminal "?>".
	    }
	    cursor += 2;
	    in.unread (xmlDecl, cursor, len - cursor);
	    return true;
	}
	in.unread (xmlDecl, 0, len);
	return false;
    }
}
