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 */
21package org.apache.xalan.templates;
22
23import java.io.IOException;
24import java.io.PrintWriter;
25import java.io.StringWriter;
26
27import javax.xml.transform.ErrorListener;
28import javax.xml.transform.Source;
29import javax.xml.transform.SourceLocator;
30import javax.xml.transform.TransformerException;
31
32import org.apache.xalan.res.XSLMessages;
33import org.apache.xalan.res.XSLTErrorResources;
34import org.apache.xml.dtm.DTM;
35import org.apache.xml.dtm.DTMIterator;
36import org.apache.xml.utils.XMLString;
37import org.apache.xpath.Expression;
38import org.apache.xpath.NodeSetDTM;
39import org.apache.xpath.SourceTreeManager;
40import org.apache.xpath.XPathContext;
41import org.apache.xpath.functions.Function2Args;
42import org.apache.xpath.functions.WrongNumberArgsException;
43import org.apache.xpath.objects.XNodeSet;
44import 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 */
61public 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   */
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   */
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}
464