/**
  * MyJaxpDomParser.java
  * @author Thomas Rowland
  * @version 02-08-03
  */
package xml.musicCatalog;

import java.io.*;
import futils.*;
import java.util.*;

// JAXP
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

// DOM
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.DOMException;

// SAX Exceptions
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/*
 *  DomParser program demonstrating how to parse an xml file 
 * using the DOM Level 2 api and JAXP 1.2. You need to have 
 * jaxp-api.jar and xercesImpl.jar in your classpath.
 */
public class MyJaxpDomParser {
    private String xmlFile = null;
    private PrintStream out = null;
    private Document domDoc = null;
    private Element root = null;

    // Node type constants
    final int ELEMENT_NODE      = 1;
    final int ATTR_NODE         = 2;
    final int TEXT_NODE         = 3;
    
    // Constants used for JAXP 1.2 DocumentBuilderFactory attributes
    static final String JAXP_SCHEMA_LANGUAGE =
        "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    static final String W3C_XML_SCHEMA =
        "http://www.w3.org/2001/XMLSchema";
    static final String JAXP_SCHEMA_SOURCE =
        "http://java.sun.com/xml/jaxp/properties/schemaSource";

    /*
     * Parses an XML Document with no validation.
     * @args uri the uri of the xml source
     * @args os the OutputStream to send the output to
     * @return the DOM Document
     */
    public Document parse (String uri, OutputStream os)         
        throws Exception {
        xmlFile = uri;
        out = new PrintStream(os);
        return parse(false, false);
    }

    /*
     * Parses an XML Document with DTD validation.
     * @args uri the uri of the xml source
     * @args os the OutputStream to send the output to
     * @return the DOM Document
     */
    public Document dtdParse (String uri, OutputStream os)      
        throws Exception {
        xmlFile = uri;
        out = new PrintStream(os);
        return parse(true, false);
    }

    /**
     * Parses an XML Document with XMLSchema validation.
     * @args uri the uri of the xml source
     * @args os the OutputStream to send the output to
     * @return the DOM Document
     */
    public Document xsdParse (String uri, OutputStream os)      
        throws Exception {
        xmlFile = uri;
        out = new PrintStream(os);
        return parse(false, true);
    }

    /*
     * Parses an XML Document with no validation, 
     * DTD validation, or XSD validation.
     * @args dtdValidate DTD validation feature setting on or off
     * @args dtdValidate DTD validation feature setting on or off
     * @return the DOM Document
     */
    private Document parse (boolean dtdValidate, boolean xsdValidate) 
        throws Exception {
        try {
            //out = new PrintStream(os);
            DocumentBuilderFactory dbf = 
                DocumentBuilderFactory.newInstance();
            dbf.setIgnoringElementContentWhitespace(false);
            
            /*  Set the validation mode to either: 
                no validation, DTD validation, or XSD validation */
            dbf.setValidating(dtdValidate || xsdValidate);
            if (xsdValidate) {
                dbf.setNamespaceAware(true);
                try {
                    dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
                } 
                catch (IllegalArgumentException e) {
                    // Parser does not support JAXP 1.2
                    System.err.println("Error: " + JAXP_SCHEMA_LANGUAGE 
                    + " is not recognized by the DocumentBuilderFactory.");
                    System.exit(1);
                }
            
                /*  You can also explicitly indentify the XMLSchema source file as 
                    follows, however we defined it within the XML file itself */
                String schemaSource = null;
                if (schemaSource != null)
                    dbf.setAttribute(JAXP_SCHEMA_SOURCE, new File(schemaSource));
            }

            //*******************************************
            DocumentBuilder db = dbf.newDocumentBuilder();
            db.setErrorHandler(new MyErrorHandler());
            domDoc = db.parse(xmlFile);
            root = domDoc.getDocumentElement();
            traverse(root);
            out.flush();
            return domDoc;
        } 
        
        catch (FactoryConfigurationError e) {
            // Unable to create the factory
            throw new Exception(
                "** FactoryConfigurationError\n" 
                + e.getMessage());
        }
        catch (ParserConfigurationException e) {
            // Factory unable to create the parser.
            throw new Exception(
                "** ParserConfigurationException\n" 
                + e.getMessage());
        }
        catch (SAXException e) {
            // Exceptions thrown by the parser
            // Get the wrapped exception, if any
            Exception ex = e.getException();
            String msg = null;
            if (ex != null) {
                ex.printStackTrace();
                msg = ex.getMessage();
            }
            throw new Exception(
                "** SAXException:\n" + e.getMessage());
        }
        catch (IOException e) {
            throw new Exception(
                "IOException:\n" + e.getMessage());
        }   
    }
        
    
    /**
     *  Recursive method which traverses the DOM Document
     *  and outputs the content.
     *  @args elem the root element to begin recursion on
     */
    private void traverse (Node elem) {
        try {
        
            //handle the attributes
            if (elem.hasAttributes()) {
                NamedNodeMap attrs = elem.getAttributes();
                int alength = attrs.getLength();
                
                //loop
                for (int i=0; i<alength; i++) {
                    Attr attr = (Attr) attrs.item(i);
                    out.println (attr.getName() + ":\t" + attr.getValue());
                }
            }
            //handle the child nodes
            if (elem.hasChildNodes()) {
                NodeList children = elem.getChildNodes();
                int length = children.getLength();
                
                //loop
                for (int i=0; i<length; i++) {
                    Node n = children.item(i);
                    String name = n.getNodeName();
                    
                    if (n.getNodeType() == ELEMENT_NODE) {
                        if (name.equals("Item"))
                            out.println("\n");
                        else
                            out.print(name + ":\t");
                            
                        traverse(n); //recurse
                    }
                    else
                    if (n.getNodeType() == TEXT_NODE) {
                        String txt = n.getNodeValue().trim();
                        if (! txt.equals("")) {
                            out.println(txt);
                        }
                    }
                }
                return;
            }
        }
        catch (DOMException e) {
            System.out.println("*** DOMException\n"
                                + e.getMessage());
        }
    }
    
