1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. *
  5. * Copyright (c) 1999 The Apache Software Foundation. All rights
  6. * reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * 1. Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * 2. Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * 3. The end-user documentation included with the redistribution,
  21. * if any, must include the following acknowledgment:
  22. * "This product includes software developed by the
  23. * Apache Software Foundation (http://www.apache.org/)."
  24. * Alternately, this acknowledgment may appear in the software itself,
  25. * if and wherever such third-party acknowledgments normally appear.
  26. *
  27. * 4. The names "Xalan" and "Apache Software Foundation" must
  28. * not be used to endorse or promote products derived from this
  29. * software without prior written permission. For written
  30. * permission, please contact apache@apache.org.
  31. *
  32. * 5. Products derived from this software may not be called "Apache",
  33. * nor may "Apache" appear in their name, without prior written
  34. * permission of the Apache Software Foundation.
  35. *
  36. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  37. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  38. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  39. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  40. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  41. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  42. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  43. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  44. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  45. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  46. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  47. * SUCH DAMAGE.
  48. * ====================================================================
  49. *
  50. * This software consists of voluntary contributions made by many
  51. * individuals on behalf of the Apache Software Foundation and was
  52. * originally based on software copyright (c) 1999, Lotus
  53. * Development Corporation., http://www.lotus.com. For more
  54. * information on the Apache Software Foundation, please see
  55. * <http://www.apache.org/>.
  56. */
  57. package org.apache.xalan.transformer;
  58. import java.util.Hashtable;
  59. import java.util.Vector;
  60. import javax.xml.transform.TransformerException;
  61. import org.apache.xml.utils.NodeVector;
  62. import org.apache.xml.utils.PrefixResolver;
  63. import org.apache.xml.utils.QName;
  64. import org.apache.xml.utils.XMLString;
  65. import org.apache.xml.utils.WrappedRuntimeException;
  66. import org.apache.xml.dtm.DTM;
  67. import org.apache.xml.dtm.DTMIterator;
  68. import org.apache.xpath.XPathContext;
  69. import org.apache.xpath.objects.XNodeSet;
  70. import org.apache.xpath.objects.XObject;
  71. import org.apache.xpath.objects.XNull;
  72. import org.apache.xalan.templates.KeyDeclaration;
  73. /**
  74. * <meta name="usage" content="advanced"/>
  75. * Table of element keys, keyed by document node. An instance of this
  76. * class is keyed by a Document node that should be matched with the
  77. * root of the current context.
  78. */
  79. public class KeyTable
  80. {
  81. /**
  82. * The document key. This table should only be used with contexts
  83. * whose Document roots match this key.
  84. */
  85. private int m_docKey;
  86. /**
  87. * Vector of KeyDeclaration instances holding the key declarations.
  88. */
  89. private Vector m_keyDeclarations;
  90. /**
  91. * Hold a cache of key() function result for each ref.
  92. * Key is XMLString, the ref value
  93. * Value is XNodeSet, the key() function result for the given ref value.
  94. */
  95. private Hashtable m_refsTable = null;
  96. /**
  97. * Get the document root matching this key.
  98. *
  99. * @return the document root matching this key
  100. */
  101. public int getDocKey()
  102. {
  103. return m_docKey;
  104. }
  105. /**
  106. * The main iterator that will walk through the source
  107. * tree for this key.
  108. */
  109. private XNodeSet m_keyNodes;
  110. KeyIterator getKeyIterator()
  111. {
  112. return (KeyIterator)(m_keyNodes.getContainedIter());
  113. }
  114. /**
  115. * Build a keys table.
  116. * @param doc The owner document key.
  117. * @param nscontext The stylesheet's namespace context.
  118. * @param name The key name
  119. * @param keyDeclarations The stylesheet's xsl:key declarations.
  120. * @param xmlLiaison The parser liaison for support of getNodeData(useNode).
  121. *
  122. * @throws javax.xml.transform.TransformerException
  123. */
  124. public KeyTable(
  125. int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt)
  126. throws javax.xml.transform.TransformerException
  127. {
  128. m_docKey = doc;
  129. m_keyDeclarations = keyDeclarations;
  130. KeyIterator ki = new KeyIterator(name, keyDeclarations);
  131. m_keyNodes = new XNodeSet(ki);
  132. m_keyNodes.allowDetachToRelease(false);
  133. m_keyNodes.setRoot(doc, xctxt);
  134. }
  135. /**
  136. * Given a valid element key, return the corresponding node list.
  137. *
  138. * @param name The name of the key, which must match the 'name' attribute on xsl:key.
  139. * @param ref The value that must match the value found by the 'match' attribute on xsl:key.
  140. * @return a set of nodes referenced by the key named <CODE>name</CODE> and the reference <CODE>ref</CODE>. If no node is referenced by this key, an empty node set is returned.
  141. */
  142. public XNodeSet getNodeSetDTMByKey(QName name, XMLString ref)
  143. {
  144. XNodeSet refNodes = (XNodeSet) getRefsTable().get(ref);
  145. // clone wiht reset the node set
  146. try
  147. {
  148. if (refNodes != null)
  149. {
  150. refNodes = (XNodeSet) refNodes.cloneWithReset();
  151. }
  152. }
  153. catch (CloneNotSupportedException e)
  154. {
  155. refNodes = null;
  156. }
  157. if (refNodes == null) {
  158. // create an empty XNodeSet
  159. KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter();
  160. XPathContext xctxt = ki.getXPathContext();
  161. refNodes = new XNodeSet(xctxt.getDTMManager()) {
  162. public void setRoot(int nodeHandle, Object environment) {
  163. // Root cannot be set on non-iterated node sets. Ignore it.
  164. }
  165. };
  166. refNodes.reset();
  167. }
  168. return refNodes;
  169. }
  170. /**
  171. * Get Key Name for this KeyTable
  172. *
  173. * @return Key name
  174. */
  175. public QName getKeyTableName()
  176. {
  177. return getKeyIterator().getName();
  178. }
  179. /**
  180. * @return key declaration for the key associated to this KeyTable
  181. */
  182. private KeyDeclaration getKeyDeclaration() {
  183. int nDeclarations = m_keyDeclarations.size();
  184. // Walk through each of the declarations made with xsl:key
  185. for (int i = 0; i < nDeclarations; i++)
  186. {
  187. KeyDeclaration kd = (KeyDeclaration) m_keyDeclarations.elementAt(i);
  188. // Only continue if the name on this key declaration
  189. // matches the name on the iterator for this walker.
  190. if (kd.getName().equals(getKeyTableName()))
  191. {
  192. return kd;
  193. }
  194. }
  195. // should never happen
  196. return null;
  197. }
  198. /**
  199. * @return lazy initialized refs table associating evaluation of key function
  200. * with a XNodeSet
  201. */
  202. private Hashtable getRefsTable()
  203. {
  204. if (m_refsTable == null)
  205. {
  206. m_refsTable = new Hashtable(89); // initial capacity set to a prime number to improve hash algorithm performance
  207. KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter();
  208. XPathContext xctxt = ki.getXPathContext();
  209. KeyDeclaration keyDeclaration = getKeyDeclaration();
  210. int currentNode;
  211. m_keyNodes.reset();
  212. while (DTM.NULL != (currentNode = m_keyNodes.nextNode()))
  213. {
  214. try
  215. {
  216. XObject xuse = keyDeclaration.getUse().execute(xctxt, currentNode, ki.getPrefixResolver());
  217. if (xuse.getType() != xuse.CLASS_NODESET)
  218. {
  219. XMLString exprResult = xuse.xstr();
  220. addValueInRefsTable(xctxt, exprResult, currentNode);
  221. }
  222. else
  223. {
  224. DTMIterator i = ((XNodeSet)xuse).iterRaw();
  225. int currentNodeInUseClause;
  226. while (DTM.NULL != (currentNodeInUseClause = i.nextNode()))
  227. {
  228. DTM dtm = xctxt.getDTM(currentNodeInUseClause);
  229. XMLString exprResult = dtm.getStringValue(currentNodeInUseClause);
  230. addValueInRefsTable(xctxt, exprResult, currentNode);
  231. }
  232. }
  233. }
  234. catch (TransformerException te)
  235. {
  236. throw new WrappedRuntimeException(te);
  237. }
  238. }
  239. }
  240. return m_refsTable;
  241. }
  242. /**
  243. * Add an association between a ref and a node in the m_refsTable.
  244. * Requires that m_refsTable != null
  245. * @param xctxt XPath context
  246. * @param ref the value of the use clause of the current key for the given node
  247. * @param node the node to reference
  248. */
  249. private void addValueInRefsTable(XPathContext xctxt, XMLString ref, int node) {
  250. XNodeSet nodes = (XNodeSet) m_refsTable.get(ref);
  251. if (nodes == null)
  252. {
  253. nodes = new XNodeSet(node, xctxt.getDTMManager());
  254. nodes.nextNode();
  255. m_refsTable.put(ref, nodes);
  256. }
  257. else
  258. {
  259. // Nodes are passed to this method in document order. Since we need to
  260. // suppress duplicates, we only need to check against the last entry
  261. // in each nodeset. We use nodes.nextNode after each entry so we can
  262. // easily compare node against the current node.
  263. if (nodes.getCurrentNode() != node) {
  264. nodes.mutableNodeset().addNode(node);
  265. nodes.nextNode();
  266. }
  267. }
  268. }
  269. }