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: ElemForEach.java 468643 2006-10-28 06:56:03Z minchau $ 020 */ 021 package org.apache.xalan.templates; 022 023 import java.util.Vector; 024 025 import javax.xml.transform.TransformerException; 026 027 import org.apache.xalan.transformer.NodeSorter; 028 import org.apache.xalan.transformer.TransformerImpl; 029 import org.apache.xml.dtm.DTM; 030 import org.apache.xml.dtm.DTMIterator; 031 import org.apache.xml.dtm.DTMManager; 032 import org.apache.xml.utils.IntStack; 033 import org.apache.xpath.Expression; 034 import org.apache.xpath.ExpressionOwner; 035 import org.apache.xpath.XPath; 036 import org.apache.xpath.XPathContext; 037 038 import java.io.ObjectInputStream; 039 import java.io.IOException; 040 041 /** 042 * Implement xsl:for-each. 043 * <pre> 044 * <!ELEMENT xsl:for-each 045 * (#PCDATA 046 * %instructions; 047 * %result-elements; 048 * | xsl:sort) 049 * > 050 * 051 * <!ATTLIST xsl:for-each 052 * select %expr; #REQUIRED 053 * %space-att; 054 * > 055 * </pre> 056 * @see <a href="http://www.w3.org/TR/xslt#for-each">for-each in XSLT Specification</a> 057 * @xsl.usage advanced 058 */ 059 public class ElemForEach extends ElemTemplateElement implements ExpressionOwner 060 { 061 static final long serialVersionUID = 6018140636363583690L; 062 /** Set true to request some basic status reports */ 063 static final boolean DEBUG = false; 064 065 /** 066 * This is set by an "xalan-doc-cache-off" pi, or the old "xalan:doc-cache-off" pi. 067 * The old form of the PI only works for XML parsers that are not namespace aware. 068 * It tells the engine that 069 * documents created in the location paths executed by this element 070 * will not be reparsed. It's set by StylesheetHandler during 071 * construction. Note that this feature applies _only_ to xsl:for-each 072 * elements in its current incarnation; a more general cache management 073 * solution is desperately needed. 074 */ 075 public boolean m_doc_cache_off=false; 076 077 /** 078 * Construct a element representing xsl:for-each. 079 */ 080 public ElemForEach(){} 081 082 /** 083 * The "select" expression. 084 * @serial 085 */ 086 protected Expression m_selectExpression = null; 087 088 089 /** 090 * Used to fix bug#16889 091 * Store XPath away for later processing. 092 */ 093 protected XPath m_xpath = null; 094 095 /** 096 * Set the "select" attribute. 097 * 098 * @param xpath The XPath expression for the "select" attribute. 099 */ 100 public void setSelect(XPath xpath) 101 { 102 m_selectExpression = xpath.getExpression(); 103 104 // The following line is part of the codes added to fix bug#16889 105 // Store xpath which will be needed when firing Selected Event 106 m_xpath = xpath; 107 } 108 109 /** 110 * Get the "select" attribute. 111 * 112 * @return The XPath expression for the "select" attribute. 113 */ 114 public Expression getSelect() 115 { 116 return m_selectExpression; 117 } 118 119 /** 120 * This function is called after everything else has been 121 * recomposed, and allows the template to set remaining 122 * values that may be based on some other property that 123 * depends on recomposition. 124 * 125 * NEEDSDOC @param sroot 126 * 127 * @throws TransformerException 128 */ 129 public void compose(StylesheetRoot sroot) throws TransformerException 130 { 131 132 super.compose(sroot); 133 134 int length = getSortElemCount(); 135 136 for (int i = 0; i < length; i++) 137 { 138 getSortElem(i).compose(sroot); 139 } 140 141 java.util.Vector vnames = sroot.getComposeState().getVariableNames(); 142 143 if (null != m_selectExpression) 144 m_selectExpression.fixupVariables( 145 vnames, sroot.getComposeState().getGlobalsSize()); 146 else 147 { 148 m_selectExpression = 149 getStylesheetRoot().m_selectDefault.getExpression(); 150 } 151 } 152 153 /** 154 * This after the template's children have been composed. 155 */ 156 public void endCompose(StylesheetRoot sroot) throws TransformerException 157 { 158 int length = getSortElemCount(); 159 160 for (int i = 0; i < length; i++) 161 { 162 getSortElem(i).endCompose(sroot); 163 } 164 165 super.endCompose(sroot); 166 } 167 168 169 // /** 170 // * This function is called after everything else has been 171 // * recomposed, and allows the template to set remaining 172 // * values that may be based on some other property that 173 // * depends on recomposition. 174 // * 175 // * @throws TransformerException 176 // */ 177 // public void compose() throws TransformerException 178 // { 179 // 180 // if (null == m_selectExpression) 181 // { 182 // m_selectExpression = 183 // getStylesheetRoot().m_selectDefault.getExpression(); 184 // } 185 // } 186 187 /** 188 * Vector containing the xsl:sort elements associated with this element. 189 * @serial 190 */ 191 protected Vector m_sortElems = null; 192 193 /** 194 * Get the count xsl:sort elements associated with this element. 195 * @return The number of xsl:sort elements. 196 */ 197 public int getSortElemCount() 198 { 199 return (m_sortElems == null) ? 0 : m_sortElems.size(); 200 } 201 202 /** 203 * Get a xsl:sort element associated with this element. 204 * 205 * @param i Index of xsl:sort element to get 206 * 207 * @return xsl:sort element at given index 208 */ 209 public ElemSort getSortElem(int i) 210 { 211 return (ElemSort) m_sortElems.elementAt(i); 212 } 213 214 /** 215 * Set a xsl:sort element associated with this element. 216 * 217 * @param sortElem xsl:sort element to set 218 */ 219 public void setSortElem(ElemSort sortElem) 220 { 221 222 if (null == m_sortElems) 223 m_sortElems = new Vector(); 224 225 m_sortElems.addElement(sortElem); 226 } 227 228 /** 229 * Get an int constant identifying the type of element. 230 * @see org.apache.xalan.templates.Constants 231 * 232 * @return The token ID for this element 233 */ 234 public int getXSLToken() 235 { 236 return Constants.ELEMNAME_FOREACH; 237 } 238 239 /** 240 * Return the node name. 241 * 242 * @return The element's name 243 */ 244 public String getNodeName() 245 { 246 return Constants.ELEMNAME_FOREACH_STRING; 247 } 248 249 /** 250 * Execute the xsl:for-each transformation 251 * 252 * @param transformer non-null reference to the the current transform-time state. 253 * 254 * @throws TransformerException 255 */ 256 public void execute(TransformerImpl transformer) throws TransformerException 257 { 258 259 transformer.pushCurrentTemplateRuleIsNull(true); 260 if (transformer.getDebug()) 261 transformer.getTraceManager().fireTraceEvent(this);//trigger for-each element event 262 263 try 264 { 265 transformSelectedNodes(transformer); 266 } 267 finally 268 { 269 if (transformer.getDebug()) 270 transformer.getTraceManager().fireTraceEndEvent(this); 271 transformer.popCurrentTemplateRuleIsNull(); 272 } 273 } 274 275 /** 276 * Get template element associated with this 277 * 278 * 279 * @return template element associated with this (itself) 280 */ 281 protected ElemTemplateElement getTemplateMatch() 282 { 283 return this; 284 } 285 286 /** 287 * Sort given nodes 288 * 289 * 290 * @param xctxt The XPath runtime state for the sort. 291 * @param keys Vector of sort keyx 292 * @param sourceNodes Iterator of nodes to sort 293 * 294 * @return iterator of sorted nodes 295 * 296 * @throws TransformerException 297 */ 298 public DTMIterator sortNodes( 299 XPathContext xctxt, Vector keys, DTMIterator sourceNodes) 300 throws TransformerException 301 { 302 303 NodeSorter sorter = new NodeSorter(xctxt); 304 sourceNodes.setShouldCacheNodes(true); 305 sourceNodes.runTo(-1); 306 xctxt.pushContextNodeList(sourceNodes); 307 308 try 309 { 310 sorter.sort(sourceNodes, keys, xctxt); 311 sourceNodes.setCurrentPos(0); 312 } 313 finally 314 { 315 xctxt.popContextNodeList(); 316 } 317 318 return sourceNodes; 319 } 320 321 /** 322 * Perform a query if needed, and call transformNode for each child. 323 * 324 * @param transformer non-null reference to the the current transform-time state. 325 * 326 * @throws TransformerException Thrown in a variety of circumstances. 327 * @xsl.usage advanced 328 */ 329 public void transformSelectedNodes(TransformerImpl transformer) 330 throws TransformerException 331 { 332 333 final XPathContext xctxt = transformer.getXPathContext(); 334 final int sourceNode = xctxt.getCurrentNode(); 335 DTMIterator sourceNodes = m_selectExpression.asIterator(xctxt, 336 sourceNode); 337 338 try 339 { 340 341 final Vector keys = (m_sortElems == null) 342 ? null 343 : transformer.processSortKeys(this, sourceNode); 344 345 // Sort if we need to. 346 if (null != keys) 347 sourceNodes = sortNodes(xctxt, keys, sourceNodes); 348 349 if (transformer.getDebug()) 350 { 351 352 // The original code, which is broken for bug#16889, 353 // which fails to get the original select expression in the select event. 354 /* transformer.getTraceManager().fireSelectedEvent( 355 * sourceNode, 356 * this, 357 * "select", 358 * new XPath(m_selectExpression), 359 * new org.apache.xpath.objects.XNodeSet(sourceNodes)); 360 */ 361 362 // The following code fixes bug#16889 363 // Solution: Store away XPath in setSelect(Xath), and use it here. 364 // Pass m_xath, which the current node is associated with, onto the TraceManager. 365 366 Expression expr = m_xpath.getExpression(); 367 org.apache.xpath.objects.XObject xObject = expr.execute(xctxt); 368 int current = xctxt.getCurrentNode(); 369 transformer.getTraceManager().fireSelectedEvent( 370 current, 371 this, 372 "select", 373 m_xpath, 374 xObject); 375 } 376 377 378 379 xctxt.pushCurrentNode(DTM.NULL); 380 381 IntStack currentNodes = xctxt.getCurrentNodeStack(); 382 383 xctxt.pushCurrentExpressionNode(DTM.NULL); 384 385 IntStack currentExpressionNodes = xctxt.getCurrentExpressionNodeStack(); 386 387 xctxt.pushSAXLocatorNull(); 388 xctxt.pushContextNodeList(sourceNodes); 389 transformer.pushElemTemplateElement(null); 390 391 // pushParams(transformer, xctxt); 392 // Should be able to get this from the iterator but there must be a bug. 393 DTM dtm = xctxt.getDTM(sourceNode); 394 int docID = sourceNode & DTMManager.IDENT_DTM_DEFAULT; 395 int child; 396 397 while (DTM.NULL != (child = sourceNodes.nextNode())) 398 { 399 currentNodes.setTop(child); 400 currentExpressionNodes.setTop(child); 401 402 if ((child & DTMManager.IDENT_DTM_DEFAULT) != docID) 403 { 404 dtm = xctxt.getDTM(child); 405 docID = child & DTMManager.IDENT_DTM_DEFAULT; 406 } 407 408 //final int exNodeType = dtm.getExpandedTypeID(child); 409 final int nodeType = dtm.getNodeType(child); 410 411 // Fire a trace event for the template. 412 if (transformer.getDebug()) 413 { 414 transformer.getTraceManager().fireTraceEvent(this); 415 } 416 417 // And execute the child templates. 418 // Loop through the children of the template, calling execute on 419 // each of them. 420 for (ElemTemplateElement t = this.m_firstChild; t != null; 421 t = t.m_nextSibling) 422 { 423 xctxt.setSAXLocator(t); 424 transformer.setCurrentElement(t); 425 t.execute(transformer); 426 } 427 428 if (transformer.getDebug()) 429 { 430 // We need to make sure an old current element is not 431 // on the stack. See TransformerImpl#getElementCallstack. 432 transformer.setCurrentElement(null); 433 transformer.getTraceManager().fireTraceEndEvent(this); 434 } 435 436 437 // KLUGE: Implement <?xalan:doc_cache_off?> 438 // ASSUMPTION: This will be set only when the XPath was indeed 439 // a call to the Document() function. Calling it in other 440 // situations is likely to fry Xalan. 441 // 442 // %REVIEW% We need a MUCH cleaner solution -- one that will 443 // handle cleaning up after document() and getDTM() in other 444 // contexts. The whole SourceTreeManager mechanism should probably 445 // be moved into DTMManager rather than being explicitly invoked in 446 // FuncDocument and here. 447 if(m_doc_cache_off) 448 { 449 if(DEBUG) 450 System.out.println("JJK***** CACHE RELEASE *****\n"+ 451 "\tdtm="+dtm.getDocumentBaseURI()); 452 // NOTE: This will work because this is _NOT_ a shared DTM, and thus has 453 // only a single Document node. If it could ever be an RTF or other 454 // shared DTM, this would require substantial rework. 455 xctxt.getSourceTreeManager().removeDocumentFromCache(dtm.getDocument()); 456 xctxt.release(dtm,false); 457 } 458 } 459 } 460 finally 461 { 462 if (transformer.getDebug()) 463 transformer.getTraceManager().fireSelectedEndEvent(sourceNode, this, 464 "select", new XPath(m_selectExpression), 465 new org.apache.xpath.objects.XNodeSet(sourceNodes)); 466 467 xctxt.popSAXLocator(); 468 xctxt.popContextNodeList(); 469 transformer.popElemTemplateElement(); 470 xctxt.popCurrentExpressionNode(); 471 xctxt.popCurrentNode(); 472 sourceNodes.detach(); 473 } 474 } 475 476 /** 477 * Add a child to the child list. 478 * <!ELEMENT xsl:apply-templates (xsl:sort|xsl:with-param)*> 479 * <!ATTLIST xsl:apply-templates 480 * select %expr; "node()" 481 * mode %qname; #IMPLIED 482 * > 483 * 484 * @param newChild Child to add to child list 485 * 486 * @return Child just added to child list 487 */ 488 public ElemTemplateElement appendChild(ElemTemplateElement newChild) 489 { 490 491 int type = ((ElemTemplateElement) newChild).getXSLToken(); 492 493 if (Constants.ELEMNAME_SORT == type) 494 { 495 setSortElem((ElemSort) newChild); 496 497 return newChild; 498 } 499 else 500 return super.appendChild(newChild); 501 } 502 503 /** 504 * Call the children visitors. 505 * @param visitor The visitor whose appropriate method will be called. 506 */ 507 public void callChildVisitors(XSLTVisitor visitor, boolean callAttributes) 508 { 509 if(callAttributes && (null != m_selectExpression)) 510 m_selectExpression.callVisitors(this, visitor); 511 512 int length = getSortElemCount(); 513 514 for (int i = 0; i < length; i++) 515 { 516 getSortElem(i).callVisitors(visitor); 517 } 518 519 super.callChildVisitors(visitor, callAttributes); 520 } 521 522 /** 523 * @see ExpressionOwner#getExpression() 524 */ 525 public Expression getExpression() 526 { 527 return m_selectExpression; 528 } 529 530 /** 531 * @see ExpressionOwner#setExpression(Expression) 532 */ 533 public void setExpression(Expression exp) 534 { 535 exp.exprSetParent(this); 536 m_selectExpression = exp; 537 } 538 539 /* 540 * to keep the binary compatibility, assign a default value for newly added 541 * globel varialbe m_xpath during deserialization of an object which was 542 * serialized using an older version 543 */ 544 private void readObject(ObjectInputStream os) throws 545 IOException, ClassNotFoundException { 546 os.defaultReadObject(); 547 m_xpath = null; 548 } 549 }