001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: FuncDocument.java 468643 2006-10-28 06:56:03Z minchau $
020 */
021 package org.apache.xalan.templates;
022
023 import java.io.IOException;
024 import java.io.PrintWriter;
025 import java.io.StringWriter;
026
027 import javax.xml.transform.ErrorListener;
028 import javax.xml.transform.Source;
029 import javax.xml.transform.SourceLocator;
030 import javax.xml.transform.TransformerException;
031
032 import org.apache.xalan.res.XSLMessages;
033 import org.apache.xalan.res.XSLTErrorResources;
034 import org.apache.xml.dtm.DTM;
035 import org.apache.xml.dtm.DTMIterator;
036 import org.apache.xml.utils.XMLString;
037 import org.apache.xpath.Expression;
038 import org.apache.xpath.NodeSetDTM;
039 import org.apache.xpath.SourceTreeManager;
040 import org.apache.xpath.XPathContext;
041 import org.apache.xpath.functions.Function2Args;
042 import org.apache.xpath.functions.WrongNumberArgsException;
043 import org.apache.xpath.objects.XNodeSet;
044 import org.apache.xpath.objects.XObject;
045
046 /**
047 * Execute the Doc() function.
048 *
049 * When the document function has exactly one argument and the argument
050 * is a node-set, then the result is the union, for each node in the
051 * argument node-set, of the result of calling the document function with
052 * the first argument being the string-value of the node, and the second
053 * argument being a node-set with the node as its only member. When the
054 * document function has two arguments and the first argument is a node-set,
055 * then the result is the union, for each node in the argument node-set,
056 * of the result of calling the document function with the first argument
057 * being the string-value of the node, and with the second argument being
058 * the second argument passed to the document function.
059 * @xsl.usage advanced
060 */
061 public class FuncDocument extends Function2Args
062 {
063 static final long serialVersionUID = 2483304325971281424L;
064
065 /**
066 * Execute the function. The function must return
067 * a valid object.
068 * @param xctxt The current execution context.
069 * @return A valid XObject.
070 *
071 * @throws javax.xml.transform.TransformerException
072 */
073 public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException
074 {
075 int context = xctxt.getCurrentNode();
076 DTM dtm = xctxt.getDTM(context);
077
078 int docContext = dtm.getDocumentRoot(context);
079 XObject arg = (XObject) this.getArg0().execute(xctxt);
080
081 String base = "";
082 Expression arg1Expr = this.getArg1();
083
084 if (null != arg1Expr)
085 {
086
087 // The URI reference may be relative. The base URI (see [3.2 Base URI])
088 // of the node in the second argument node-set that is first in document
089 // order is used as the base URI for resolving the
090 // relative URI into an absolute URI.
091 XObject arg2 = arg1Expr.execute(xctxt);
092
093 if (XObject.CLASS_NODESET == arg2.getType())
094 {
095 int baseNode = arg2.iter().nextNode();
096
097 if (baseNode == DTM.NULL)
098 {
099 // See http://www.w3.org/1999/11/REC-xslt-19991116-errata#E14.
100 // If the second argument is an empty nodeset, this is an error.
101 // The processor can recover by returning an empty nodeset.
102 warn(xctxt, XSLTErrorResources.WG_EMPTY_SECOND_ARG, null);
103 XNodeSet nodes = new XNodeSet(xctxt.getDTMManager());
104 return nodes;
105 } else{
106 DTM baseDTM = xctxt.getDTM(baseNode);
107 base = baseDTM.getDocumentBaseURI();
108 }
109 // %REVIEW% This doesn't seem to be a problem with the conformance
110 // suite, but maybe it's just not doing a good test?
111 // int baseDoc = baseDTM.getDocument();
112 //
113 // if (baseDoc == DTM.NULL /* || baseDoc instanceof Stylesheet -->What to do?? */)
114 // {
115 //
116 // // base = ((Stylesheet)baseDoc).getBaseIdentifier();
117 // base = xctxt.getNamespaceContext().getBaseIdentifier();
118 // }
119 // else
120 // base = xctxt.getSourceTreeManager().findURIFromDoc(baseDoc);
121 }
122 else
123 {
124 //Can not convert other type to a node-set!;
125 arg2.iter();
126 }
127 }
128 else
129 {
130
131 // If the second argument is omitted, then it defaults to
132 // the node in the stylesheet that contains the expression that
133 // includes the call to the document function. Note that a
134 // zero-length URI reference is a reference to the document
135 // relative to which the URI reference is being resolved; thus
136 // document("") refers to the root node of the stylesheet;
137 // the tree representation of the stylesheet is exactly
138 // the same as if the XML document containing the stylesheet
139 // was the initial source document.
140 assertion(null != xctxt.getNamespaceContext(), "Namespace context can not be null!");
141 base = xctxt.getNamespaceContext().getBaseIdentifier();
142 }
143
144 XNodeSet nodes = new XNodeSet(xctxt.getDTMManager());
145 NodeSetDTM mnl = nodes.mutableNodeset();
146 DTMIterator iterator = (XObject.CLASS_NODESET == arg.getType())
147 ? arg.iter() : null;
148 int pos = DTM.NULL;
149
150 while ((null == iterator) || (DTM.NULL != (pos = iterator.nextNode())))
151 {
152 XMLString ref = (null != iterator)
153 ? xctxt.getDTM(pos).getStringValue(pos) : arg.xstr();
154
155 // The first and only argument was a nodeset, the base in that
156 // case is the base URI of the node from the first argument nodeset.
157 // Remember, when the document function has exactly one argument and
158 // the argument is a node-set, then the result is the union, for each
159 // node in the argument node-set, of the result of calling the document
160 // function with the first argument being the string-value of the node,
161 // and the second argument being a node-set with the node as its only
162 // member.
163 if (null == arg1Expr && DTM.NULL != pos)
164 {
165 DTM baseDTM = xctxt.getDTM(pos);
166 base = baseDTM.getDocumentBaseURI();
167 }
168
169 if (null == ref)
170 continue;
171
172 if (DTM.NULL == docContext)
173 {
174 error(xctxt, XSLTErrorResources.ER_NO_CONTEXT_OWNERDOC, null); //"context does not have an owner document!");
175 }
176
177 // From http://www.ics.uci.edu/pub/ietf/uri/rfc1630.txt
178 // A partial form can be distinguished from an absolute form in that the
179 // latter must have a colon and that colon must occur before any slash
180 // characters. Systems not requiring partial forms should not use any
181 // unencoded slashes in their naming schemes. If they do, absolute URIs
182 // will still work, but confusion may result.
183 int indexOfColon = ref.indexOf(':');
184 int indexOfSlash = ref.indexOf('/');
185
186 if ((indexOfColon != -1) && (indexOfSlash != -1)
187 && (indexOfColon < indexOfSlash))
188 {
189
190 // The url (or filename, for that matter) is absolute.
191 base = null;
192 }
193
194 int newDoc = getDoc(xctxt, context, ref.toString(), base);
195
196 // nodes.mutableNodeset().addNode(newDoc);
197 if (DTM.NULL != newDoc)
198 {
199 // TODO: mnl.addNodeInDocOrder(newDoc, true, xctxt); ??
200 if (!mnl.contains(newDoc))
201 {
202 mnl.addElement(newDoc);
203 }
204 }
205
206 if (null == iterator || newDoc == DTM.NULL)
207 break;
208 }
209
210 return nodes;
211 }
212
213 /**
214 * Get the document from the given URI and base
215 *
216 * @param xctxt The XPath runtime state.
217 * @param context The current context node
218 * @param uri Relative(?) URI of the document
219 * @param base Base to resolve relative URI from.
220 *
221 * @return The document Node pointing to the document at the given URI
222 * or null
223 *
224 * @throws javax.xml.transform.TransformerException
225 */
226 int getDoc(XPathContext xctxt, int context, String uri, String base)
227 throws javax.xml.transform.TransformerException
228 {
229
230 // System.out.println("base: "+base+", uri: "+uri);
231 SourceTreeManager treeMgr = xctxt.getSourceTreeManager();
232 Source source;
233
234 int newDoc;
235 try
236 {
237 source = treeMgr.resolveURI(base, uri, xctxt.getSAXLocator());
238 newDoc = treeMgr.getNode(source);
239 }
240 catch (IOException ioe)
241 {
242 throw new TransformerException(ioe.getMessage(),
243 (SourceLocator)xctxt.getSAXLocator(), ioe);
244 }
245 catch(TransformerException te)
246 {
247 throw new TransformerException(te);
248 }
249
250 if (DTM.NULL != newDoc)
251 return newDoc;
252
253 // If the uri length is zero, get the uri of the stylesheet.
254 if (uri.length() == 0)
255 {
256 // Hmmm... this seems pretty bogus to me... -sb
257 uri = xctxt.getNamespaceContext().getBaseIdentifier();
258 try
259 {
260 source = treeMgr.resolveURI(base, uri, xctxt.getSAXLocator());
261 }
262 catch (IOException ioe)
263 {
264 throw new TransformerException(ioe.getMessage(),
265 (SourceLocator)xctxt.getSAXLocator(), ioe);
266 }
267 }
268
269 String diagnosticsString = null;
270
271 try
272 {
273 if ((null != uri) && (uri.length() > 0))
274 {
275 newDoc = treeMgr.getSourceTree(source, xctxt.getSAXLocator(), xctxt);
276
277 // System.out.println("newDoc: "+((Document)newDoc).getDocumentElement().getNodeName());
278 }
279 else
280 warn(xctxt, XSLTErrorResources.WG_CANNOT_MAKE_URL_FROM,
281 new Object[]{ ((base == null) ? "" : base) + uri }); //"Can not make URL from: "+((base == null) ? "" : base )+uri);
282 }
283 catch (Throwable throwable)
284 {
285
286 // throwable.printStackTrace();
287 newDoc = DTM.NULL;
288
289 // path.warn(XSLTErrorResources.WG_ENCODING_NOT_SUPPORTED_USING_JAVA, new Object[]{((base == null) ? "" : base )+uri}); //"Can not load requested doc: "+((base == null) ? "" : base )+uri);
290 while (throwable
291 instanceof org.apache.xml.utils.WrappedRuntimeException)
292 {
293 throwable =
294 ((org.apache.xml.utils.WrappedRuntimeException) throwable).getException();
295 }
296
297 if ((throwable instanceof NullPointerException)
298 || (throwable instanceof ClassCastException))
299 {
300 throw new org.apache.xml.utils.WrappedRuntimeException(
301 (Exception) throwable);
302 }
303
304 StringWriter sw = new StringWriter();
305 PrintWriter diagnosticsWriter = new PrintWriter(sw);
306
307 if (throwable instanceof TransformerException)
308 {
309 TransformerException spe = (TransformerException) throwable;
310
311 {
312 Throwable e = spe;
313
314 while (null != e)
315 {
316 if (null != e.getMessage())
317 {
318 diagnosticsWriter.println(" (" + e.getClass().getName() + "): "
319 + e.getMessage());
320 }
321
322 if (e instanceof TransformerException)
323 {
324 TransformerException spe2 = (TransformerException) e;
325
326 SourceLocator locator = spe2.getLocator();
327 if ((null != locator) && (null != locator.getSystemId()))
328 diagnosticsWriter.println(" ID: " + locator.getSystemId()
329 + " Line #" + locator.getLineNumber()
330 + " Column #"
331 + locator.getColumnNumber());
332
333 e = spe2.getException();
334
335 if (e instanceof org.apache.xml.utils.WrappedRuntimeException)
336 e = ((org.apache.xml.utils.WrappedRuntimeException) e).getException();
337 }
338 else
339 e = null;
340 }
341 }
342 }
343 else
344 {
345 diagnosticsWriter.println(" (" + throwable.getClass().getName()
346 + "): " + throwable.getMessage());
347 }
348
349 diagnosticsString = throwable.getMessage(); //sw.toString();
350 }
351
352 if (DTM.NULL == newDoc)
353 {
354
355 // System.out.println("what?: "+base+", uri: "+uri);
356 if (null != diagnosticsString)
357 {
358 warn(xctxt, XSLTErrorResources.WG_CANNOT_LOAD_REQUESTED_DOC,
359 new Object[]{ diagnosticsString }); //"Can not load requested doc: "+((base == null) ? "" : base )+uri);
360 }
361 else
362 warn(xctxt, XSLTErrorResources.WG_CANNOT_LOAD_REQUESTED_DOC,
363 new Object[]{
364 uri == null
365 ? ((base == null) ? "" : base) + uri : uri.toString() }); //"Can not load requested doc: "+((base == null) ? "" : base )+uri);
366 }
367 else
368 {
369 // %REVIEW%
370 // TBD: What to do about XLocator?
371 // xctxt.getSourceTreeManager().associateXLocatorToNode(newDoc, url, null);
372 }
373
374 return newDoc;
375 }
376
377 /**
378 * Tell the user of an error, and probably throw an
379 * exception.
380 *
381 * @param xctxt The XPath runtime state.
382 * @param msg The error message key
383 * @param args Arguments to be used in the error message
384 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
385 * the error condition is severe enough to halt processing.
386 *
387 * @throws javax.xml.transform.TransformerException
388 */
389 public void error(XPathContext xctxt, String msg, Object args[])
390 throws javax.xml.transform.TransformerException
391 {
392
393 String formattedMsg = XSLMessages.createMessage(msg, args);
394 ErrorListener errHandler = xctxt.getErrorListener();
395 TransformerException spe = new TransformerException(formattedMsg,
396 (SourceLocator)xctxt.getSAXLocator());
397
398 if (null != errHandler)
399 errHandler.error(spe);
400 else
401 System.out.println(formattedMsg);
402 }
403
404 /**
405 * Warn the user of a problem.
406 *
407 * @param xctxt The XPath runtime state.
408 * @param msg Warning message key
409 * @param args Arguments to be used in the warning message
410 * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide
411 * the error condition is severe enough to halt processing.
412 *
413 * @throws javax.xml.transform.TransformerException
414 */
415 public void warn(XPathContext xctxt, String msg, Object args[])
416 throws javax.xml.transform.TransformerException
417 {
418
419 String formattedMsg = XSLMessages.createWarning(msg, args);
420 ErrorListener errHandler = xctxt.getErrorListener();
421 TransformerException spe = new TransformerException(formattedMsg,
422 (SourceLocator)xctxt.getSAXLocator());
423
424 if (null != errHandler)
425 errHandler.warning(spe);
426 else
427 System.out.println(formattedMsg);
428 }
429
430 /**
431 * Overide the superclass method to allow one or two arguments.
432 *
433 *
434 * @param argNum Number of arguments passed in to this function
435 *
436 * @throws WrongNumberArgsException
437 */
438 public void checkNumberArgs(int argNum) throws WrongNumberArgsException
439 {
440 if ((argNum < 1) || (argNum > 2))
441 reportWrongNumberArgs();
442 }
443
444 /**
445 * Constructs and throws a WrongNumberArgException with the appropriate
446 * message for this function object.
447 *
448 * @throws WrongNumberArgsException
449 */
450 protected void reportWrongNumberArgs() throws WrongNumberArgsException {
451 throw new WrongNumberArgsException(XSLMessages.createMessage(XSLTErrorResources.ER_ONE_OR_TWO, null)); //"1 or 2");
452 }
453
454 /**
455 * Tell if the expression is a nodeset expression.
456 * @return true if the expression can be represented as a nodeset.
457 */
458 public boolean isNodesetExpr()
459 {
460 return true;
461 }
462
463 }