package org.basex.query.func;
import static org.basex.query.QueryText.*;
import static org.basex.query.util.Err.*;
import static org.basex.util.Reflect.*;
import static org.basex.util.Token.*;
import java.io.ByteArrayInputStream;
import java.lang.reflect.InvocationTargetException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.basex.io.IO;
import org.basex.io.IOContent;
import org.basex.io.out.ArrayOutput;
import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.item.ANode;
import org.basex.query.item.DBNode;
import org.basex.query.item.Item;
import org.basex.query.item.QNm;
import org.basex.query.item.SeqType;
import org.basex.query.item.Type;
import org.basex.query.item.map.Map;
import org.basex.query.iter.AxisIter;
import org.basex.util.InputInfo;
import org.basex.util.Util;
import org.basex.util.hash.TokenObjMap;
/**
* Project specific functions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class FNXslt extends StandardFunc {
/** Element: parameters. */
private static final QNm E_PARAM = new QNm(token("parameters"), XSLTURI);
/** XSLT implementations. */
private static final String[] IMPL = {
"", "Java", "1.0",
"net.sf.saxon.TransformerFactoryImpl", "Saxon HE", "2.0",
"com.saxonica.config.ProfessionalTransformerFactory", "Saxon PE", "2.0",
"com.saxonica.config.EnterpriseTransformerFactory", "Saxon EE", "2.0"
};
/** Implementation offset. */
private static final int OFFSET;
static {
final String fac = TransformerFactory.class.getName();
final String impl = System.getProperty(fac);
// system property has already been set
if(System.getProperty(fac) != null) {
// modify processor and version
IMPL[1] = impl;
IMPL[2] = "Unknown";
OFFSET = 0;
} else {
// search for existing processors
int s = IMPL.length - 3;
while(s != 0 && find(IMPL[s]) == null) s -= 3;
OFFSET = s;
// set processor, or use default processor
if(s != 0) System.setProperty(fac, IMPL[s]);
}
}
/**
* Returns details on the XSLT implementation.
* @param name name flag
* @return string
*/
static String get(final boolean name) {
return IMPL[OFFSET + (name ? 1 : 2)];
}
/**
* Constructor.
* @param ii input info
* @param f function definition
* @param e arguments
*/
public FNXslt(final InputInfo ii, final Function f, final Expr... e) {
super(ii, f, e);
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
checkAdmin(ctx);
try {
final IO in = read(expr[0], ctx);
final IO xsl = read(expr[1], ctx);
final TokenObjMap<Object> map = xsltParams(2, E_PARAM, ctx);
final byte[] result = transform(in, xsl, map);
return new DBNode(new IOContent(result), ctx.context.prop);
} catch(final Exception ex) {
Util.debug(ex);
// return cause of reflection error, or error itself
throw IOERR.thrw(input, ex instanceof InvocationTargetException ?
ex.getCause() : ex);
}
}
/**
* Creates serializer properties.
* @param arg argument with parameters
* @param root expected root element
* @param ctx query context
* @return serialization parameters
* @throws QueryException query exception
*/
private TokenObjMap<Object> xsltParams(final int arg, final QNm root,
final QueryContext ctx) throws QueryException {
// initialize token map
final TokenObjMap<Object> tm = new TokenObjMap<Object>();
// argument does not exist...
if(arg >= expr.length) return tm;
// empty sequence...
final Item it = expr[arg].item(ctx, input);
if(it == null) return tm;
// XQuery map: convert to internal map
if(it instanceof Map) return ((Map) it).tokenJavaMap(input);
// no element: convert XQuery map to internal map
if(!it.type().eq(SeqType.ELM)) throw NODFUNTYPE.thrw(input, this, it.type);
// parse nodes
ANode node = (ANode) it;
if(!node.qname().eq(root)) PARWHICH.thrw(input, node.qname());
// interpret query parameters
final AxisIter ai = node.children();
while((node = ai.next()) != null) {
final QNm qn = node.qname();
if(!eq(qn.uri(), XSLTURI)) PARWHICH.thrw(input, qn);
tm.add(qn.local(), node.children().next());
}
return tm;
}
/**
* Returns an input reference (possibly cached) to the specified input.
* @param e expression to be evaluated
* @param ctx query context
* @return item
* @throws QueryException query exception
* @throws Exception exception
*/
private IO read(final Expr e, final QueryContext ctx) throws Exception {
final Item it = checkNoEmpty(e.item(ctx, input));
final Type ip = it.type;
if(ip.isNode()) {
final ArrayOutput ao = new ArrayOutput();
final Serializer ser = Serializer.get(ao);
it.serialize(ser);
ser.close();
return new IOContent(ao.toArray());
}
if(ip.isString()) return IO.get(string(it.string(input)));
throw STRNODTYPE.thrw(input, this, ip);
}
@Override
public boolean uses(final Use u) {
return u == Use.NDT && sig == Function._UTIL_TRANSFORM || super.uses(u);
}
/**
* Uses Java's XSLT implementation to perform an XSL transformation.
* @param in input
* @param xsl style sheet
* @param par parameters
* @return transformed result
* @throws Exception exception
*/
private static byte[] transform(final IO in, final IO xsl,
final TokenObjMap<Object> par) throws Exception {
// create transformer
final TransformerFactory tc = TransformerFactory.newInstance();
final Transformer tr = tc.newTransformer(
new StreamSource(new ByteArrayInputStream(xsl.read())));
// bind parameters
for(final byte[] key : par) tr.setParameter(string(key), par.get(key));
// create serializer
final ArrayOutput ao = new ArrayOutput();
// do transformation and return result
tr.transform(new StreamSource(new ByteArrayInputStream(in.read())),
new StreamResult(ao));
return ao.toArray();
}
}