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: KeyTable.java 468645 2006-10-28 06:57:24Z minchau $ 20 */ 21 package org.apache.xalan.transformer; 22 23 import java.util.Hashtable; 24 import java.util.Vector; 25 26 import javax.xml.transform.TransformerException; 27 28 import org.apache.xalan.templates.KeyDeclaration; 29 import org.apache.xml.dtm.DTM; 30 import org.apache.xml.dtm.DTMIterator; 31 import org.apache.xml.utils.PrefixResolver; 32 import org.apache.xml.utils.QName; 33 import org.apache.xml.utils.WrappedRuntimeException; 34 import org.apache.xml.utils.XMLString; 35 import org.apache.xpath.XPathContext; 36 import org.apache.xpath.objects.XNodeSet; 37 import org.apache.xpath.objects.XObject; 38 39 /** 40 * Table of element keys, keyed by document node. An instance of this 41 * class is keyed by a Document node that should be matched with the 42 * root of the current context. 43 * @xsl.usage advanced 44 */ 45 public class KeyTable 46 { 47 /** 48 * The document key. This table should only be used with contexts 49 * whose Document roots match this key. 50 */ 51 private int m_docKey; 52 53 /** 54 * Vector of KeyDeclaration instances holding the key declarations. 55 */ 56 private Vector m_keyDeclarations; 57 58 /** 59 * Hold a cache of key() function result for each ref. 60 * Key is XMLString, the ref value 61 * Value is XNodeSet, the key() function result for the given ref value. 62 */ 63 private Hashtable m_refsTable = null; 64 65 /** 66 * Get the document root matching this key. 67 * 68 * @return the document root matching this key 69 */ getDocKey()70 public int getDocKey() 71 { 72 return m_docKey; 73 } 74 75 /** 76 * The main iterator that will walk through the source 77 * tree for this key. 78 */ 79 private XNodeSet m_keyNodes; 80 getKeyIterator()81 KeyIterator getKeyIterator() 82 { 83 return (KeyIterator)(m_keyNodes.getContainedIter()); 84 } 85 86 /** 87 * Build a keys table. 88 * @param doc The owner document key. 89 * @param nscontext The stylesheet's namespace context. 90 * @param name The key name 91 * @param keyDeclarations The stylesheet's xsl:key declarations. 92 * 93 * @throws javax.xml.transform.TransformerException 94 */ KeyTable( int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt)95 public KeyTable( 96 int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt) 97 throws javax.xml.transform.TransformerException 98 { 99 m_docKey = doc; 100 m_keyDeclarations = keyDeclarations; 101 KeyIterator ki = new KeyIterator(name, keyDeclarations); 102 103 m_keyNodes = new XNodeSet(ki); 104 m_keyNodes.allowDetachToRelease(false); 105 m_keyNodes.setRoot(doc, xctxt); 106 } 107 108 /** 109 * Given a valid element key, return the corresponding node list. 110 * 111 * @param name The name of the key, which must match the 'name' attribute on xsl:key. 112 * @param ref The value that must match the value found by the 'match' attribute on xsl:key. 113 * @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. 114 */ getNodeSetDTMByKey(QName name, XMLString ref)115 public XNodeSet getNodeSetDTMByKey(QName name, XMLString ref) 116 117 { 118 XNodeSet refNodes = (XNodeSet) getRefsTable().get(ref); 119 // clone wiht reset the node set 120 try 121 { 122 if (refNodes != null) 123 { 124 refNodes = (XNodeSet) refNodes.cloneWithReset(); 125 } 126 } 127 catch (CloneNotSupportedException e) 128 { 129 refNodes = null; 130 } 131 132 if (refNodes == null) { 133 // create an empty XNodeSet 134 KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter(); 135 XPathContext xctxt = ki.getXPathContext(); 136 refNodes = new XNodeSet(xctxt.getDTMManager()) { 137 public void setRoot(int nodeHandle, Object environment) { 138 // Root cannot be set on non-iterated node sets. Ignore it. 139 } 140 }; 141 refNodes.reset(); 142 } 143 144 return refNodes; 145 } 146 147 /** 148 * Get Key Name for this KeyTable 149 * 150 * @return Key name 151 */ getKeyTableName()152 public QName getKeyTableName() 153 { 154 return getKeyIterator().getName(); 155 } 156 157 /** 158 * @return key declarations for the key associated to this KeyTable 159 */ getKeyDeclarations()160 private Vector getKeyDeclarations() { 161 int nDeclarations = m_keyDeclarations.size(); 162 Vector keyDecls = new Vector(nDeclarations); 163 164 // Walk through each of the declarations made with xsl:key 165 for (int i = 0; i < nDeclarations; i++) 166 { 167 KeyDeclaration kd = (KeyDeclaration) m_keyDeclarations.elementAt(i); 168 169 // Add the declaration if the name on this key declaration 170 // matches the name on the iterator for this walker. 171 if (kd.getName().equals(getKeyTableName())) { 172 keyDecls.add(kd); 173 } 174 } 175 176 return keyDecls; 177 } 178 179 /** 180 * @return lazy initialized refs table associating evaluation of key function 181 * with a XNodeSet 182 */ getRefsTable()183 private Hashtable getRefsTable() 184 { 185 if (m_refsTable == null) { 186 // initial capacity set to a prime number to improve hash performance 187 m_refsTable = new Hashtable(89); 188 189 KeyIterator ki = (KeyIterator) (m_keyNodes).getContainedIter(); 190 XPathContext xctxt = ki.getXPathContext(); 191 192 Vector keyDecls = getKeyDeclarations(); 193 int nKeyDecls = keyDecls.size(); 194 195 int currentNode; 196 m_keyNodes.reset(); 197 while (DTM.NULL != (currentNode = m_keyNodes.nextNode())) 198 { 199 try 200 { 201 for (int keyDeclIdx = 0; keyDeclIdx < nKeyDecls; keyDeclIdx++) { 202 KeyDeclaration keyDeclaration = 203 (KeyDeclaration) keyDecls.elementAt(keyDeclIdx); 204 XObject xuse = 205 keyDeclaration.getUse().execute(xctxt, 206 currentNode, 207 ki.getPrefixResolver()); 208 209 if (xuse.getType() != xuse.CLASS_NODESET) { 210 XMLString exprResult = xuse.xstr(); 211 addValueInRefsTable(xctxt, exprResult, currentNode); 212 } else { 213 DTMIterator i = ((XNodeSet)xuse).iterRaw(); 214 int currentNodeInUseClause; 215 216 while (DTM.NULL != (currentNodeInUseClause = i.nextNode())) { 217 DTM dtm = xctxt.getDTM(currentNodeInUseClause); 218 XMLString exprResult = 219 dtm.getStringValue(currentNodeInUseClause); 220 addValueInRefsTable(xctxt, exprResult, currentNode); 221 } 222 } 223 } 224 } catch (TransformerException te) { 225 throw new WrappedRuntimeException(te); 226 } 227 } 228 } 229 return m_refsTable; 230 } 231 232 /** 233 * Add an association between a ref and a node in the m_refsTable. 234 * Requires that m_refsTable != null 235 * @param xctxt XPath context 236 * @param ref the value of the use clause of the current key for the given node 237 * @param node the node to reference 238 */ addValueInRefsTable(XPathContext xctxt, XMLString ref, int node)239 private void addValueInRefsTable(XPathContext xctxt, XMLString ref, int node) { 240 241 XNodeSet nodes = (XNodeSet) m_refsTable.get(ref); 242 if (nodes == null) 243 { 244 nodes = new XNodeSet(node, xctxt.getDTMManager()); 245 nodes.nextNode(); 246 m_refsTable.put(ref, nodes); 247 } 248 else 249 { 250 // Nodes are passed to this method in document order. Since we need to 251 // suppress duplicates, we only need to check against the last entry 252 // in each nodeset. We use nodes.nextNode after each entry so we can 253 // easily compare node against the current node. 254 if (nodes.getCurrentNode() != node) { 255 nodes.mutableNodeset().addNode(node); 256 nodes.nextNode(); 257 } 258 } 259 } 260 } 261