001: package net.sf.saxon.functions;
002:
003: import net.sf.saxon.Controller;
004: import net.sf.saxon.Configuration;
005: import net.sf.saxon.NonDelegatingURIResolver;
006: import net.sf.saxon.event.Builder;
007: import net.sf.saxon.event.Receiver;
008: import net.sf.saxon.event.Sender;
009: import net.sf.saxon.event.PipelineConfiguration;
010: import net.sf.saxon.expr.*;
011: import net.sf.saxon.om.*;
012: import net.sf.saxon.sort.DocumentOrderIterator;
013: import net.sf.saxon.sort.GlobalOrderComparer;
014: import net.sf.saxon.trans.DynamicError;
015: import net.sf.saxon.trans.XPathException;
016: import net.sf.saxon.value.Cardinality;
017:
018: import javax.xml.transform.Source;
019: import javax.xml.transform.TransformerException;
020: import javax.xml.transform.URIResolver;
021: import javax.xml.transform.SourceLocator;
022: import javax.xml.transform.dom.DOMSource;
023: import java.net.URI;
024: import java.net.URISyntaxException;
025:
026: /**
027: * Implements the XSLT document() function
028: */
029:
030: public class Document extends SystemFunction implements XSLTFunction {
031:
032: private String expressionBaseURI = null;
033:
034: public void checkArguments(StaticContext env) throws XPathException {
035: if (expressionBaseURI == null) {
036: // only do this once. The second call supplies an env pointing to the containing
037: // xsl:template, which has a different base URI (and in a simplified stylesheet, has no base URI)
038: super .checkArguments(env);
039: expressionBaseURI = env.getBaseURI();
040: Optimizer opt = env.getConfiguration().getOptimizer();
041: argument[0] = ExpressionTool.unsorted(opt, argument[0],
042: false);
043: }
044: }
045:
046: /**
047: * Determine the static cardinality
048: */
049:
050: public int computeCardinality() {
051: Expression expression = argument[0];
052: if (Cardinality.allowsMany(expression.getCardinality())) {
053: return StaticProperty.ALLOWS_ZERO_OR_MORE;
054: } else {
055: return StaticProperty.ALLOWS_ZERO_OR_ONE;
056: }
057: // may have to revise this if the argument can be a list-valued element or attribute
058: }
059:
060: /**
061: * Get the static properties of this expression (other than its type). The result is
062: * bit-signficant. These properties are used for optimizations. In general, if
063: * property bit is set, it is true, but if it is unset, the value is unknown.
064: */
065:
066: public int computeSpecialProperties() {
067: return StaticProperty.ORDERED_NODESET
068: | StaticProperty.PEER_NODESET
069: | StaticProperty.NON_CREATIVE;
070: // Declaring it as a peer node-set expression avoids sorting of expressions such as
071: // document(XXX)/a/b/c
072: // The document() function might appear to be creative: but it isn't, because multiple calls
073: // with the same arguments will produce identical results.
074: }
075:
076: /**
077: * preEvaluate: this method suppresses compile-time evaluation by doing nothing
078: */
079:
080: public Expression preEvaluate(StaticContext env) {
081: return this ;
082: }
083:
084: /**
085: * iterate() handles evaluation of the function:
086: * it returns a sequence of Document nodes
087: */
088:
089: public SequenceIterator iterate(XPathContext context)
090: throws XPathException {
091: int numArgs = argument.length;
092:
093: SequenceIterator hrefSequence = argument[0].iterate(context);
094: String baseURI = null;
095: if (numArgs == 2) {
096: // we can trust the type checking: it must be a node
097: NodeInfo base = (NodeInfo) argument[1]
098: .evaluateItem(context);
099: baseURI = base.getBaseURI();
100: }
101:
102: DocumentMappingFunction map = new DocumentMappingFunction();
103: map.baseURI = baseURI;
104: map.stylesheetURI = expressionBaseURI;
105: map.locator = this ;
106:
107: MappingIterator iter = new MappingIterator(hrefSequence, map,
108: context);
109:
110: Expression expression = argument[0];
111: if (Cardinality.allowsMany(expression.getCardinality())) {
112: return new DocumentOrderIterator(iter, GlobalOrderComparer
113: .getInstance());
114: // this is to make sure we eliminate duplicates: two href's might be the same
115: } else {
116: return iter;
117: }
118: }
119:
120: private static class DocumentMappingFunction implements
121: MappingFunction {
122:
123: public String baseURI;
124: public String stylesheetURI;
125: public SourceLocator locator;
126:
127: /**
128: * Implement the MappingFunction interface: called from the MappingIterator
129: */
130:
131: public Object map(Item item, XPathContext context)
132: throws XPathException {
133:
134: if (baseURI == null) {
135: if (item instanceof NodeInfo) {
136: baseURI = ((NodeInfo) item).getBaseURI();
137: } else {
138: baseURI = stylesheetURI;
139: }
140: }
141: NodeInfo doc = makeDoc(item.getStringValue(), baseURI,
142: context, locator);
143: return doc;
144: }
145: }
146:
147: /**
148: * Supporting routine to load one external document given a URI (href) and a baseURI
149: */
150:
151: public static NodeInfo makeDoc(String href, String baseURL,
152: XPathContext c, SourceLocator locator)
153: throws XPathException {
154:
155: // If the href contains a fragment identifier, strip it out now
156:
157: int hash = href.indexOf('#');
158:
159: String fragmentId = null;
160: if (hash >= 0) {
161: if (hash == href.length() - 1) {
162: // # sign at end - just ignore it
163: href = href.substring(0, hash);
164: } else {
165: fragmentId = href.substring(hash + 1);
166: href = href.substring(0, hash);
167: }
168: }
169:
170: // Resolve relative URI
171:
172: String documentKey;
173: if (baseURL == null) { // no base URI available
174: try {
175: // the href might be an absolute URL
176: documentKey = (new URI(href)).toString();
177: } catch (URISyntaxException err) {
178: // it isn't; but the URI resolver might know how to cope
179: documentKey = '/' + href;
180: baseURL = "";
181: }
182: } else if (href.equals("")) {
183: // common case in XSLT, which java.net.URI#resolve() does not handle correctly
184: documentKey = baseURL;
185: } else {
186: try {
187: URI url = new URI(baseURL).resolve(href);
188: documentKey = url.toString();
189: } catch (URISyntaxException err) {
190: documentKey = baseURL + "/../" + href;
191: } catch (IllegalArgumentException err) {
192: documentKey = baseURL + "/../" + href;
193: }
194: }
195:
196: Controller controller = c.getController();
197:
198: // see if the document is already loaded
199:
200: DocumentInfo doc = controller.getDocumentPool().find(
201: documentKey);
202: if (doc != null) {
203: return getFragment(doc, fragmentId, c);
204: }
205:
206: try {
207: // Get a Source from the URIResolver
208:
209: URIResolver r = controller.getURIResolver();
210: Source source = null;
211: if (r != null) {
212: source = r.resolve(href, baseURL);
213: }
214:
215: // if a user URI resolver returns null, try the standard one
216: // (Note, the standard URI resolver never returns null)
217: if (source == null
218: && !(r instanceof NonDelegatingURIResolver)) {
219: r = controller.getStandardURIResolver();
220: source = r.resolve(href, baseURL);
221: }
222:
223: Configuration config = controller.getConfiguration();
224: source = config.getSourceResolver().resolveSource(source,
225: config);
226:
227: DocumentInfo newdoc;
228: if (source instanceof NodeInfo
229: || source instanceof DOMSource) {
230: NodeInfo startNode = controller
231: .prepareInputTree(source);
232: newdoc = startNode.getDocumentRoot();
233: } else {
234: Builder b = controller.makeBuilder();
235: Receiver s = controller.makeStripper(b);
236: if (controller.getExecutable()
237: .stripsInputTypeAnnotations()) {
238: s = controller.getConfiguration()
239: .getAnnotationStripper(s);
240: }
241: new Sender(controller.makePipelineConfiguration())
242: .send(source, s);
243: newdoc = (DocumentInfo) b.getCurrentRoot();
244: }
245: controller.registerDocument(newdoc, documentKey);
246: return getFragment(newdoc, fragmentId, c);
247:
248: } catch (TransformerException err) {
249: DynamicError xerr = DynamicError.makeDynamicError(err);
250: xerr.setLocator(locator);
251: xerr.setErrorCode("FODC0005");
252: try {
253: controller.recoverableError(xerr);
254: } catch (XPathException err2) {
255: throw new DynamicError(err);
256: }
257: return null;
258: }
259: }
260:
261: /**
262: * Copy the documents identified by this expression to a given Receiver. This method is used only when it is
263: * known that the documents are being copied, because there is then no problem about node identity.
264: */
265:
266: public void sendDocuments(XPathContext context, Receiver out)
267: throws XPathException {
268: SequenceIterator hrefSequence = argument[0].iterate(context);
269: String explicitBaseURI = null;
270: if (argument.length == 2) {
271: // we can trust the type checking: it must be a node
272: NodeInfo base = (NodeInfo) argument[1]
273: .evaluateItem(context);
274: explicitBaseURI = base.getBaseURI();
275: }
276: while (true) {
277: Item href = hrefSequence.next();
278: if (href == null) {
279: break;
280: }
281: String base;
282: if (explicitBaseURI == null) {
283: if (href instanceof NodeInfo) {
284: base = ((NodeInfo) href).getBaseURI();
285: } else {
286: base = expressionBaseURI;
287: }
288: } else {
289: base = explicitBaseURI;
290: }
291: sendDoc(href.getStringValue(), base, context, this , out);
292: }
293: }
294:
295: /**
296: * Supporting routine to push one external document given a URI (href) and a baseURI to a given Receiver.
297: * This method cannot handle fragment identifiers
298: */
299:
300: public static void sendDoc(String href, String baseURL,
301: XPathContext c, SourceLocator locator, Receiver out)
302: throws XPathException {
303:
304: PipelineConfiguration pipe = out.getPipelineConfiguration();
305: if (pipe == null) {
306: pipe = c.getController().makePipelineConfiguration();
307: }
308:
309: // Resolve relative URI
310:
311: String documentKey;
312: if (baseURL == null) { // no base URI available
313: try {
314: // the href might be an absolute URL
315: documentKey = (new URI(href)).toString();
316: } catch (URISyntaxException err) {
317: // it isn't; but the URI resolver might know how to cope
318: documentKey = '/' + href;
319: baseURL = "";
320: }
321: } else if (href.equals("")) {
322: // common case in XSLT, which java.net.URI#resolve() does not handle correctly
323: documentKey = baseURL;
324: } else {
325: try {
326: URI url = new URI(baseURL).resolve(href);
327: documentKey = url.toString();
328: } catch (URISyntaxException err) {
329: documentKey = baseURL + "/../" + href;
330: } catch (IllegalArgumentException err) {
331: documentKey = baseURL + "/../" + href;
332: }
333: }
334:
335: Controller controller = c.getController();
336:
337: // see if the document is already loaded
338:
339: DocumentInfo doc = controller.getDocumentPool().find(
340: documentKey);
341: Source source = null;
342: if (doc != null) {
343: source = doc;
344: } else {
345:
346: try {
347: // Get a Source from the URIResolver
348:
349: URIResolver r = controller.getURIResolver();
350: if (r != null) {
351: source = r.resolve(href, baseURL);
352: }
353:
354: // if a user URI resolver returns null, try the standard one
355: // (Note, the standard URI resolver never returns null)
356: if (source == null) {
357: r = controller.getStandardURIResolver();
358: source = r.resolve(href, baseURL);
359: }
360: if (source instanceof NodeInfo
361: || source instanceof DOMSource) {
362: NodeInfo startNode = controller
363: .prepareInputTree(source);
364: source = startNode.getDocumentRoot();
365: }
366: } catch (TransformerException err) {
367: DynamicError xerr = DynamicError.makeDynamicError(err);
368: xerr.setLocator(locator);
369: xerr.setErrorCode("FODC0005");
370: throw xerr;
371: }
372: }
373: out = controller.makeStripper(out);
374: out.setPipelineConfiguration(pipe);
375: if (controller.getExecutable().stripsInputTypeAnnotations()) {
376: out = controller.getConfiguration().getAnnotationStripper(
377: out);
378: out.setPipelineConfiguration(pipe);
379: }
380: new Sender(pipe).send(source, out);
381: }
382:
383: /**
384: * Resolve the fragment identifier within a URI Reference.
385: * Only "bare names" XPointers are recognized, that is, a fragment identifier
386: * that matches an ID attribute value within the target document.
387: * @return the element within the supplied document that matches the
388: * given id value; or null if no such element is found.
389: */
390:
391: private static NodeInfo getFragment(DocumentInfo doc,
392: String fragmentId, XPathContext context)
393: throws XPathException {
394: // TODO: we only support one kind of fragment identifier. The rules say
395: // that the interpretation of the fragment identifier depends on media type,
396: // but we aren't getting the media type from the URIResolver.
397: if (fragmentId == null) {
398: return doc;
399: }
400: if (!context.getConfiguration().getNameChecker().isValidNCName(
401: fragmentId)) {
402: DynamicError err = new DynamicError(
403: "Invalid fragment identifier in URI");
404: err.setXPathContext(context);
405: err.setErrorCode("XTRE1160");
406: try {
407: context.getController().recoverableError(err);
408: } catch (DynamicError dynamicError) {
409: throw err;
410: }
411: return doc;
412: }
413: return doc.selectID(fragmentId);
414: }
415:
416: }
417:
418: //
419: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
420: // you may not use this file except in compliance with the License. You may obtain a copy of the
421: // License at http://www.mozilla.org/MPL/
422: //
423: // Software distributed under the License is distributed on an "AS IS" basis,
424: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
425: // See the License for the specific language governing rights and limitations under the License.
426: //
427: // The Original Code is: all this file.
428: //
429: // The Initial Developer of the Original Code is Michael H. Kay.
430: //
431: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
432: //
433: // Contributor(s): none.
434: //
|