Home | History | Annotate | Download | only in transformer
      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    */
     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 
     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    */
     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    */
    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 }
    261