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: AVT.java 469221 2006-10-30 18:26:44Z minchau $
020     */
021    package org.apache.xalan.templates;
022    
023    import java.util.StringTokenizer;
024    import java.util.Vector;
025    
026    import javax.xml.transform.TransformerException;
027    
028    import org.apache.xalan.processor.StylesheetHandler;
029    import org.apache.xalan.res.XSLMessages;
030    import org.apache.xalan.res.XSLTErrorResources;
031    import org.apache.xml.utils.FastStringBuffer;
032    import org.apache.xml.utils.StringBufferPool;
033    import org.apache.xpath.XPath;
034    import org.apache.xpath.XPathContext;
035    
036    /**
037     * Class to hold an Attribute Value Template.
038     * @xsl.usage advanced
039     */
040    public class AVT implements java.io.Serializable, XSLTVisitable
041    {
042        static final long serialVersionUID = 5167607155517042691L;
043      
044      /**
045        *We are not going to use the object pool if USE_OBJECT_POOL == false.
046      */
047      private final static boolean USE_OBJECT_POOL = false; 
048      
049      /**
050        * INIT_BUFFER_CHUNK_BITS is used to set initial size of
051        * of the char m_array in FastStringBuffer if USE_OBJECT_POOL == false. 
052        * size = 2^ INIT_BUFFER_CHUNK_BITS, INIT_BUFFER_CHUNK_BITS = 7 
053        * corresponds size = 256. 
054      */
055      private final static int INIT_BUFFER_CHUNK_BITS = 8; 
056      
057      /**
058       * If the AVT is not complex, just hold the simple string.
059       * @serial
060       */
061      private String m_simpleString = null;
062    
063      /**
064       * If the AVT is complex, hold a Vector of AVTParts.
065       * @serial
066       */
067      private Vector m_parts = null;
068      
069       
070    
071      /**
072       * The name of the attribute.
073       * @serial
074       */
075      private String m_rawName;
076    
077      /**
078       * Get the raw name of the attribute, with the prefix unprocessed.
079       *
080       * @return non-null reference to prefixed name.
081       */
082      public String getRawName()
083      {
084        return m_rawName;
085      }
086    
087      /**
088       * Get the raw name of the attribute, with the prefix unprocessed.
089       *
090       * @param rawName non-null reference to prefixed name.
091       */
092      public void setRawName(String rawName)
093      {
094        m_rawName = rawName;
095      }
096    
097      /**
098       * The name of the attribute.
099       * @serial
100       */
101      private String m_name;
102    
103      /**
104       * Get the local name of the attribute.
105       *
106       * @return non-null reference to name string.
107       */
108      public String getName()
109      {
110        return m_name;
111      }
112    
113      /**
114       * Set the local name of the attribute.
115       *
116       * @param name non-null reference to name string.
117       */
118      public void setName(String name)
119      {
120        m_name = name;
121      }
122    
123      /**
124       * The namespace URI of the owning attribute.
125       * @serial
126       */
127      private String m_uri;
128    
129      /**
130       * Get the namespace URI of the attribute.
131       *
132       * @return non-null reference to URI, "" if null namespace.
133       */
134      public String getURI()
135      {
136        return m_uri;
137      }
138    
139      /**
140       * Get the namespace URI of the attribute.
141       *
142       * @param uri non-null reference to URI, "" if null namespace.
143       */
144      public void setURI(String uri)
145      {
146        m_uri = uri;
147      }
148    
149      /**
150       * Construct an AVT by parsing the string, and either
151       * constructing a vector of AVTParts, or simply hold
152       * on to the string if the AVT is simple.
153       *
154       * @param handler non-null reference to StylesheetHandler that is constructing.
155       * @param uri non-null reference to URI, "" if null namespace.
156       * @param name  non-null reference to name string.
157       * @param rawName prefixed name.
158       * @param stringedValue non-null raw string value.
159       *
160       * @throws javax.xml.transform.TransformerException
161       */
162      public AVT(StylesheetHandler handler, String uri, String name, 
163                 String rawName, String stringedValue,
164                 ElemTemplateElement owner)
165              throws javax.xml.transform.TransformerException
166      {
167    
168        m_uri = uri;
169        m_name = name;
170        m_rawName = rawName;
171    
172        StringTokenizer tokenizer = new StringTokenizer(stringedValue, "{}\"\'",
173                                      true);
174        int nTokens = tokenizer.countTokens();
175    
176        if (nTokens < 2)
177        {
178          m_simpleString = stringedValue;  // then do the simple thing
179        }
180        else
181        {
182          FastStringBuffer buffer = null;
183          FastStringBuffer exprBuffer = null;
184          if(USE_OBJECT_POOL){
185            buffer = StringBufferPool.get(); 
186            exprBuffer = StringBufferPool.get();
187          }else{
188            buffer = new FastStringBuffer(6);
189            exprBuffer = new FastStringBuffer(6);
190          }
191          try
192          {
193            m_parts = new Vector(nTokens + 1);
194    
195            String t = null;  // base token
196            String lookahead = null;  // next token
197            String error = null;  // if non-null, break from loop
198    
199            while (tokenizer.hasMoreTokens())
200            {
201              if (lookahead != null)
202              {
203                t = lookahead;
204                lookahead = null;
205              }
206              else
207                t = tokenizer.nextToken();
208    
209              if (t.length() == 1)
210              {
211                switch (t.charAt(0))
212                {
213                case ('\"') :
214                case ('\'') :
215                {
216    
217                  // just keep on going, since we're not in an attribute template
218                  buffer.append(t);
219    
220                  break;
221                }
222                case ('{') :
223                {
224    
225                  try
226                  {
227                    // Attribute Value Template start
228                    lookahead = tokenizer.nextToken();
229    
230                    if (lookahead.equals("{"))
231                    {
232    
233                      // Double curlys mean escape to show curly
234                      buffer.append(lookahead);
235    
236                      lookahead = null;
237    
238                      break;  // from switch
239                    }
240    
241                    /*
242                    else if(lookahead.equals("\"") || lookahead.equals("\'"))
243                    {
244                    // Error. Expressions can not begin with quotes.
245                    error = "Expressions can not begin with quotes.";
246                    break; // from switch
247                    }
248                    */
249                    else
250                    {
251                      if (buffer.length() > 0)
252                      {
253                        m_parts.addElement(new AVTPartSimple(buffer.toString()));
254                        buffer.setLength(0);
255                      }
256    
257                      exprBuffer.setLength(0);
258    
259                      while (null != lookahead)
260                      {
261                        if (lookahead.length() == 1)
262                        {
263                          switch (lookahead.charAt(0))
264                          {
265                          case '\'' :
266                          case '\"' :
267                            {
268    
269                              // String start
270                              exprBuffer.append(lookahead);
271    
272                              String quote = lookahead;
273    
274                              // Consume stuff 'till next quote
275                              lookahead = tokenizer.nextToken();
276    
277                              while (!lookahead.equals(quote))
278                              {
279                                exprBuffer.append(lookahead);
280    
281                                lookahead = tokenizer.nextToken();
282                              }
283    
284                              exprBuffer.append(lookahead);
285    
286                              lookahead = tokenizer.nextToken();
287    
288                              break;
289                            }
290                          case '{' :
291                            {
292    
293                              // What's another curly doing here?
294                              error = XSLMessages.createMessage(
295                                                                XSLTErrorResources.ER_NO_CURLYBRACE, null);  //"Error: Can not have \"{\" within expression.";
296                              
297                              lookahead = null;  // breaks out of inner while loop
298    
299                              break;
300                            }
301                          case '}' :
302                            {
303    
304                              // Proper close of attribute template.
305                              // Evaluate the expression.
306                              buffer.setLength(0);
307    
308                              XPath xpath =
309                                           handler.createXPath(exprBuffer.toString(), owner);
310    
311                              m_parts.addElement(new AVTPartXPath(xpath));
312    
313                              lookahead = null;  // breaks out of inner while loop
314    
315                              break;
316                            }
317                          default :
318                            {
319    
320                              // part of the template stuff, just add it.
321                              exprBuffer.append(lookahead);
322    
323                              lookahead = tokenizer.nextToken();
324                            }
325                          }  // end inner switch
326                        }  // end if lookahead length == 1
327                        else
328                        {
329    
330                          // part of the template stuff, just add it.
331                          exprBuffer.append(lookahead);
332    
333                          lookahead = tokenizer.nextToken();
334                        }
335                      }  // end while(!lookahead.equals("}"))
336    
337                      if (error != null)
338                      {
339                        break;  // from inner while loop
340                      }
341                    }
342    
343                    break;
344                  }
345                  catch (java.util.NoSuchElementException ex)
346                  {
347                    error = XSLMessages.createMessage(XSLTErrorResources.ER_ILLEGAL_ATTRIBUTE_VALUE, new Object[]{ name, stringedValue }); 
348                    break;
349                  }
350                }
351                case ('}') :
352                {
353                  lookahead = tokenizer.nextToken();
354    
355                  if (lookahead.equals("}"))
356                  {
357    
358                    // Double curlys mean escape to show curly
359                    buffer.append(lookahead);
360    
361                    lookahead = null;  // swallow
362                  }
363                  else
364                  {
365    
366                    // Illegal, I think...
367                    try
368                    {
369                      handler.warn(XSLTErrorResources.WG_FOUND_CURLYBRACE, null);  //"Found \"}\" but no attribute template open!");
370                    }
371                    catch (org.xml.sax.SAXException se)
372                    {
373                      throw new TransformerException(se);
374                    }
375    
376                    buffer.append("}");
377    
378                    // leave the lookahead to be processed by the next round.
379                  }
380    
381                  break;
382                }
383                default :
384                {
385    
386                  // Anything else just add to string.
387                  buffer.append(t);
388                }
389                }  // end switch t
390              }  // end if length == 1
391              else
392              {
393    
394                // Anything else just add to string.
395                buffer.append(t);
396              }
397    
398              if (null != error)
399              {
400                try
401                {
402                  handler.warn(XSLTErrorResources.WG_ATTR_TEMPLATE,
403                               new Object[]{ error });  //"Attr Template, "+error);
404                }
405                catch (org.xml.sax.SAXException se)
406                {
407                  throw new TransformerException(se);
408                }
409    
410                break;
411              }
412            }  // end while(tokenizer.hasMoreTokens())
413    
414            if (buffer.length() > 0)
415            {
416              m_parts.addElement(new AVTPartSimple(buffer.toString()));
417              buffer.setLength(0);
418            }
419          }
420          finally
421          {
422            if(USE_OBJECT_POOL){
423                 StringBufferPool.free(buffer);
424                 StringBufferPool.free(exprBuffer);
425             }else{
426                buffer = null;
427                exprBuffer = null;
428             };
429          }
430        }  // end else nTokens > 1
431    
432        if (null == m_parts && (null == m_simpleString))
433        {
434    
435          // Error?
436          m_simpleString = "";
437        }
438      }
439    
440      /**
441       * Get the AVT as the original string.
442       *
443       * @return The AVT as the original string
444       */
445      public String getSimpleString()
446      {
447    
448        if (null != m_simpleString){
449          return m_simpleString;
450        }
451        else if (null != m_parts){
452         final FastStringBuffer buf = getBuffer();
453         String out = null;
454    
455        int n = m_parts.size();
456        try{
457          for (int i = 0; i < n; i++){
458            AVTPart part = (AVTPart) m_parts.elementAt(i);
459            buf.append(part.getSimpleString());
460          }
461          out = buf.toString();
462        }finally{
463          if(USE_OBJECT_POOL){
464             StringBufferPool.free(buf);
465         }else{
466            buf.setLength(0); 
467         };
468        }
469        return out;
470      }else{
471          return "";
472      }
473    }
474    
475      /**
476       * Evaluate the AVT and return a String.
477       *
478       * @param xctxt Te XPathContext to use to evaluate this.
479       * @param context The current source tree context.
480       * @param nsNode The current namespace context (stylesheet tree context).
481       *
482       * @return The AVT evaluated as a string
483       *
484       * @throws javax.xml.transform.TransformerException
485       */
486      public String evaluate(
487              XPathContext xctxt, int context, org.apache.xml.utils.PrefixResolver nsNode)
488                throws javax.xml.transform.TransformerException
489      {
490        if (null != m_simpleString){
491            return m_simpleString;
492        }else if (null != m_parts){
493          final FastStringBuffer buf =getBuffer();
494          String out = null;
495          int n = m_parts.size();
496          try{
497            for (int i = 0; i < n; i++){
498              AVTPart part = (AVTPart) m_parts.elementAt(i);  
499              part.evaluate(xctxt, buf, context, nsNode);
500            }
501           out = buf.toString();
502          }finally{
503              if(USE_OBJECT_POOL){
504                 StringBufferPool.free(buf);
505             }else{
506               buf.setLength(0); 
507             }
508          }
509         return out;
510        }else{
511          return "";
512        }
513      }
514    
515      /**
516       * Test whether the AVT is insensitive to the context in which
517       *  it is being evaluated. This is intended to facilitate
518       *  compilation of templates, by allowing simple AVTs to be
519       *  converted back into strings.
520       *
521       *  Currently the only case we recognize is simple strings.
522       * ADDED 9/5/2000 to support compilation experiment
523       *
524       * @return True if the m_simpleString member of this AVT is not null
525       */
526      public boolean isContextInsensitive()
527      {
528        return null != m_simpleString;
529      }
530    
531      /**
532       * Tell if this expression or it's subexpressions can traverse outside
533       * the current subtree.
534       *
535       * @return true if traversal outside the context node's subtree can occur.
536       */
537      public boolean canTraverseOutsideSubtree()
538      {
539    
540        if (null != m_parts)
541        {
542          int n = m_parts.size();
543    
544          for (int i = 0; i < n; i++)
545          {
546            AVTPart part = (AVTPart) m_parts.elementAt(i);
547    
548            if (part.canTraverseOutsideSubtree())
549              return true;
550          }
551        }
552    
553        return false;
554      }
555      
556      /**
557       * This function is used to fixup variables from QNames to stack frame 
558       * indexes at stylesheet build time.
559       * @param vars List of QNames that correspond to variables.  This list 
560       * should be searched backwards for the first qualified name that 
561       * corresponds to the variable reference qname.  The position of the 
562       * QName in the vector from the start of the vector will be its position 
563       * in the stack frame (but variables above the globalsTop value will need 
564       * to be offset to the current stack frame).
565       */
566      public void fixupVariables(java.util.Vector vars, int globalsSize)
567      {
568        if (null != m_parts)
569        {
570          int n = m_parts.size();
571    
572          for (int i = 0; i < n; i++)
573          {
574            AVTPart part = (AVTPart) m_parts.elementAt(i);
575    
576            part.fixupVariables(vars, globalsSize);
577          }
578        }
579      }
580      
581      /**
582       * @see XSLTVisitable#callVisitors(XSLTVisitor)
583       */
584      public void callVisitors(XSLTVisitor visitor)
585      {
586            if(visitor.visitAVT(this) && (null != m_parts))
587            {
588          int n = m_parts.size();
589    
590          for (int i = 0; i < n; i++)
591          {
592            AVTPart part = (AVTPart) m_parts.elementAt(i);
593    
594            part.callVisitors(visitor);
595          }                 
596            }
597      }
598    
599    
600      /**
601       * Returns true if this AVT is simple
602       */
603      public boolean isSimple() {
604            return m_simpleString != null;
605      }
606      
607      private final FastStringBuffer getBuffer(){
608        if(USE_OBJECT_POOL){
609          return StringBufferPool.get();
610        }else{
611          return new FastStringBuffer(INIT_BUFFER_CHUNK_BITS);
612        }
613      }
614    }