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 }