package client.net.sf.saxon.ce.expr.instruct;
import client.net.sf.saxon.ce.Controller;
import client.net.sf.saxon.ce.Controller.APIcommand;
import client.net.sf.saxon.ce.LogController;
import client.net.sf.saxon.ce.dom.HTMLDocumentWrapper;
import client.net.sf.saxon.ce.dom.HTMLNodeWrapper;
import client.net.sf.saxon.ce.dom.HTMLDocumentWrapper.DocType;
import client.net.sf.saxon.ce.dom.XMLDOM;
import client.net.sf.saxon.ce.event.PipelineConfiguration;
import client.net.sf.saxon.ce.event.Receiver;
import client.net.sf.saxon.ce.expr.*;
import client.net.sf.saxon.ce.functions.DocumentFn;
import client.net.sf.saxon.ce.functions.FunctionLibrary;
import client.net.sf.saxon.ce.functions.ResolveURI;
import client.net.sf.saxon.ce.js.JSObjectValue;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.pattern.EmptySequenceTest;
import client.net.sf.saxon.ce.pattern.NodeKindTest;
import client.net.sf.saxon.ce.style.StyleElement;
import client.net.sf.saxon.ce.sxpath.AbstractStaticContext;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.trans.update.DeleteAction;
import client.net.sf.saxon.ce.trans.update.InsertAction;
import client.net.sf.saxon.ce.trans.update.PendingUpdateList;
import client.net.sf.saxon.ce.tree.iter.AxisIterator;
import client.net.sf.saxon.ce.tree.iter.SingleNodeIterator;
import client.net.sf.saxon.ce.tree.util.URI;
//import client.net.sf.saxon.ce.tree.util.URI.URISyntaxException;
import client.net.sf.saxon.ce.type.ItemType;
import client.net.sf.saxon.ce.type.Type;
import client.net.sf.saxon.ce.type.TypeHierarchy;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Node;
import com.google.gwt.logging.client.LogConfiguration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Logger;
/**
* The compiled form of an xsl:result-document element in the stylesheet.
* <p/>
* The xsl:result-document element takes an attribute href="filename". The filename will
* often contain parameters, e.g. {position()} to ensure that a different file is produced
* for each element instance.
* <p/>
* There is a further attribute "format" which determines the format of the
* output file, it identifies the name of an xsl:output element containing the output
* format details. In addition, individual serialization properties may be specified as attributes.
* These are attribute value templates, so they may need to be computed at run-time.
*/
public class ResultDocument extends Instruction {
private Expression href;
private Expression methodExpression;
private Expression content;
private NamespaceResolver nsResolver;
private Logger logger = Logger.getLogger("Xstl20Processor");
public final static int APPEND_CONTENT = 0;
public final static int REPLACE_CONTENT = 1;
/**
* Create a result-document instruction
* @param href href attribute of instruction
* @param methodExpression format attribute of instruction
* @param baseURI base URI of the instruction
* @param nsResolver namespace resolver
*/
public ResultDocument(Expression href,
Expression methodExpression, // AVT defining the output format
String baseURI,
NamespaceResolver nsResolver) {
this.href = href;
this.methodExpression = methodExpression;
this.nsResolver = nsResolver;
adoptChildExpression(href);
if (LogConfiguration.loggingIsEnabled() && LogController.traceIsEnabled()) {
this.AddTraceProperty("href", href);
}
}
/**
* Set the expression that constructs the content
* @param content the expression defining the content of the result document
*/
public void setContentExpression(Expression content) {
this.content = content;
adoptChildExpression(content);
}
/**
* Simplify an expression. This performs any static optimization (by rewriting the expression
* as a different expression). The default implementation does nothing.
* @param visitor an expression visitor
* @return the simplified expression
* @throws client.net.sf.saxon.ce.trans.XPathException
* if an error is discovered during expression rewriting
*/
public Expression simplify(ExpressionVisitor visitor) throws XPathException {
content = visitor.simplify(content);
href = visitor.simplify(href);
return this;
}
public Expression typeCheck(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
content = visitor.typeCheck(content, contextItemType);
adoptChildExpression(content);
if (href != null) {
href = visitor.typeCheck(href, contextItemType);
adoptChildExpression(href);
}
if (methodExpression != null) {
methodExpression = visitor.typeCheck(methodExpression, contextItemType);
adoptChildExpression(methodExpression);
}
return this;
}
public Expression optimize(ExpressionVisitor visitor, ItemType contextItemType) throws XPathException {
content = visitor.optimize(content, contextItemType);
adoptChildExpression(content);
if (href != null) {
href = visitor.optimize(href, contextItemType);
adoptChildExpression(href);
}
if (methodExpression != null) {
methodExpression = visitor.optimize(methodExpression, contextItemType);
adoptChildExpression(methodExpression);
}
return this;
}
public int getIntrinsicDependencies() {
return StaticProperty.HAS_SIDE_EFFECTS;
}
/**
* Handle promotion offers, that is, non-local tree rewrites.
* @param offer The type of rewrite being offered
* @throws XPathException
*/
protected void promoteInst(PromotionOffer offer) throws XPathException {
content = doPromotion(content, offer);
if (href != null) {
href = doPromotion(href, offer);
}
}
/**
* Get the name of this instruction for diagnostic and tracing purposes
* (the string "xsl:result-document")
*/
public int getInstructionNameCode() {
return StandardNames.XSL_RESULT_DOCUMENT;
}
/**
* Get the item type of the items returned by evaluating this instruction
* @param th the type hierarchy cache
* @return the static item type of the instruction. This is empty: the result-document instruction
* returns nothing.
*/
public ItemType getItemType(TypeHierarchy th) {
return EmptySequenceTest.getInstance();
}
/**
* Get all the XPath expressions associated with this instruction
* (in XSLT terms, the expression present on attributes of the instruction,
* as distinct from the child instructions in a sequence construction)
*/
public Iterator<Expression> iterateSubExpressions() {
ArrayList list = new ArrayList(6);
list.add(content);
if (href != null) {
list.add(href);
}
if (methodExpression != null) {
list.add(methodExpression);
}
return list.iterator();
}
/**
* Replace one subexpression by a replacement subexpression
* @param original the original subexpression
* @param replacement the replacement subexpression
* @return true if the original subexpression is found
*/
public boolean replaceSubExpression(Expression original, Expression replacement) {
boolean found = false;
if (content == original) {
content = replacement;
found = true;
}
if (href == original) {
href = replacement;
found = true;
}
return found;
}
/**
* Return a valid URI - event if base URI is not set
*/
public static String getValidAbsoluteURI(Controller controller, String href) throws XPathException {
String baseURI = (controller.getBaseOutputURI() != null && controller.getBaseOutputURI().length() > 0)?
controller.getBaseOutputURI() : Document.get().getURL();
try {
return ResolveURI.makeAbsolute(href, baseURI).toString();
} catch (URI.URISyntaxException ue) {
throw new XPathException(ue.getMessage());
}
}
public TailCall processLeavingTail(XPathContext context) throws XPathException {
final Controller controller = context.getController();
final APIcommand command = controller.getApiCommand();
XPathContext c2 = context.newMinorContext();
int action = APPEND_CONTENT;
if (methodExpression != null) {
String method = methodExpression.evaluateAsString(context).toString();
StructuredQName methodQ;
if (method.indexOf(':') >= 0) {
methodQ = StructuredQName.fromLexicalQName(method, false, nsResolver);
} else {
methodQ = new StructuredQName("", "", method);
}
if ("replace-content".equals(methodQ.getLocalName())) {
// TODO: check the namespace URI is NamespaceConstant.IXSL
action = REPLACE_CONTENT;
}
}
String hrefValue = null;
if (href != null) {
hrefValue = href.evaluateAsString(context).toString();
} else if (command == APIcommand.UPDATE_HTML) {
throw new XPathException("html update - no href value for result-document instruction");
} else {
hrefValue = "result" + (controller.getResultDocumentCount() + 1);
}
NodeInfo target = null;
Node targetNode = null;
String contextNodeName = "";
String absURI = "";
if (command == APIcommand.TRANSFORM_TO_DOCUMENT) {
absURI = getValidAbsoluteURI(controller, hrefValue);
targetNode = XMLDOM.createDocument(absURI);
} else if (command == APIcommand.TRANSFORM_TO_FRAGMENT || command == APIcommand.TRANSFORM_TO_HTML_FRAGMENT){
absURI = getValidAbsoluteURI(controller, hrefValue);
targetNode = HTMLDocumentWrapper.createDocumentFragment((Document)controller.getTargetNode());
} else if (hrefValue.startsWith("#")) {
hrefValue = hrefValue.substring(1);
targetNode = ((Document)controller.getTargetNode()).getElementById(hrefValue); // com.google.gwt.dom.client.Document.get().getElementById(hrefValue);
} else if (hrefValue.startsWith("?select=")) {
String select = hrefValue.substring(8);
AbstractStaticContext env = new AbstractStaticContext() {
public String getURIForPrefix(String prefix) throws XPathException {
return null;
}
public Expression bindVariable(StructuredQName qName) throws XPathException {
return null;
}
public NamespaceResolver getNamespaceResolver() {
return null;
}
//override getFunctionLibrary to return that loaded for the prepared stylesheet
public FunctionLibrary getFunctionLibrary() {
return controller.getPreparedStylesheet().getFunctionLibrary();
}
};
ExpressionVisitor visitor = new ExpressionVisitor();
visitor.setConfiguration(context.getConfiguration());
visitor.setExecutable(new Executable(context.getConfiguration()));
visitor.setStaticContext(env);
env.setConfiguration(context.getConfiguration());
Container container = (StyleElement)getSourceLocator();
Expression expr = null;
try {
expr = ExpressionTool.make(select, env, container, 0, Token.EOF, getSourceLocator());
} catch (Exception e) {
// occurs if expression contains references to variables etc. within the dynamic context
throw new XPathException("Error on evaluating (in static context) result-document href: " + hrefValue);
}
expr = visitor.typeCheck(expr, NodeKindTest.DOCUMENT);
XPathContext c3 = context.newCleanContext();
//context for ?select expression is the html page if an external node is the context
Document page = (Document)controller.getTargetNode(); //com.google.gwt.dom.client.Document.get();
Item cItem = context.getContextItem();
NodeInfo currentContextItem;
if (cItem instanceof JSObjectValue){
currentContextItem = null;
} else {
currentContextItem = (NodeInfo)cItem;
}
boolean useCurrentContext;
if (currentContextItem == null) {
useCurrentContext = false;
} else {
useCurrentContext = (currentContextItem.getBaseURI().equals(page.getURL()));
}
NodeInfo contextItem;
if (useCurrentContext) {
contextItem = currentContextItem;
if (LogConfiguration.loggingIsEnabled() && contextItem.getNodeKind() == Type.ELEMENT) {
contextNodeName = controller.getNamePool().getLocalName(contextItem.getNameCode());
}
} else {
contextItem = new HTMLDocumentWrapper(page, page.getURL(), context.getConfiguration(), DocType.UNKNOWN);
}
if (LogConfiguration.loggingIsEnabled()) {
contextNodeName = (contextNodeName.equals("")? "" : " context node: " + contextNodeName);
}
AxisIterator iterator = SingleNodeIterator.makeIterator(contextItem);
iterator.next(); // position on the single item
c3.setCurrentIterator(iterator);
SequenceIterator iter = expr.iterate(c3);
Item resultItem = iter.next();
if (resultItem == null) {} // do nothing
else if (!(resultItem instanceof NodeInfo)) {
throw new XPathException("non-node returned by result-document href: " + hrefValue);
} else {
target = (NodeInfo)resultItem;
targetNode = (com.google.gwt.dom.client.Node)((HTMLNodeWrapper)target).getUnderlyingNode();
}
}
else if (command == APIcommand.UPDATE_HTML) {
throw new XPathException("expected '?select=' or '#' at start of result-document href, found: " + hrefValue);
}
if (targetNode == null) {
logger.warning("result-document target not found for href: " + hrefValue + contextNodeName);
return null;
} else {
logger.fine("processing result-document for href: " + hrefValue + contextNodeName);
}
//checkAcceptableUri(context, absoluteResultURI.toString());
//IFrameElement container = Document.get().createIFrameElement();
Node container = null;
if (command == APIcommand.UPDATE_HTML) {
container = HTMLDocumentWrapper.createDocumentFragment((Document)controller.getTargetNode());
} else {
addResultDocument(context, new DocumentURI(absURI), (Document)targetNode);
container = targetNode;
}
PipelineConfiguration pipe = controller.makePipelineConfiguration();
Receiver out = controller.openResult(pipe, c2, container, action);
try {
content.process(c2);
out.endDocument();
} catch (XPathException err) {
err.setXPathContext(context);
err.maybeSetLocation(getSourceLocator());
throw err;
}
controller.closeResult(out, c2);
if (command == APIcommand.UPDATE_HTML){
PendingUpdateList list = controller.getPendingUpdateList();
if (action == REPLACE_CONTENT && command == APIcommand.UPDATE_HTML) {
int existingChildren = targetNode.getChildCount();
for (int i=0; i<existingChildren; i++) {
Node child = targetNode.getChild(i);
list.add(new DeleteAction(child));
}
}
list.add(new InsertAction(container, targetNode, InsertAction.LAST));
}
//controller.setResultTree(absoluteResultURI.toString(), root);
return null;
}
private void addResultDocument(XPathContext context, DocumentURI documentKey, Document doc) throws XPathException {
Controller controller = context.getController();
if (controller.getDocumentPool().find(documentKey.toString()) != null) {
dynamicError("Cannot write to a URI that has already been read: " + documentKey.toString(), "XTRE1500", context);
}
if (!controller.checkUniqueOutputDestination(documentKey)) {
dynamicError("Cannot write more than one result document to the same URI: " + documentKey.toString(),"XTDE1490" ,context);
} else {
controller.addToResultDocumentPool(documentKey, doc);
//controller.addUnavailableOutputDestination(documentKey);
}
//controller.setThereHasBeenAnExplicitResultDocument();
}
}
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is “Incompatible With Secondary Licenses”, as defined by the Mozilla Public License, v. 2.0.