1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the  "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: FuncDocument.java 468643 2006-10-28 06:56:03Z minchau $
20  */
21 package org.apache.xalan.templates;
22 
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 
27 import javax.xml.transform.ErrorListener;
28 import javax.xml.transform.Source;
29 import javax.xml.transform.SourceLocator;
30 import javax.xml.transform.TransformerException;
31 
32 import org.apache.xalan.res.XSLMessages;
33 import org.apache.xalan.res.XSLTErrorResources;
34 import org.apache.xml.dtm.DTM;
35 import org.apache.xml.dtm.DTMIterator;
36 import org.apache.xml.utils.XMLString;
37 import org.apache.xpath.Expression;
38 import org.apache.xpath.NodeSetDTM;
39 import org.apache.xpath.SourceTreeManager;
40 import org.apache.xpath.XPathContext;
41 import org.apache.xpath.functions.Function2Args;
42 import org.apache.xpath.functions.WrongNumberArgsException;
43 import org.apache.xpath.objects.XNodeSet;
44 import org.apache.xpath.objects.XObject;
45 
46 /**
47  * Execute the Doc() function.
48  *
49  * When the document function has exactly one argument and the argument
50  * is a node-set, then the result is the union, for each node in the
51  * argument node-set, of the result of calling the document function with
52  * the first argument being the string-value of the node, and the second
53  * argument being a node-set with the node as its only member. When the
54  * document function has two arguments and the first argument is a node-set,
55  * then the result is the union, for each node in the argument node-set,
56  * of the result of calling the document function with the first argument
57  * being the string-value of the node, and with the second argument being
58  * the second argument passed to the document function.
59  * @xsl.usage advanced
60  */
61 public class FuncDocument extends Function2Args
62 {
63     static final long serialVersionUID = 2483304325971281424L;
64 
65   /**
66    * Execute the function.  The function must return
67    * a valid object.
68    * @param xctxt The current execution context.
69    * @return A valid XObject.
70    *
71    * @throws javax.xml.transform.TransformerException
72    */
execute(XPathContext xctxt)73   public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException
74   {
75     int context = xctxt.getCurrentNode();
76     DTM dtm = xctxt.getDTM(context);
77 
78     int docContext = dtm.getDocumentRoot(context);
79     XObject arg = (XObject) this.getArg0().execute(xctxt);
80 
81     String base = "";
82     Expression arg1Expr = this.getArg1();
83 
84     if (null != arg1Expr)
85     {
86 
87       // The URI reference may be relative. The base URI (see [3.2 Base URI])
88       // of the node in the second argument node-set that is first in document
89       // order is used as the base URI for resolving the
90       // relative URI into an absolute URI.
91       XObject arg2 = arg1Expr.execute(xctxt);
92 
93       if (XObject.CLASS_NODESET == arg2.getType())
94       {
95         int baseNode = arg2.iter().nextNode();
96 
97         if (baseNode == DTM.NULL)
98         {
99             // 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    */
getDoc(XPathContext xctxt, int context, String uri, String base)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    */
error(XPathContext xctxt, String msg, Object args[])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    */
warn(XPathContext xctxt, String msg, Object args[])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    */
checkNumberArgs(int argNum)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    */
reportWrongNumberArgs()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    */
isNodesetExpr()458   public boolean isNodesetExpr()
459   {
460     return true;
461   }
462 
463 }
464