package client.net.sf.saxon.ce.functions;
import client.net.sf.saxon.ce.Configuration;
import client.net.sf.saxon.ce.Controller;
import client.net.sf.saxon.ce.expr.*;
import client.net.sf.saxon.ce.expr.sort.DocumentOrderIterator;
import client.net.sf.saxon.ce.expr.sort.GlobalOrderComparer;
import client.net.sf.saxon.ce.om.*;
import client.net.sf.saxon.ce.trans.Err;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.util.SourceLocator;
import client.net.sf.saxon.ce.tree.util.URI;
import client.net.sf.saxon.ce.value.Cardinality;
/**
* Implements the XSLT document() function
*/
public class DocumentFn extends SystemFunction {
public DocumentFn newInstance() {
return new DocumentFn();
}
private String expressionBaseURI = null;
/**
* Method called during static type checking
*/
public void checkArguments(ExpressionVisitor visitor) throws XPathException {
if (expressionBaseURI == null) {
// only do this once. The second call supplies an env pointing to the containing
// xsl:template, which has a different base URI (and in a simplified stylesheet, has no base URI)
super.checkArguments(visitor);
expressionBaseURI = visitor.getStaticContext().getBaseURI();
Optimizer opt = visitor.getConfiguration().getOptimizer();
argument[0] = ExpressionTool.unsorted(opt, argument[0], false);
}
}
/**
* Determine the static cardinality
*/
public int computeCardinality() {
Expression expression = argument[0];
if (Cardinality.allowsMany(expression.getCardinality())) {
return StaticProperty.ALLOWS_ZERO_OR_MORE;
} else {
return StaticProperty.ALLOWS_ZERO_OR_ONE;
}
// may have to revise this if the argument can be a list-valued element or attribute
}
/**
* Get the base URI from the static context
* @return the base URI
*/
public String getStaticBaseURI() {
return expressionBaseURI;
}
/**
* Get the static properties of this expression (other than its type). The result is
* bit-signficant. These properties are used for optimizations. In general, if
* property bit is set, it is true, but if it is unset, the value is unknown.
*/
public int computeSpecialProperties() {
return StaticProperty.ORDERED_NODESET |
StaticProperty.PEER_NODESET |
StaticProperty.NON_CREATIVE;
// Declaring it as a peer node-set expression avoids sorting of expressions such as
// document(XXX)/a/b/c
// The document() function might appear to be creative: but it isn't, because multiple calls
// with the same arguments will produce identical results.
}
/**
* preEvaluate:
* @param visitor an expression visitor
*/
public Expression preEvaluate(ExpressionVisitor visitor) {
return this;
}
/**
* iterate() handles evaluation of the function:
* it returns a sequence of Document nodes
*/
public SequenceIterator iterate(XPathContext context) throws XPathException {
int numArgs = argument.length;
SequenceIterator hrefSequence = argument[0].iterate(context);
String baseURI = null;
if (numArgs==2) {
// we can trust the type checking: it must be a node
NodeInfo base = (NodeInfo)argument[1].evaluateItem(context);
baseURI = base.getBaseURI();
}
DocumentMappingFunction map = new DocumentMappingFunction(context);
map.baseURI = baseURI;
map.stylesheetURI = expressionBaseURI;
map.locator = getSourceLocator();
ItemMappingIterator iter = new ItemMappingIterator(hrefSequence, map);
Expression expression = argument[0];
if (Cardinality.allowsMany(expression.getCardinality())) {
return new DocumentOrderIterator(iter, GlobalOrderComparer.getInstance());
// this is to make sure we eliminate duplicates: two href's might be the same
} else {
return iter;
}
}
private static class DocumentMappingFunction implements ItemMappingFunction {
public String baseURI;
public String stylesheetURI;
public SourceLocator locator;
public XPathContext context;
public DocumentMappingFunction(XPathContext context) {
this.context = context;
}
public Item mapItem(Item item) throws XPathException {
String b = baseURI;
if (b==null) {
if (item instanceof NodeInfo) {
b = ((NodeInfo)item).getBaseURI();
} else {
b = stylesheetURI;
}
}
return makeDoc(item.getStringValue(), b, context, locator);
}
}
/**
* Supporting routine to load one external document given a URI (href) and a baseURI. This is used
* in the normal case when a document is loaded at run-time (that is, when a Controller is available)
* @param href the relative URI
* @param baseURI the base URI
* @param c the dynamic XPath context
* @param locator used to identify the location of the instruction in event of error
* @return the root of the constructed document, or the selected element within the document
* if a fragment identifier was supplied
*/
public static NodeInfo makeDoc(String href, String baseURI, XPathContext c, SourceLocator locator)
throws XPathException {
Configuration config = c.getConfiguration();
// If the href contains a fragment identifier, strip it out now
//System.err.println("Entering makeDoc " + href);
int hash = href.indexOf('#');
String fragmentId = null;
if (hash>=0) {
if (hash==href.length()-1) {
// # sign at end - just ignore it
href = href.substring(0, hash);
} else {
fragmentId = href.substring(hash+1);
href = href.substring(0, hash);
if (!NameChecker.isValidNCName(fragmentId)) {
XPathException de = new XPathException("The fragment identifier " + Err.wrap(fragmentId) + " is not a valid NCName");
de.setErrorCode("XTRE1160");
de.setXPathContext(c);
throw de;
}
}
}
Controller controller = c.getController();
// Resolve relative URI
DocumentURI documentKey = computeDocumentKey(href, baseURI);
// see if the document is already loaded
DocumentInfo doc = config.getGlobalDocumentPool().find(documentKey);
if (doc != null) {
return doc;
}
DocumentPool pool = controller.getDocumentPool();
doc = pool.find(documentKey);
if (doc != null) {
return getFragment(doc, fragmentId, c);
}
// check that the document was not written by this transformation
if (!controller.checkUniqueOutputDestination(documentKey)) {
pool.markUnavailable(documentKey);
XPathException err = new XPathException(
"Cannot read a document that was written during the same transformation: " + documentKey);
err.setXPathContext(c);
err.setErrorCode("XTRE1500");
throw err;
}
try {
if (pool.isMarkedUnavailable(documentKey)) {
XPathException err = new XPathException(
"Document has been marked not available: " + documentKey);
err.setXPathContext(c);
err.setErrorCode("FODC0002");
throw err;
}
DocumentInfo newdoc = config.buildDocument(documentKey.toString());
controller.registerDocument(newdoc, documentKey);
controller.addUnavailableOutputDestination(documentKey);
return getFragment(newdoc, fragmentId, c);
} catch (XPathException err) {
pool.markUnavailable(documentKey);
err.maybeSetLocation(locator);
String code = (err.getCause() instanceof URI.URISyntaxException) ? "FODC0005" : "FODC0002";
err.maybeSetErrorCode(code);
controller.recoverableError(err);
return null;
}
}
/**
* Compute a document key (an absolute URI that can be used to see if a document is already loaded)
* @param href the relative URI
* @param baseURI the base URI
* @return a unique key for the document
*/
public static DocumentURI computeDocumentKey(String href, String baseURI) {
String documentKey;
try {
documentKey = ResolveURI.makeAbsolute(href, baseURI).toString();
} catch (URI.URISyntaxException e) {
documentKey = baseURI + "/../" + href;
}
return new DocumentURI(documentKey);
}
/**
* Resolve the fragment identifier within a URI Reference.
* Only "bare names" XPointers are recognized, that is, a fragment identifier
* that matches an ID attribute value within the target document.
* @param doc the document node
* @param fragmentId the fragment identifier (an ID value within the document)
* @param context the XPath dynamic context
* @return the element within the supplied document that matches the
* given id value; or null if no such element is found.
*/
private static NodeInfo getFragment(DocumentInfo doc, String fragmentId, XPathContext context)
throws XPathException {
// TODO: we only support one kind of fragment identifier. The rules say
// that the interpretation of the fragment identifier depends on media type,
// but we aren't getting the media type from the URIResolver.
if (fragmentId==null) {
return doc;
}
if (!NameChecker.isValidNCName(fragmentId)) {
XPathException err = new XPathException("Invalid fragment identifier in URI");
err.setXPathContext(context);
err.setErrorCode("XTRE1160");
try {
context.getController().recoverableError(err);
} catch (XPathException dynamicError) {
throw err;
}
return doc;
}
return doc.selectID(fragmentId);
}
}
// 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.