<commit_before>/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 <NAME>
 *  <EMAIL>
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id$
 */
package org.exist.xquery.functions.transform;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.memtree.DocumentBuilderReceiver;
import org.exist.memtree.MemTreeBuilder;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.Constants;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Variable;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.ValueSequence;
import org.exist.xslt.TransformerFactoryAllocator;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * @author <NAME> (<EMAIL>)
 */
public class Transform extends BasicFunction {

	public final static FunctionSignature signatures[] = {
		new FunctionSignature(
			new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
			"Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
			"is specified in the second argument. This should either be an URI or a node. If it is an " +
			"URI, it can either point to an external location or to an XSL stored in the db by using the " +
			"'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
			"fragment and not from a complete document. " +
			"Stylesheet parameters " +
			"may be passed in the third argument using an XML fragment with the following structure: " +
			"<parameters><param name=\"param-name1\" value=\"param-value1\"/>" +
			"</parameters>",
			new SequenceType[] {
				new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE),
				new SequenceType(Type.ITEM, Cardinality.EXACTLY_ONE),
				new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)},
			new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)),
        new FunctionSignature(
            new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
            "Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
            "as for the transform function. stream-transform can only be used within a servlet context. Instead " +
            "of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
            "It should thus be the last statement in the XQuery.",
            new SequenceType[] {
                new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE),
                new SequenceType(Type.ITEM, Cardinality.EXACTLY_ONE),
                new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)},
            new SequenceType(Type.ITEM, Cardinality.EMPTY))
    };

	private final Map cache = new HashMap();
	
	/**
	 * @param context
	 * @param signature
	 */
	public Transform(XQueryContext context, FunctionSignature signature) {
		super(context, signature);
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
	 */
	public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
		if(args[0].isEmpty())
			return Sequence.EMPTY_SEQUENCE;
		Item inputNode = args[0].itemAt(0);
		Item stylesheetItem = args[1].itemAt(0);
		
		Node options = null;
		if(!args[2].isEmpty())
			options = ((NodeValue)args[2].itemAt(0)).getNode();
		
		TransformerHandler handler = createHandler(stylesheetItem, options);
        if (isCalledAs("transform")) {
            ValueSequence seq = new ValueSequence();
    		context.pushDocumentContext();
    		MemTreeBuilder builder = context.getDocumentBuilder();
    		DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder);
    		SAXResult result = new SAXResult(receiver);
    		result.setLexicalHandler(receiver);		//preserve comments etc... from xslt output
    		handler.setResult(result);
    		try {
    			handler.startDocument();
    			inputNode.toSAX(context.getBroker(), handler);
    			handler.endDocument();
    		} catch (SAXException e) {
    			throw new XPathException(getASTNode(), "SAX exception while transforming node: " + e.getMessage(), e);
    		}
    		Node next = builder.getDocument().getFirstChild();
            while (next != null) {
                seq.add((NodeValue) next);
                next = next.getNextSibling();
            }
    		context.popDocumentContext();
    		return seq;
        } else {
            ResponseModule myModule = (ResponseModule)context.getModule(ResponseModule.NAMESPACE_URI);
            // response object is read from global variable $response
            Variable respVar = myModule.resolveVariable(ResponseModule.RESPONSE_VAR);
            if(respVar == null)
                throw new XPathException(getASTNode(), "No response object found in the current XQuery context.");
            if(respVar.getValue().getItemType() != Type.JAVA_OBJECT)
                throw new XPathException(getASTNode(), "Variable $response is not bound to an Java object.");
            JavaObjectValue respValue = (JavaObjectValue)
                respVar.getValue().itemAt(0);
            if (!"org.exist.http.servlets.HttpResponseWrapper".equals(respValue.getObject().getClass().getName()))
                throw new XPathException(getASTNode(), signatures[1].toString() + 
                        " can only be used within the EXistServlet or XQueryServlet");
            ResponseWrapper response = (ResponseWrapper) respValue.getObject();
            try {
                OutputStream os = new BufferedOutputStream(response.getOutputStream());
                StreamResult result = new StreamResult(os);
                handler.setResult(result);
                handler.startDocument();
                inputNode.toSAX(context.getBroker(), handler);
                handler.endDocument();
            } catch (SAXException e) {
                throw new XPathException(getASTNode(), "SAX exception while transforming node: " + e.getMessage(), e);
            } catch (IOException e) {
                throw new XPathException(getASTNode(), "IO exception while transforming node: " + e.getMessage(), e);
            }
            return Sequence.EMPTY_SEQUENCE;
        }
	}

    /**
     * @param stylesheetItem
     * @param options
     * @return
     * @throws TransformerFactoryConfigurationError
     * @throws XPathException
     */
    private TransformerHandler createHandler(Item stylesheetItem, Node options) throws TransformerFactoryConfigurationError, XPathException
    {
    	//SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
    	SAXTransformerFactory factory = (SAXTransformerFactory)TransformerFactoryAllocator.getTransformerFactory(context.getBroker()).newInstance();
    	
		TransformerHandler handler;
		try {
			Templates templates = null;
			if(Type.subTypeOf(stylesheetItem.getType(), Type.NODE)) {
				NodeValue stylesheetNode = (NodeValue)stylesheetItem;
				// if the passed node is a document node or document root element,
				// we construct an XMLDB URI and use the caching implementation. 
				if(stylesheetNode.getImplementationType() == NodeValue.PERSISTENT_NODE) {<commit_msg>It is now possinle to select the XSLT processor (Xalan or Saxon) from conf.xml<commit_after>/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 <NAME>
 *  <EMAIL>
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id$
 */
package org.exist.xquery.functions.transform;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.memtree.DocumentBuilderReceiver;
import org.exist.memtree.MemTreeBuilder;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.Constants;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Variable;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.ValueSequence;
import org.exist.xslt.TransformerFactoryAllocator;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * @author <NAME> (<EMAIL>)
 */
public class Transform extends BasicFunction {

	public final static FunctionSignature signatures[] = {
		new FunctionSignature(
			new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
			"Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
			"is specified in the second argument. This should either be an URI or a node. If it is an " +
			"URI, it can either point to an external location or to an XSL stored in the db by using the " +
			"'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
			"fragment and not from a complete document. " +
			"Stylesheet parameters " +
			"may be passed in the third argument using an XML fragment with the following structure: " +
			"<parameters><param name=\"param-name1\" value=\"param-value1\"/>" +
			"</parameters>",
			new SequenceType[] {
				new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE),
				new SequenceType(Type.ITEM, Cardinality.EXACTLY_ONE),
				new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)},
			new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)),
        new FunctionSignature(
            new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
            "Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
            "as for the transform function. stream-transform can only be used within a servlet context. Instead " +
            "of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
            "It should thus be the last statement in the XQuery.",
            new SequenceType[] {
                new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE),
                new SequenceType(Type.ITEM, Cardinality.EXACTLY_ONE),
                new SequenceType(Type.NODE, Cardinality.ZERO_OR_ONE)},
            new SequenceType(Type.ITEM, Cardinality.EMPTY))
    };

	private final Map cache = new HashMap();
	
	/**
	 * @param context
	 * @param signature
	 */
	public Transform(XQueryContext context, FunctionSignature signature) {
		super(context, signature);
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
	 */
	public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
		if(args[0].isEmpty())
			return Sequence.EMPTY_SEQUENCE;
		Item inputNode = args[0].itemAt(0);
		Item stylesheetItem = args[1].itemAt(0);
		
		Node options = null;
		if(!args[2].isEmpty())
			options = ((NodeValue)args[2].itemAt(0)).getNode();
		
		TransformerHandler handler = createHandler(stylesheetItem, options);
        if (isCalledAs("transform")) {
            ValueSequence seq = new ValueSequence();
    		context.pushDocumentContext();
    		MemTreeBuilder builder = context.getDocumentBuilder();
    		DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder);
    		SAXResult result = new SAXResult(receiver);
    		result.setLexicalHandler(receiver);		//preserve comments etc... from xslt output
    		handler.setResult(result);
    		try {
    			handler.startDocument();
    			inputNode.toSAX(context.getBroker(), handler);
    			handler.endDocument();
    		} catch (SAXException e) {
    			throw new XPathException(getASTNode(), "SAX exception while transforming node: " + e.getMessage(), e);
    		}
    		Node next = builder.getDocument().getFirstChild();
            while (next != null) {
                seq.add((NodeValue) next);
                next = next.getNextSibling();
            }
    		context.popDocumentContext();
    		return seq;
        } else {
            ResponseModule myModule = (ResponseModule)context.getModule(ResponseModule.NAMESPACE_URI);
            // response object is read from global variable $response
            Variable respVar = myModule.resolveVariable(ResponseModule.RESPONSE_VAR);
            if(respVar == null)
                throw new XPathException(getASTNode(), "No response object found in the current XQuery context.");
            if(respVar.getValue().getItemType() != Type.JAVA_OBJECT)
                throw new XPathException(getASTNode(), "Variable $response is not bound to an Java object.");
            JavaObjectValue respValue = (JavaObjectValue)
                respVar.getValue().itemAt(0);
            if (!"org.exist.http.servlets.HttpResponseWrapper".equals(respValue.getObject().getClass().getName()))
                throw new XPathException(getASTNode(), signatures[1].toString() + 
                        " can only be used within the EXistServlet or XQueryServlet");
            ResponseWrapper response = (ResponseWrapper) respValue.getObject();
            try {
                OutputStream os = new BufferedOutputStream(response.getOutputStream());
                StreamResult result = new StreamResult(os);
                handler.setResult(result);
                handler.startDocument();
                inputNode.toSAX(context.getBroker(), handler);
                handler.endDocument();
            } catch (SAXException e) {
                throw new XPathException(getASTNode(), "SAX exception while transforming node: " + e.getMessage(), e);
            } catch (IOException e) {
                throw new XPathException(getASTNode(), "IO exception while transforming node: " + e.getMessage(), e);
            }
            return Sequence.EMPTY_SEQUENCE;
        }
	}

    /**
     * @param stylesheetItem
     * @param options
     * @return
     * @throws TransformerFactoryConfigurationError
     * @throws XPathException
     */
    private TransformerHandler createHandler(Item stylesheetItem, Node options) throws TransformerFactoryConfigurationError, XPathException
    {
    	SAXTransformerFactory factory = (SAXTransformerFactory)TransformerFactoryAllocator.getTransformerFactory(context.getBroker());
    	
		TransformerHandler handler;
		try {
			Templates templates = null;
			if(Type.subTypeOf(stylesheetItem.getType(), Type.NODE)) {
				NodeValue stylesheetNode = (NodeValue)stylesheetItem;
				// if the passed node is a document node or document root element,
				// we construct an XMLDB URI and use the caching implementation. 
				if(stylesheetNode.getImplementationType() == NodeValue.PERSISTENT_NODE) {
{'fraction_non_alphanumeric': 0.09692387904066736, 'fraction_numerical': 0.003023983315954119, 'mean_word_length': 3.5431075319753673, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 35}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3628994', 'n_tokens_mistral': 5509, 'n_tokens_neox': 5229, 'n_words': 1440}