/* * eXist Open Source Native XML Database * Copyright (C) 2008-2009 The eXist Project * http://exist-db.org * * 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.xslt; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import org.exist.security.xacml.XACMLSource; import org.exist.xquery.AnalyzeContextInfo; import org.exist.xquery.CompiledXQuery; import org.exist.xquery.Expression; import org.exist.xquery.Variable; import org.exist.xquery.XPathException; import org.exist.xquery.value.Item; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceIterator; import org.exist.xquery.value.ValueSequence; import org.exist.dom.QName; import org.exist.dom.NamespaceNodeAtExist; import org.exist.dom.Validation; import org.exist.interpreter.ContextAtExist; import org.exist.xslt.expression.AttributeSet; import org.exist.xslt.expression.Declaration; import org.exist.xslt.expression.Param; import org.exist.xslt.expression.Template; import org.exist.xslt.expression.XSLExpression; import org.exist.xslt.expression.i.Parameted; import org.w3c.dom.Attr; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * <(xsl:stylesheet|xsl:transform) * id? = id * extension-element-prefixes? = tokens * exclude-result-prefixes? = tokens * version = number * xpath-default-namespace? = uri * default-validation? = "preserve" | "strip" * default-collation? = uri-list * input-type-annotations? = "preserve" | "strip" | "unspecified"> * <!-- Content: (xsl:import*, other-declarations) --> * </(xsl:stylesheet|xsl:transform)> * * @author <a href="mailto:shabanovd@gmail.com">Dmitriy Shabanov</a> * */ public class XSLStylesheet extends Declaration implements CompiledXQuery, Templates, XSLExpression, Parameted { private Transformer transformer = null; public final double version = 2.0; private String id = null; private String extension_element_prefixes = null; private String exclude_result_prefixes = null; private String xpath_default_namespace = null; private int default_validation = Validation.STRIP; private String default_collation = null; private String input_type_annotations = null; private boolean simplified = false; private Template rootTemplate = null; private Set<Template> templates = new TreeSet<Template>(); private Map<QName, Template> namedTemplates = new HashMap<QName, Template>(); private Map<String, AttributeSet> attributeSets = new HashMap<String, AttributeSet>(); private Map<QName, org.exist.xquery.Variable> params = null; public XSLStylesheet(XSLContext context) { super(context); //UNDERSTAND: may be better to set at eval??? context.setXSLStylesheet(this); context.setStripWhitespace(true); } public XSLStylesheet(XSLContext context, boolean embedded) { this(context); this.simplified = embedded; } public void setToDefaults() { id = null; extension_element_prefixes = null; exclude_result_prefixes = null; xpath_default_namespace = null; default_validation = Validation.STRIP; default_collation = null; input_type_annotations = null; } public void prepareAttribute(ContextAtExist context, Attr attr) throws XPathException { String attr_name = attr.getLocalName(); if (attr instanceof NamespaceNodeAtExist) { NamespaceNodeAtExist namespace = (NamespaceNodeAtExist) attr; if (attr_name.equals("")) context.setDefaultElementNamespace(namespace.getValue(), null); context.declareInScopeNamespace(attr_name, namespace.getValue()); return; } if (attr_name.equals(ID)) { id = attr.getValue(); } else if (attr_name.equals(EXTENSION_ELEMENT_PREFIXES)) { extension_element_prefixes = attr.getValue(); } else if (attr_name.equals(EXCLUDE_RESULT_PREFIXES)) { exclude_result_prefixes = attr.getValue(); } else if (attr_name.equals(VERSION)) { if (!Double.valueOf(attr.getValue()).equals(version)) { } } else if (attr_name.equals(XPATH_DEFAULT_NAMESPACE)) { xpath_default_namespace = attr.getValue(); } else if (attr_name.equals(DEFAULT_VALIDATION)) { //XXX: fix -> default_validation = attr.getValue(); } else if (attr_name.equals(DEFAULT_COLLATION)) { default_collation = attr.getValue(); } else if (attr_name.equals(INPUT_TYPE_ANNOTATIONS)) { input_type_annotations = attr.getValue(); } } /* (non-Javadoc) * @see javax.xml.transform.Templates#getOutputProperties() */ public Properties getOutputProperties() { throw new RuntimeException("Not implemented: getOutputProperties() at "+this.getClass()); } /* (non-Javadoc) * @see javax.xml.transform.Templates#newTransformer() */ public Transformer newTransformer() throws TransformerConfigurationException { org.exist.xslt.Transformer transformer = new org.exist.xslt.Transformer(); transformer.setPreparedStylesheet(this); return transformer; } @Override public void dump(Writer writer) { throw new RuntimeException("Not implemented: dump(Writer writer) at "+this.getClass()); } @Override public XACMLSource getSource() { throw new RuntimeException("Not implemented: getSource() at "+this.getClass()); } public boolean isValid() { throw new RuntimeException("Not implemented: isValid() at "+this.getClass()); } @Override public void reset() { throw new RuntimeException("Not implemented: reset() at "+this.getClass()); } public void setSource(XACMLSource source) { throw new RuntimeException("Not implemented: setSource(XACMLSource source) at "+this.getClass()); } public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { super.analyze(contextInfo); for (Expression expr : steps) { if (expr instanceof Template) { Template template = (Template) expr; if (template.isRootMatch()) { if (rootTemplate != null) compileError("double root match");//XXX: put error code rootTemplate = template; if (template.getName() != null) namedTemplates.put(template.getName(), template);//UNDERSTAND: check doubles? } else if (template.getName() == null) { templates.add(template); } else { namedTemplates.put(template.getName(), template);//UNDERSTAND: check doubles? } } else if (expr instanceof AttributeSet) { AttributeSet attributeSet = (AttributeSet) expr; attributeSets.put(attributeSet.getName(), attributeSet); } } } public void validate() throws XPathException { super.validate(); } public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { Sequence result; if (simplified) result = super.eval(contextSequence, null); else result = templates(contextSequence, null); if (result == null) result = Sequence.EMPTY_SEQUENCE; return result; } public Sequence attributeSet(String name, Sequence contextSequence, Item contextItem) throws XPathException { Sequence result = new ValueSequence(); String[] names = name.split(" "); String n; for (int i = 0; i < names.length; i++) { n = names[i]; if (attributeSets.containsKey(n)) { result.addAll(attributeSets.get(n).eval(null, null)); } else { //UNDERSTAND: error??? } } return result; } public Sequence templates(Sequence contextSequence, Item contextItem) throws XPathException { boolean matched = false; Sequence result = new ValueSequence(); Sequence currentSequence = contextSequence; if (contextItem != null) currentSequence = contextItem.toSequence(); int pos = context.getContextPosition(); // for (Item item : currentSequence) { for (SequenceIterator iterInner = currentSequence.iterate(); iterInner.hasNext();) { Item item = iterInner.nextItem(); //UNDERSTAND: work around if (item instanceof org.w3c.dom.Document) { org.w3c.dom.Document document = (org.w3c.dom.Document) item; item = (Item) document.getDocumentElement(); } context.setContextPosition(pos); if ((contextItem == null) && (rootTemplate != null)) { context.setContextPosition(0); Sequence res = rootTemplate.eval(contextSequence, item); result.addAll(res); matched = true; } for (Template template : templates) { if (template.matched(contextSequence, item)) { //contextSequence matched = true; Sequence res = template.eval(contextSequence, item); result.addAll(res); if (res.getItemCount() > 0) break; } } //XXX: performance !?! how to get subelements sequence?? fast... // if (!matched) { // if (item instanceof ElementAtExist) { // ElementAtExist element = (ElementAtExist) item; // // NodeList children = element.getChildNodes(); // for (int i=0; i<children.getLength(); i++) { // NodeAtExist child = (NodeAtExist)children.item(i); // // if (child instanceof Text) { // MemTreeBuilder builder = context.getDocumentBuilder(); // builder.characters(item.getStringValue()); // result.add(item); // } else { // Sequence res = templates((Sequence)element, (Item)child); // if (res != null) { // result.addAll(res); // matched = true; // } // } // } // } // } pos++; } if (matched) return result; return null; } public Sequence template(QName name, Sequence contextSequence, Item contextItem) throws XPathException { if (!namedTemplates.containsKey(name)) throw new XPathException("no template with given name = "+name);//TODO: error? Sequence result = new ValueSequence(); Sequence currentSequence = contextSequence; if (contextItem != null) currentSequence = contextItem.toSequence(); int pos = context.getContextPosition(); Template template = namedTemplates.get(name); for (SequenceIterator iterInner = currentSequence.iterate(); iterInner.hasNext();) { Item item = iterInner.nextItem(); //UNDERSTAND: work around if (item instanceof org.w3c.dom.Document) { org.w3c.dom.Document document = (org.w3c.dom.Document) item; item = (Item) document.getDocumentElement(); } context.setContextPosition(pos); Sequence res = template.eval(contextSequence, item); result.addAll(res); if (res.getItemCount() > 0) break; pos++; } return result; } public Map<QName, org.exist.xquery.Variable> getXSLParams() { if (params == null) params = new HashMap<QName, org.exist.xquery.Variable>(); return params; } /* (non-Javadoc) * @see org.exist.xslt.expression.i.Parameted#addXSLParam(org.exist.xslt.expression.Param) */ public void addXSLParam(Param param) throws XPathException { Map<QName, org.exist.xquery.Variable> params = getXSLParams(); if (params.containsKey(param.getName())) compileError(XSLExceptions.ERR_XTSE0580); Variable variable = context.declareVariable(param.getName(), param);//UNDERSTAND: global params.put(param.getName(), variable); } public void setTransformer(Transformer transformer) { this.transformer = transformer; } public Transformer getTransformer() { return transformer; } }