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    }