package org.tigris.juxy.builder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.tigris.juxy.*; import org.tigris.juxy.util.DOMUtil; import org.tigris.juxy.util.JuxyURIResolver; import org.tigris.juxy.util.SAXUtil; import org.tigris.juxy.util.XSLTEngineSupport; import org.tigris.juxy.xpath.XPathExpr; import org.tigris.juxy.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * @author Pavel Sher */ public class TemplatesBuilderImpl implements TemplatesBuilder { private final static String DEFAULT_VERSION = "1.0"; private String importSystemId; private String resolvedSystemId; private URIResolver resolver; private Source stylesheetSource; private Collection globalVariables = null; private Map namespaces = new HashMap(); private XPathExpr currentNode; private InvokationStatementInfo invokationStatementInfo = null; private boolean newTemplatesRequired = true; private Templates currentTemplates = null; private Document currentStylesheetDoc = null; private boolean tracingEnabled = false; private XSLTEngineSupport engineSupport; private TransformerFactory transformerFactory = null; private static final Log logger = LogFactory.getLog(TemplatesBuilderImpl.class); public TemplatesBuilderImpl(TransformerFactory trFactory) { assert trFactory != null; this.transformerFactory = trFactory; this.transformerFactory.setErrorListener(new BuilderErrorListener()); this.rootNode = XPathFactory.newXPath("/"); engineSupport = new XSLTEngineSupport(transformerFactory); } public void setImportSystemId(String systemId, URIResolver resolver) { assert systemId != null && systemId.length() > 0; if (resolver == null) resolver = new JuxyURIResolver(); Source src; try { src = resolver.resolve(systemId, ""); if (src == null) throw new JuxyRuntimeException("Failed to resolve system id: " + systemId); } catch (TransformerException e) { throw new JuxyRuntimeException("Failed to resolve system id: " + systemId, e); } updateNewTemplateFlag(!src.getSystemId().equals(resolvedSystemId)); this.stylesheetSource = src; this.resolvedSystemId = src.getSystemId(); this.importSystemId = systemId; this.resolver = resolver; } public void setTracingEnabled(boolean tracingEnabled) { updateNewTemplateFlag(this.tracingEnabled != tracingEnabled); this.tracingEnabled = tracingEnabled; } public void setGlobalVariables(Collection variables) { updateNewTemplateFlag(!isEquivalentCollections(globalVariables, variables)); this.globalVariables = variables; } public void setCurrentNode(XPathExpr currentNode) { if (currentNode != null) updateNewTemplateFlag(!currentNode.equals(this.currentNode)); else if (this.currentNode != null) updateNewTemplateFlag(!this.currentNode.equals(currentNode)); this.currentNode = currentNode; } public void setInvokationStatementInfo(String name, Collection invokeParams) { assert name != null; InvokationStatementInfo newInvokationStatementInfo = new InvokationStatementInfo(name, invokeParams); updateNewTemplateFlag( invokationStatementInfo == null || !invokationStatementInfo.isEquivalent(newInvokationStatementInfo) ); invokationStatementInfo = newInvokationStatementInfo; } public void setInvokationStatementInfo(XPathExpr selectXpathExpr, String mode, Collection invokeParams) { InvokationStatementInfo newInvokationStatementInfo = new InvokationStatementInfo(selectXpathExpr, mode, invokeParams); updateNewTemplateFlag( invokationStatementInfo == null || !invokationStatementInfo.isEquivalent(newInvokationStatementInfo) ); invokationStatementInfo = newInvokationStatementInfo; } public void setNamespaces(Map namespaces) { if (namespaces == null) { updateNewTemplateFlag(this.namespaces.size() > 0); this.namespaces.clear(); return; } updateNewTemplateFlag(namespaces.size() != this.namespaces.size()); if (!newTemplatesRequired) updateNewTemplateFlag(!this.namespaces.equals(namespaces)); this.namespaces.clear(); this.namespaces.putAll(namespaces); } public Templates build() { if (newTemplatesRequired) { checkBuildConfiguration(); String version = getImportedStylesheetVersion(); Element rootEl = createSkeleton(version); createGlobalVariables(rootEl); createInvokationStatement(rootEl); //DOMUtil.logDocument("juxy-stylesheet.xsl", rootEl); updateCurrentTemplates(rootEl.getOwnerDocument()); currentStylesheetDoc = rootEl.getOwnerDocument(); } newTemplatesRequired = false; return currentTemplates; } private String getImportedStylesheetVersion() { assert resolvedSystemId != null; assert stylesheetSource != null; String version = null; if (!(stylesheetSource instanceof DOMSource)) { XSLVersionRetriever handler = new XSLVersionRetriever(); try { XMLReader reader = SAXUtil.newXMLReader(); InputSource is = SAXSource.sourceToInputSource(stylesheetSource); reader.setContentHandler(handler); reader.parse(is); } catch (SAXException e) { if (!XSLVersionRetriever.STOP_MESSAGE.equals(e.getMessage())) throw new JuxyRuntimeException("XML parse error", e); } catch (IOException e) { throw new JuxyRuntimeException("Input / output error on attempt to read from stylesheet: " + importSystemId, e); } version = handler.getVersion(); } else { // obtain xsl version from DOM DOMSource ds = (DOMSource) stylesheetSource; Node node = ds.getNode(); if (node == null) throw new JuxyRuntimeException("DOMSource Node is null for system id: " + importSystemId); Document doc = node.getOwnerDocument(); if (doc == null && node.getNodeType() == Node.DOCUMENT_NODE) doc = (Document) node; if (doc == null) throw new JuxyRuntimeException("Failed to obtain Document from the DOMSource Node for system id: " + importSystemId); NodeList nodes = doc.getElementsByTagNameNS(XSLTKeys.XSLT_NS, "stylesheet"); if (nodes.getLength() == 0) logger.warn("Element xsl:stylesheet was not found in the system id: " + importSystemId); else { Element stylesheetEl = (Element) nodes.item(0); version = stylesheetEl.getAttribute("version"); } } if (version == null) { logger.warn("Unable to obtain stylesheet version for system id: " + importSystemId + ", version " + DEFAULT_VERSION + " will be used"); return DEFAULT_VERSION; } logger.debug("Imported stylesheet version: " + version); return version; } Document getCurrentStylesheetDoc() { return currentStylesheetDoc; } private void checkBuildConfiguration() { if (importSystemId == null || resolvedSystemId == null) throw new IllegalStateException("System id not specified"); } private void updateCurrentTemplates(Document stylesheet) { assert resolver != null; try { boolean enableTracing = false; if (tracingEnabled && !engineSupport.isTracingSupported()) { logger.warn("Tracing is not supported with this type of XSLT transformer: " + transformerFactory.getClass().getName()); } else { enableTracing = tracingEnabled; } if (engineSupport.isCustomURIResolverSupported()) { transformerFactory.setURIResolver(enableTracing ? new TracingURIResolver(resolver, engineSupport) : resolver); } else { if (!(resolver instanceof JuxyURIResolver)) { logger.warn("Due to a bug in Java 1.5 XSLT engine custom URI resolver is not supported."); } } DOMSource source = new DOMSource(stylesheet); // Setting system id to be in the current directory (we are using some file for that, // but it does not matter whether this file exists or not). // This system id is required to resolve paths to // imported and included stylesheets source.setSystemId(new File("juxy-stylesheet.xsl").getCanonicalFile().toURI().toString()); currentTemplates = transformerFactory.newTemplates(source); } catch (TransformerConfigurationException ex) { throw new JuxyRuntimeException("Failed to create Templates object", ex); } catch (IOException e) { throw new JuxyRuntimeException("Failed to create Templates object", e); } } private void createInvokationStatement(Element stylesheetEl) { if (invokationStatementInfo == null) return; if (invokationStatementInfo.isNamedTemplateCall()) createNamedTemplateCall(stylesheetEl); else createAppliedTemplateCall(stylesheetEl); } private void createNamedTemplateCall(Element stylesheetEl) { Element callTemplateEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:call-template"); callTemplateEl.setAttribute("name", invokationStatementInfo.getTemplateName()); createCallingStatementParent(stylesheetEl).appendChild(callTemplateEl); createInvokationParams(callTemplateEl, invokationStatementInfo.getTemplateInvokeParams()); } private void createAppliedTemplateCall(Element stylesheetEl) { if (!rootNode.equals(invokationStatementInfo.getTemplateSelectXPath()) || invokationStatementInfo.getTemplateMode() != null) { if (invokationStatementInfo.getTemplateSelectXPath() == null && invokationStatementInfo.getTemplateMode() == null && (currentNode == null || currentNode.equals(rootNode))) { Element applyImportsEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:apply-imports"); Element templateEl = createCallingStatementParent(stylesheetEl); // templateEl.setAttribute("match", "/"); templateEl.appendChild(applyImportsEl); createInvokationParams(applyImportsEl, invokationStatementInfo.getTemplateInvokeParams()); return; } Element applyTemplatesEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:apply-templates"); if (invokationStatementInfo.getTemplateSelectXPath() != null) applyTemplatesEl.setAttribute("select", invokationStatementInfo.getTemplateSelectXPath().getExpression()); if (invokationStatementInfo.getTemplateMode() != null) applyTemplatesEl.setAttribute("mode", invokationStatementInfo.getTemplateMode()); createCallingStatementParent(stylesheetEl).appendChild(applyTemplatesEl); createInvokationParams(applyTemplatesEl, invokationStatementInfo.getTemplateInvokeParams()); } } private Element createCallingStatementParent(Element stylesheetEl) { Element rootTemplateEl = createRootTemplate(stylesheetEl); if (currentNode == null || rootNode.equals(currentNode)) return rootTemplateEl; Element targetEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:for-each"); targetEl.setAttribute("select", currentNode.getExpression()); rootTemplateEl.appendChild(targetEl); return targetEl; } private Element createRootTemplate(Element stylesheetEl) { Element templateEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:template"); stylesheetEl.appendChild(templateEl); templateEl.setAttribute("match", "/"); return templateEl; } private void createInvokationParams(Element invokeEl, Collection params) { if (params == null || params.isEmpty()) return; Iterator it = params.iterator(); while (it.hasNext()) { InvokeParam param = (InvokeParam) it.next(); Element paramEl = invokeEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:with-param"); invokeEl.appendChild(paramEl); setVariableContentAndAttributes(param, paramEl); } } private void createGlobalVariables(Element stylesheetEl) { if (globalVariables == null || globalVariables.size() == 0) return; Iterator varIt = globalVariables.iterator(); while (varIt.hasNext()) { GlobalVariable var = (GlobalVariable) varIt.next(); Element variableEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:variable"); stylesheetEl.appendChild(variableEl); setVariableContentAndAttributes(var, variableEl); } } private void setVariableContentAndAttributes(VariableBase var, Element variableEl) { Document doc = variableEl.getOwnerDocument(); variableEl.setAttribute("name", var.getQname()); if (var.isVariableWithContent()) variableEl.appendChild(doc.importNode(var.getContent().getDocumentElement(), true)); else if (var.isXPathValue()) variableEl.setAttribute("select", var.getXPathValue()); else if (var.getStringValue() != null) variableEl.appendChild(doc.createTextNode(var.getStringValue())); else variableEl.setAttribute("select", "/.."); // set empty node set as param value } private Element createSkeleton(String version) { assert version != null; Document stylesheetDoc; stylesheetDoc = DOMUtil.newDocument(); Element stylesheetEl = stylesheetDoc.createElementNS(XSLTKeys.XSLT_NS, "xsl:stylesheet"); stylesheetEl.setAttribute("version", version); stylesheetDoc.appendChild(stylesheetEl); registerNamespaces(stylesheetEl); createImport(stylesheetEl); return stylesheetEl; } private void registerNamespaces(Element stylesheetEl) { Iterator it = namespaces.entrySet().iterator(); while (it.hasNext()) { Map.Entry ns = (Map.Entry) it.next(); String uri = (String) ns.getKey(); String prefix = (String) ns.getValue(); String qname = prefix != null && prefix.length() > 0 ? "xmlns:" + prefix : "xmlns"; stylesheetEl.setAttributeNS(XMLConstants.XMLNS_PREFIX_URI, qname, uri); } } private void createImport(Element stylesheetEl) { Element importEl = stylesheetEl.getOwnerDocument().createElementNS(XSLTKeys.XSLT_NS, "xsl:import"); stylesheetEl.appendChild(importEl); importEl.setAttribute("href", importSystemId); } private void updateNewTemplateFlag(boolean needNew) { newTemplatesRequired = newTemplatesRequired || needNew; } class InvokationStatementInfo { private String templateName = null; private Collection templateInvokeParams = null; private XPathExpr templateSelectXPath = null; private String templateMode = null; private boolean namedTemplateCall; public InvokationStatementInfo(String templateName, Collection templateInvokeParams) { this.templateName = templateName; this.templateInvokeParams = templateInvokeParams; namedTemplateCall = true; } public InvokationStatementInfo(XPathExpr templateSelectXPath, String templateMode, Collection templateInvokeParams) { this.templateSelectXPath = templateSelectXPath; this.templateMode = templateMode; this.templateInvokeParams = templateInvokeParams; namedTemplateCall = false; } public boolean isEquivalent(InvokationStatementInfo newStatement) { if (newStatement.isNamedTemplateCall() != isNamedTemplateCall()) return false; if (newStatement.isNamedTemplateCall()) { return isEquivalentInvokePrams(newStatement.getTemplateInvokeParams()) && newStatement.getTemplateName().equals(templateName); } return isEquivalentInvokePrams(newStatement.getTemplateInvokeParams()) && isEquivalentSelectXPath(newStatement.getTemplateSelectXPath()) && isEquivalentMode(newStatement.getTemplateMode()); } public String getTemplateName() { return templateName; } public Collection getTemplateInvokeParams() { return templateInvokeParams; } public XPathExpr getTemplateSelectXPath() { return templateSelectXPath; } public String getTemplateMode() { return templateMode; } private boolean isEquivalentInvokePrams(Collection newInvokeParams) { return isEquivalentCollections(templateInvokeParams, newInvokeParams); } private boolean isEquivalentSelectXPath(XPathExpr newSelectXpath) { if (newSelectXpath != null) return newSelectXpath.equals(templateSelectXPath); return templateSelectXPath == null; } private boolean isEquivalentMode(String newMode) { if (newMode != null) return newMode.equals(templateMode); return templateMode == null; } public boolean isNamedTemplateCall() { return namedTemplateCall; } } private boolean isEquivalentCollections(Collection orig, Collection newc) { if (newc != null && newc.size() > 0) return false; if (orig == null || orig.size() == 0) return true; return false; // TODO } private XPathExpr rootNode = null; }