
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;

/**
 * This class has nothing to do with the current version of Azure browser.
 * It's included to represent the other approach to browsing XHTML
 * pages. After all, Azure was originally supposed to be an XHTML browser.
 * Learning how to use the Java API for XML Parsing (JAXP) and writing this
 * class have also taken a very significant part of the time I had in my
 * hands for the Azure project. Having written this code, played around with
 * it and tried until the last day to find out how it could get me any closer
 * to actually turning the XHTML tags into a hypermedia document (ie. a
 * browsable web page), I finally could honestly say it wouldn't happen during
 * this project.
 * <p>
 * Currently, <code>AzureXHTMLHandler</code> can be used to parse the contents
 * of some <code>InputSource</code> (which can easily be created from an
 * <code>InputStream</code> or a <code>Reader</code>) and return those
 * parsed contents in a <code>String</code>. By tweaking the methods of this
 * class the parsed text can be adjusted. It would be easy to filter away
 * tags that the current HTMLEditorKit in Java API doesn't support or that it
 * doesn't use correctly. I chose not to use it even for that in the
 * current Azure browser because it wouldn't be all that neat and it's hard
 * to be sure if the computer used to demonstrate the application even would
 * understand these quite new JAXP classes. Anyway, actually showing the page
 * would then still not be my doing. Having to rely on the inadequate HTML
 * support is the thing I haven't been able to get away from. Making my own
 * XHTMLEditorKit turned out to mean writing my own versions of the numerous
 * HTML classes in the API.
 *
 * @author Pekka Ryynänen
 */
public class AzureXHTMLHandler extends DefaultHandler {

    private SAXParser parser;
    private String parsedText;

    /**
     * Constructs an <code>AzureXHTMLHandler</code> with a validating and
     * namespace aware SAX parser.
     *
     * @exception ParserConfigurationException if a parser cannot be created
     *                           which satisfies the requested configuration
     * @exception SAXException Any SAX exception, possibly wrapping another
     *                         exception.
     */
    public AzureXHTMLHandler()
	throws ParserConfigurationException, SAXException {
	SAXParserFactory factory = SAXParserFactory.newInstance();
	factory.setValidating(true);
	factory.setNamespaceAware(true);
	parser = factory.newSAXParser(); // might cause the Exceptions
    }
    
    /**
     * Parses the <code>InputSource</code> using <code>AzureXHTMLHandler</code>
     * as <code>ContentHandler</code>, <code>DTDHandler</code>,
     * <code>ErrorHandler</code> and <code>EntityResolver</code>.
     *
     * @param is the <code>InputSource</code> to be parsed
     * @return <code>String</code> of the parsed text from <code>is</code>
     */
    public String parse(InputSource is) {
	parsedText = "";
     	try {
	    parser.parse(is, this);
	} catch(Throwable t) {
	    t.printStackTrace();
	}
	return parsedText;
    }

    /**
     * Adds the element's start tag to the parsed text with any attributes.
     * Called by the parser when handling start tags.
     *
     * @param lName The local name (without prefix) of element, or the empty
     *              string if Namespace processing is not being performed.
     * @param qName The qualified XML 1.0 name (with prefix), or the empty
     *              string if qualified names are not available.
     * @param attrs The specified or defaulted attributes.
     * @exception SAXException Any SAX exception, possibly wrapping another
     *                         exception.
     */
    public void startElement(String namespaceURI,
                             String lName, // local name
                             String qName, // qualified name
			     Attributes attrs) throws SAXException {
        String eName = lName; // element name
        if ("".equals(eName)) // in case namespaceAware is false
	    eName = qName;
        parsedText += "<" + eName;
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength(); i++) {
                String aName = attrs.getLocalName(i); // attribute name 
                if ("".equals(aName))
		    aName = attrs.getQName(i);
                parsedText += " ";
                parsedText += aName + "=\"" + attrs.getValue(i) + "\"";
            }
        }
        parsedText += ">";
    }

    /**
     * Adds the element's end tag to the parsed text.
     * Called by the parser when handling end tags. There will be a
     * corresponding <code>endElement</code> event to every
     * <code>startElement</code> event.
     *
     * @param lName The local name (without prefix) of element, or the empty
     *              string if Namespace processing is not being performed.
     * @param qName The qualified XML 1.0 name (with prefix), or the empty
     *              string if qualified names are not available.
     * @exception SAXException Any SAX exception, possibly wrapping another
     *                         exception.
     */
    public void endElement(String namespaceURI,
                           String lName, // simple name
                           String qName  // qualified name
			   ) throws SAXException {
        parsedText += "</" + lName + ">";
    }

    /**
     * Adds text to the parsed text.
     * Called by the parser when handling text content (not within tags).
     *
     * @param buf The character array.
     * @param offset The start position in the character array.
     * @param length The number of characters to use from the array.
     * @exception SAXException Any SAX exception, possibly wrapping another
     *                         exception.
     */
    public void characters(char buf[], int offset, int length)
    throws SAXException
    {
        String s = new String(buf, offset, length);
        parsedText += s;
    }

}