    /**
     *  Creates and returns a new Item object
     *  @return the new Item
     */
    public Item createItem () {
        return new Item();
    }


    /**
     *  Adds a newly created Item object to the DOM Document
     *  @args item the Item object
     */
    public void addItem (Item item) throws Exception {
        
        Element itemElem = null;
        Element elem = null;
        Text txtNode = null;
        
        itemElem = domDoc.createElement("Item");
        itemElem.setAttribute("media", item.getMedia());
        root.appendChild(itemElem);

        elem = domDoc.createElement("Artist");
        txtNode = domDoc.createTextNode(item.getArtist ());
        elem.appendChild(txtNode);
        itemElem.appendChild(elem);

        elem = domDoc.createElement("Title");
        txtNode = domDoc.createTextNode(item.getTitle());
        elem.appendChild(txtNode);
        itemElem.appendChild(elem);

        elem = domDoc.createElement("Year");
        txtNode = domDoc.createTextNode(item.getYear());
        elem.appendChild(txtNode);
        itemElem.appendChild(elem);

        //...Write code to add the <members> elements
        
        traverse(root);
    }
    

    /**
     *  Writes a DOM Document as HTML
     */
    public void toHtml (File stylesheet) throws Exception {
        
        //try {
            System.out.println("\nXML File: " + xmlFile);
            String fn = xmlFile.substring(0, xmlFile.lastIndexOf("."));
            File htmlFile = new File(fn + ".html");
            BufferedWriter out = new BufferedWriter(
                                 new FileWriter(htmlFile));
                                 
            System.out.println("\nStylesheet: " + stylesheet);
            System.out.println("\nOutput: " + htmlFile);
            //OutputStream out = new FileOutputStream(htmlFile);
            
            XmlTransformer.transform(domDoc, stylesheet, out);
            //out.flush();
            //out.close();
        //} catch (IOException e) {
            //e.printStackTrace();
        //}
    }
    
    
    /**
     *  Writes a DOM Document as XML
     */
    public void toXml () 
        throws Exception {
        XmlTransformer.writeXml(domDoc, xmlFile);
        System.out.println("\nOutput should have been written to : " 
                            + xmlFile);
    }

    /**
     * Inner class Error Handler to report validation errors 
     * and warnings. DOM uses these SAX error handlers.
     */
    private class MyErrorHandler implements ErrorHandler {

        //-- The following methods are standard SAX ErrorHandler methods.
        public void warning (SAXParseException spe) throws SAXException {
            System.out.println("Warning: " + getParseExceptionInfo(spe));
        }
        
        public void error (SAXParseException spe) throws SAXException {
            String message = "Error: " + getParseExceptionInfo(spe);
            throw new SAXException(message);
        }

        public void fatalError (SAXParseException spe) throws SAXException {
            String message = "Fatal Error: " + getParseExceptionInfo(spe);
            throw new SAXException(message);
        }
        
        /**
         * Returns a string describing parse exception details
         */
        private String getParseExceptionInfo (SAXParseException spe) {
            String systemId = spe.getSystemId();
            if (systemId == null) {
                systemId = "null";
            }
            String info = "URI=" + systemId +
                "\nLine=" + spe.getLineNumber() +
                "\n" + spe.getMessage();
            return info;
        }
    }

}//