001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: KeyTable.java 468645 2006-10-28 06:57:24Z minchau $ 020 */ 021 package org.apache.xalan.transformer; 022 023 import java.util.Hashtable; 024 import java.util.Vector; 025 026 import javax.xml.transform.TransformerException; 027 028 import org.apache.xalan.templates.KeyDeclaration; 029 import org.apache.xml.dtm.DTM; 030 import org.apache.xml.dtm.DTMIterator; 031 import org.apache.xml.utils.PrefixResolver; 032 import org.apache.xml.utils.QName; 033 import org.apache.xml.utils.WrappedRuntimeException; 034 import org.apache.xml.utils.XMLString; 035 import org.apache.xpath.XPathContext; 036 import org.apache.xpath.objects.XNodeSet; 037 import org.apache.xpath.objects.XObject; 038 039 /** 040 * Table of element keys, keyed by document node. An instance of this 041 * class is keyed by a Document node that should be matched with the 042 * root of the current context. 043 * @xsl.usage advanced 044 */ 045 public class KeyTable 046 { 047 /** 048 * The document key. This table should only be used with contexts 049 * whose Document roots match this key. 050 */ 051 private int m_docKey; 052 053 /** 054 * Vector of KeyDeclaration instances holding the key declarations. 055 */ 056 private Vector m_keyDeclarations; 057 058 /** 059 * Hold a cache of key() function result for each ref. 060 * Key is XMLString, the ref value 061 * Value is XNodeSet, the key() function result for the given ref value. 062 */ 063 private Hashtable m_refsTable = null; 064 065 /** 066 * Get the document root matching this key. 067 * 068 * @return the document root matching this key 069 */ 070 public int getDocKey() 071 { 072 return m_docKey; 073 } 074 075 /** 076 * The main iterator that will walk through the source 077 * tree for this key. 078 */ 079 private XNodeSet m_keyNodes; 080 081 KeyIterator getKeyIterator() 082 { 083 return (KeyIterator)(m_keyNodes.getContainedIter()); 084 } 085 086 /** 087 * Build a keys table. 088 * @param doc The owner document key. 089 * @param nscontext The stylesheet's namespace context. 090 * @param name The key name 091 * @param keyDeclarations The stylesheet's xsl:key declarations. 092 * 093 * @throws javax.xml.transform.TransformerException 094 */ 095 public KeyTable( 096 int doc, PrefixResolver nscontext, QName name, Vector keyDeclarations, XPathContext xctxt) 097 throws javax.xml.transform.TransformerException 098 { 099 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 */ 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 */ 152 public QName getKeyTableName() 153 { 154 return getKeyIterator().getName(); 155 } 156 157 /** 158 * @return key declarations for the key associated to this KeyTable 159 */ 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 */ 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 */ 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 }