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