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    }