| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * $Id: FuncDocument.java 468643 2006-10-28 06:56:03Z minchau $ |
| */ |
| package org.apache.xalan.templates; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| |
| import javax.xml.transform.ErrorListener; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.SourceLocator; |
| import javax.xml.transform.TransformerException; |
| |
| import org.apache.xalan.res.XSLMessages; |
| import org.apache.xalan.res.XSLTErrorResources; |
| import org.apache.xml.dtm.DTM; |
| import org.apache.xml.dtm.DTMIterator; |
| import org.apache.xml.utils.XMLString; |
| import org.apache.xpath.Expression; |
| import org.apache.xpath.NodeSetDTM; |
| import org.apache.xpath.SourceTreeManager; |
| import org.apache.xpath.XPathContext; |
| import org.apache.xpath.functions.Function2Args; |
| import org.apache.xpath.functions.WrongNumberArgsException; |
| import org.apache.xpath.objects.XNodeSet; |
| import org.apache.xpath.objects.XObject; |
| |
| /** |
| * Execute the Doc() function. |
| * |
| * When the document function has exactly one argument and the argument |
| * is a node-set, then the result is the union, for each node in the |
| * argument node-set, of the result of calling the document function with |
| * the first argument being the string-value of the node, and the second |
| * argument being a node-set with the node as its only member. When the |
| * document function has two arguments and the first argument is a node-set, |
| * then the result is the union, for each node in the argument node-set, |
| * of the result of calling the document function with the first argument |
| * being the string-value of the node, and with the second argument being |
| * the second argument passed to the document function. |
| * @xsl.usage advanced |
| */ |
| public class FuncDocument extends Function2Args |
| { |
| static final long serialVersionUID = 2483304325971281424L; |
| |
| /** |
| * Execute the function. The function must return |
| * a valid object. |
| * @param xctxt The current execution context. |
| * @return A valid XObject. |
| * |
| * @throws javax.xml.transform.TransformerException |
| */ |
| public XObject execute(XPathContext xctxt) throws javax.xml.transform.TransformerException |
| { |
| int context = xctxt.getCurrentNode(); |
| DTM dtm = xctxt.getDTM(context); |
| |
| int docContext = dtm.getDocumentRoot(context); |
| XObject arg = (XObject) this.getArg0().execute(xctxt); |
| |
| String base = ""; |
| Expression arg1Expr = this.getArg1(); |
| |
| if (null != arg1Expr) |
| { |
| |
| // The URI reference may be relative. The base URI (see [3.2 Base URI]) |
| // of the node in the second argument node-set that is first in document |
| // order is used as the base URI for resolving the |
| // relative URI into an absolute URI. |
| XObject arg2 = arg1Expr.execute(xctxt); |
| |
| if (XObject.CLASS_NODESET == arg2.getType()) |
| { |
| int baseNode = arg2.iter().nextNode(); |
| |
| if (baseNode == DTM.NULL) |
| { |
| // See http://www.w3.org/1999/11/REC-xslt-19991116-errata#E14. |
| // If the second argument is an empty nodeset, this is an error. |
| // The processor can recover by returning an empty nodeset. |
| warn(xctxt, XSLTErrorResources.WG_EMPTY_SECOND_ARG, null); |
| XNodeSet nodes = new XNodeSet(xctxt.getDTMManager()); |
| return nodes; |
| } else{ |
| DTM baseDTM = xctxt.getDTM(baseNode); |
| base = baseDTM.getDocumentBaseURI(); |
| } |
| // %REVIEW% This doesn't seem to be a problem with the conformance |
| // suite, but maybe it's just not doing a good test? |
| // int baseDoc = baseDTM.getDocument(); |
| // |
| // if (baseDoc == DTM.NULL /* || baseDoc instanceof Stylesheet -->What to do?? */) |
| // { |
| // |
| // // base = ((Stylesheet)baseDoc).getBaseIdentifier(); |
| // base = xctxt.getNamespaceContext().getBaseIdentifier(); |
| // } |
| // else |
| // base = xctxt.getSourceTreeManager().findURIFromDoc(baseDoc); |
| } |
| else |
| { |
| //Can not convert other type to a node-set!; |
| arg2.iter(); |
| } |
| } |
| else |
| { |
| |
| // If the second argument is omitted, then it defaults to |
| // the node in the stylesheet that contains the expression that |
| // includes the call to the document function. Note that a |
| // zero-length URI reference is a reference to the document |
| // relative to which the URI reference is being resolved; thus |
| // document("") refers to the root node of the stylesheet; |
| // the tree representation of the stylesheet is exactly |
| // the same as if the XML document containing the stylesheet |
| // was the initial source document. |
| assertion(null != xctxt.getNamespaceContext(), "Namespace context can not be null!"); |
| base = xctxt.getNamespaceContext().getBaseIdentifier(); |
| } |
| |
| XNodeSet nodes = new XNodeSet(xctxt.getDTMManager()); |
| NodeSetDTM mnl = nodes.mutableNodeset(); |
| DTMIterator iterator = (XObject.CLASS_NODESET == arg.getType()) |
| ? arg.iter() : null; |
| int pos = DTM.NULL; |
| |
| while ((null == iterator) || (DTM.NULL != (pos = iterator.nextNode()))) |
| { |
| XMLString ref = (null != iterator) |
| ? xctxt.getDTM(pos).getStringValue(pos) : arg.xstr(); |
| |
| // The first and only argument was a nodeset, the base in that |
| // case is the base URI of the node from the first argument nodeset. |
| // Remember, when the document function has exactly one argument and |
| // the argument is a node-set, then the result is the union, for each |
| // node in the argument node-set, of the result of calling the document |
| // function with the first argument being the string-value of the node, |
| // and the second argument being a node-set with the node as its only |
| // member. |
| if (null == arg1Expr && DTM.NULL != pos) |
| { |
| DTM baseDTM = xctxt.getDTM(pos); |
| base = baseDTM.getDocumentBaseURI(); |
| } |
| |
| if (null == ref) |
| continue; |
| |
| if (DTM.NULL == docContext) |
| { |
| error(xctxt, XSLTErrorResources.ER_NO_CONTEXT_OWNERDOC, null); //"context does not have an owner document!"); |
| } |
| |
| // From http://www.ics.uci.edu/pub/ietf/uri/rfc1630.txt |
| // A partial form can be distinguished from an absolute form in that the |
| // latter must have a colon and that colon must occur before any slash |
| // characters. Systems not requiring partial forms should not use any |
| // unencoded slashes in their naming schemes. If they do, absolute URIs |
| // will still work, but confusion may result. |
| int indexOfColon = ref.indexOf(':'); |
| int indexOfSlash = ref.indexOf('/'); |
| |
| if ((indexOfColon != -1) && (indexOfSlash != -1) |
| && (indexOfColon < indexOfSlash)) |
| { |
| |
| // The url (or filename, for that matter) is absolute. |
| base = null; |
| } |
| |
| int newDoc = getDoc(xctxt, context, ref.toString(), base); |
| |
| // nodes.mutableNodeset().addNode(newDoc); |
| if (DTM.NULL != newDoc) |
| { |
| // TODO: mnl.addNodeInDocOrder(newDoc, true, xctxt); ?? |
| if (!mnl.contains(newDoc)) |
| { |
| mnl.addElement(newDoc); |
| } |
| } |
| |
| if (null == iterator || newDoc == DTM.NULL) |
| break; |
| } |
| |
| return nodes; |
| } |
| |
| /** |
| * Get the document from the given URI and base |
| * |
| * @param xctxt The XPath runtime state. |
| * @param context The current context node |
| * @param uri Relative(?) URI of the document |
| * @param base Base to resolve relative URI from. |
| * |
| * @return The document Node pointing to the document at the given URI |
| * or null |
| * |
| * @throws javax.xml.transform.TransformerException |
| */ |
| int getDoc(XPathContext xctxt, int context, String uri, String base) |
| throws javax.xml.transform.TransformerException |
| { |
| |
| // System.out.println("base: "+base+", uri: "+uri); |
| SourceTreeManager treeMgr = xctxt.getSourceTreeManager(); |
| Source source; |
| |
| int newDoc; |
| try |
| { |
| source = treeMgr.resolveURI(base, uri, xctxt.getSAXLocator()); |
| newDoc = treeMgr.getNode(source); |
| } |
| catch (IOException ioe) |
| { |
| throw new TransformerException(ioe.getMessage(), |
| (SourceLocator)xctxt.getSAXLocator(), ioe); |
| } |
| catch(TransformerException te) |
| { |
| throw new TransformerException(te); |
| } |
| |
| if (DTM.NULL != newDoc) |
| return newDoc; |
| |
| // If the uri length is zero, get the uri of the stylesheet. |
| if (uri.length() == 0) |
| { |
| // Hmmm... this seems pretty bogus to me... -sb |
| uri = xctxt.getNamespaceContext().getBaseIdentifier(); |
| try |
| { |
| source = treeMgr.resolveURI(base, uri, xctxt.getSAXLocator()); |
| } |
| catch (IOException ioe) |
| { |
| throw new TransformerException(ioe.getMessage(), |
| (SourceLocator)xctxt.getSAXLocator(), ioe); |
| } |
| } |
| |
| String diagnosticsString = null; |
| |
| try |
| { |
| if ((null != uri) && (uri.length() > 0)) |
| { |
| newDoc = treeMgr.getSourceTree(source, xctxt.getSAXLocator(), xctxt); |
| |
| // System.out.println("newDoc: "+((Document)newDoc).getDocumentElement().getNodeName()); |
| } |
| else |
| warn(xctxt, XSLTErrorResources.WG_CANNOT_MAKE_URL_FROM, |
| new Object[]{ ((base == null) ? "" : base) + uri }); //"Can not make URL from: "+((base == null) ? "" : base )+uri); |
| } |
| catch (Throwable throwable) |
| { |
| |
| // throwable.printStackTrace(); |
| newDoc = DTM.NULL; |
| |
| // path.warn(XSLTErrorResources.WG_ENCODING_NOT_SUPPORTED_USING_JAVA, new Object[]{((base == null) ? "" : base )+uri}); //"Can not load requested doc: "+((base == null) ? "" : base )+uri); |
| while (throwable |
| instanceof org.apache.xml.utils.WrappedRuntimeException) |
| { |
| throwable = |
| ((org.apache.xml.utils.WrappedRuntimeException) throwable).getException(); |
| } |
| |
| if ((throwable instanceof NullPointerException) |
| || (throwable instanceof ClassCastException)) |
| { |
| throw new org.apache.xml.utils.WrappedRuntimeException( |
| (Exception) throwable); |
| } |
| |
| StringWriter sw = new StringWriter(); |
| PrintWriter diagnosticsWriter = new PrintWriter(sw); |
| |
| if (throwable instanceof TransformerException) |
| { |
| TransformerException spe = (TransformerException) throwable; |
| |
| { |
| Throwable e = spe; |
| |
| while (null != e) |
| { |
| if (null != e.getMessage()) |
| { |
| diagnosticsWriter.println(" (" + e.getClass().getName() + "): " |
| + e.getMessage()); |
| } |
| |
| if (e instanceof TransformerException) |
| { |
| TransformerException spe2 = (TransformerException) e; |
| |
| SourceLocator locator = spe2.getLocator(); |
| if ((null != locator) && (null != locator.getSystemId())) |
| diagnosticsWriter.println(" ID: " + locator.getSystemId() |
| + " Line #" + locator.getLineNumber() |
| + " Column #" |
| + locator.getColumnNumber()); |
| |
| e = spe2.getException(); |
| |
| if (e instanceof org.apache.xml.utils.WrappedRuntimeException) |
| e = ((org.apache.xml.utils.WrappedRuntimeException) e).getException(); |
| } |
| else |
| e = null; |
| } |
| } |
| } |
| else |
| { |
| diagnosticsWriter.println(" (" + throwable.getClass().getName() |
| + "): " + throwable.getMessage()); |
| } |
| |
| diagnosticsString = throwable.getMessage(); //sw.toString(); |
| } |
| |
| if (DTM.NULL == newDoc) |
| { |
| |
| // System.out.println("what?: "+base+", uri: "+uri); |
| if (null != diagnosticsString) |
| { |
| warn(xctxt, XSLTErrorResources.WG_CANNOT_LOAD_REQUESTED_DOC, |
| new Object[]{ diagnosticsString }); //"Can not load requested doc: "+((base == null) ? "" : base )+uri); |
| } |
| else |
| warn(xctxt, XSLTErrorResources.WG_CANNOT_LOAD_REQUESTED_DOC, |
| new Object[]{ |
| uri == null |
| ? ((base == null) ? "" : base) + uri : uri.toString() }); //"Can not load requested doc: "+((base == null) ? "" : base )+uri); |
| } |
| else |
| { |
| // %REVIEW% |
| // TBD: What to do about XLocator? |
| // xctxt.getSourceTreeManager().associateXLocatorToNode(newDoc, url, null); |
| } |
| |
| return newDoc; |
| } |
| |
| /** |
| * Tell the user of an error, and probably throw an |
| * exception. |
| * |
| * @param xctxt The XPath runtime state. |
| * @param msg The error message key |
| * @param args Arguments to be used in the error message |
| * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide |
| * the error condition is severe enough to halt processing. |
| * |
| * @throws javax.xml.transform.TransformerException |
| */ |
| public void error(XPathContext xctxt, String msg, Object args[]) |
| throws javax.xml.transform.TransformerException |
| { |
| |
| String formattedMsg = XSLMessages.createMessage(msg, args); |
| ErrorListener errHandler = xctxt.getErrorListener(); |
| TransformerException spe = new TransformerException(formattedMsg, |
| (SourceLocator)xctxt.getSAXLocator()); |
| |
| if (null != errHandler) |
| errHandler.error(spe); |
| else |
| System.out.println(formattedMsg); |
| } |
| |
| /** |
| * Warn the user of a problem. |
| * |
| * @param xctxt The XPath runtime state. |
| * @param msg Warning message key |
| * @param args Arguments to be used in the warning message |
| * @throws XSLProcessorException thrown if the active ProblemListener and XPathContext decide |
| * the error condition is severe enough to halt processing. |
| * |
| * @throws javax.xml.transform.TransformerException |
| */ |
| public void warn(XPathContext xctxt, String msg, Object args[]) |
| throws javax.xml.transform.TransformerException |
| { |
| |
| String formattedMsg = XSLMessages.createWarning(msg, args); |
| ErrorListener errHandler = xctxt.getErrorListener(); |
| TransformerException spe = new TransformerException(formattedMsg, |
| (SourceLocator)xctxt.getSAXLocator()); |
| |
| if (null != errHandler) |
| errHandler.warning(spe); |
| else |
| System.out.println(formattedMsg); |
| } |
| |
| /** |
| * Overide the superclass method to allow one or two arguments. |
| * |
| * |
| * @param argNum Number of arguments passed in to this function |
| * |
| * @throws WrongNumberArgsException |
| */ |
| public void checkNumberArgs(int argNum) throws WrongNumberArgsException |
| { |
| if ((argNum < 1) || (argNum > 2)) |
| reportWrongNumberArgs(); |
| } |
| |
| /** |
| * Constructs and throws a WrongNumberArgException with the appropriate |
| * message for this function object. |
| * |
| * @throws WrongNumberArgsException |
| */ |
| protected void reportWrongNumberArgs() throws WrongNumberArgsException { |
| throw new WrongNumberArgsException(XSLMessages.createMessage(XSLTErrorResources.ER_ONE_OR_TWO, null)); //"1 or 2"); |
| } |
| |
| /** |
| * Tell if the expression is a nodeset expression. |
| * @return true if the expression can be represented as a nodeset. |
| */ |
| public boolean isNodesetExpr() |
| { |
| return true; |
| } |
| |
| } |