/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xalan" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, Lotus
 * Development Corporation., http://www.lotus.com.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
// This file uses 4 space indents, no tabs.

import net.sf.saxon.om.NamespaceConstant;
import net.sf.saxon.xpath.XPathEvaluator;
import org.jdom.input.SAXBuilder;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.*;
import java.io.File;
import java.io.FileInputStream;
import java.util.Iterator;
import java.util.List;


/**
 * Very basic utility for applying the  XPath API in JAXP 1.3
 * to an xml file and printing information about the execution of the XPath object
 * and the nodes it finds.
 * Takes 2 or 3 arguments:
 * (1) an xml filename
 * (2) an XPath expression to apply to the file
 * (3) (optionally) short name of the object model to be used: "saxon" | "dom" | "jdom" | "xom"
 * Examples:
 * java ApplyXPathJAXP foo.xml /
 * java ApplyXPathJAXP foo.xml /doc/name[1]/@last
 * <p/>
 * This version modified by Michael Kay to allow testing of additional
 * features of the interface.
 *
 * The XPath expression may use:
 * (a) A namespace prefix f which is bound to the namespace http://localhost/f
 * (b) A variable $f:pi whose value is the Double pi
 * (c) A function f:sqrt(x) that calculates the square root of its argument x
 *
 * @see javax.xml.xpath.XPath
 */
public class ApplyXPathJAXP {
    protected String filename = null;
    protected String xpathExpressionStr = null;
    protected String objectModel = null;

    /**
     * Process input args and execute the XPath.
     */
    public void doMain(String[] args)
            throws Exception {
        filename = args[0];
        xpathExpressionStr = args[1];
        String om = "saxon";
        if (args.length > 2) {
            om = args[2];
        }
        if (om.equals("saxon")) {
            objectModel = NamespaceConstant.OBJECT_MODEL_SAXON;
        } else if (om.equals("dom")) {
            objectModel = XPathConstants.DOM_OBJECT_MODEL;
        } else if (om.equals("jdom")) {
            objectModel = NamespaceConstant.OBJECT_MODEL_JDOM;
        } else if (om.equals("xom")) {
            objectModel = NamespaceConstant.OBJECT_MODEL_XOM;
        } else {
            System.err.println("Unknown object model " + args[2]);
            return;
        }

        if ((filename != null) && (filename.length() > 0)
                && (xpathExpressionStr != null) && (xpathExpressionStr.length() > 0)) {
            // Tell that we're loading classes and parsing, so the time it
            // takes to do this doesn't get confused with the time to do
            // the actual query and serialization.
            System.out.println("Loading classes, parsing " + filename + ", and setting up serializer");

            Object document = null;
            if (objectModel.equals(NamespaceConstant.OBJECT_MODEL_SAXON)) {
                document = new XPathEvaluator().setSource(new StreamSource(new File(filename)));

            } else if (objectModel.equals(XPathConstants.DOM_OBJECT_MODEL)) {
                InputSource in = new InputSource(new FileInputStream(filename));
                DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
                dfactory.setNamespaceAware(true);
                Document doc = dfactory.newDocumentBuilder().parse(in);
                document = doc;

            } else if (objectModel.equals(NamespaceConstant.OBJECT_MODEL_JDOM)) {
                InputSource in = new InputSource(new FileInputStream(filename));
                SAXBuilder builder = new SAXBuilder();
                document = builder.build(in);

            } else if (objectModel.equals(NamespaceConstant.OBJECT_MODEL_XOM)) {
                nu.xom.Builder builder = new nu.xom.Builder();
                document = builder.build(new FileInputStream(filename));
            }

            // Set up an identity transformer to use as serializer.
            Transformer serializer = TransformerFactory.newInstance().newTransformer();
            serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

            // Use the JAXP 1.3 XPath API to apply the xpath expression to the doc.
            System.out.println("Querying " + om + " using " + xpathExpressionStr);

            // Following is specific to Saxon: should be in a properties file
            System.setProperty("javax.xml.xpath.XPathFactory:"+NamespaceConstant.OBJECT_MODEL_SAXON,
                    "net.sf.saxon.xpath.XPathFactoryImpl");
            System.setProperty("javax.xml.xpath.XPathFactory:"+XPathConstants.DOM_OBJECT_MODEL,
                    "net.sf.saxon.xpath.XPathFactoryImpl");
            System.setProperty("javax.xml.xpath.XPathFactory:"+NamespaceConstant.OBJECT_MODEL_JDOM,
                    "net.sf.saxon.xpath.XPathFactoryImpl");
            System.setProperty("javax.xml.xpath.XPathFactory:"+NamespaceConstant.OBJECT_MODEL_XOM,
                    "net.sf.saxon.xpath.XPathFactoryImpl");

            // Get a instance of XPathFactory with ObjectModel URL parameter. If no parameter
            // is mentioned then default DOM Object Model is used
            XPathFactory xpathFactory = XPathFactory.newInstance(objectModel);
            XPath xpath = xpathFactory.newXPath();

            // Declare a namespace context:
            xpath.setNamespaceContext(
                    new NamespaceContext() {
                        public String getNamespaceURI(String s) {
                            if (s.equals("f")) {
                                return "http://localhost/f";
                            } else {
                                return null;
                            }
                        }

                        public String getPrefix(String s) { return null; }

                        public Iterator getPrefixes(String s) { return null; }
                    }
                );

            // Declare a variable:

            final QName f_var = new QName("http://localhost/f", "pi");
            xpath.setXPathVariableResolver(
                    new XPathVariableResolver() {
                        public Object resolveVariable(QName qName) {
                            if (qName.equals(f_var)) {
                                return new Double(Math.PI);
                            } else {
                                return null;
                            }
                        }
                    }
                );

            // Declare a function:

            final XPathFunction sqrt = new XPathFunction() {
                public Object evaluate(List list) throws XPathFunctionException {
                    Object arg = list.get(0);
                    if (!(arg instanceof Double)) {
                        throw new XPathFunctionException("f:sqrt() expects an xs:double argument");
                    }
                    return new Double(Math.sqrt(((Double)arg).doubleValue()));
                }
            };

            final QName f_sqrt = new QName("http://localhost/f", "sqrt");
            xpath.setXPathFunctionResolver(
                    new XPathFunctionResolver() {
                        public XPathFunction resolveFunction(QName qName, int arity) {
                            if (qName.equals(f_sqrt) && arity==1) {
                                return sqrt;
                            } else {
                                return null;
                            }
                        }
                    }
                );


            // Now compile the expression
            XPathExpression xpathExpression = xpath.compile(xpathExpressionStr);


            // Now evaluate the expression on the document to get String result
            String resultString = xpathExpression.evaluate(document);

            // Serialize the found nodes to System.out.
            System.out.println("<output>");
            System.out.println("String Value => " + resultString);
            System.out.println("</output>");
        } else {
            System.out.println("Bad input args: " + filename + ", " + xpathExpressionStr);
        }
    }


    /**
     * Main method to run from the command line.
     */
    public static void main(String[] args)
            throws Exception {
        if (args.length != 2 && args.length != 3) {
            System.out.println("java ApplyXPathJAXP filename.xml xpath [saxon|dom|jdom|xom]\n"
                    + "Reads filename.xml and applies the xpath; prints the String value of result.");
            return;
        }

        ApplyXPathJAXP app = new ApplyXPathJAXP();
        app.doMain(args);
    }

} // end of class ApplyXPathJAXP

