Home | History | Annotate | Download | only in templates
      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    */
     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