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: AVT.java 469221 2006-10-30 18:26:44Z minchau $
20 */
21package org.apache.xalan.templates;
22
23import java.util.StringTokenizer;
24import java.util.Vector;
25
26import javax.xml.transform.TransformerException;
27
28import org.apache.xalan.processor.StylesheetHandler;
29import org.apache.xalan.res.XSLMessages;
30import org.apache.xalan.res.XSLTErrorResources;
31import org.apache.xml.utils.FastStringBuffer;
32import org.apache.xml.utils.StringBufferPool;
33import org.apache.xpath.XPath;
34import org.apache.xpath.XPathContext;
35
36/**
37 * Class to hold an Attribute Value Template.
38 * @xsl.usage advanced
39 */
40public class AVT implements java.io.Serializable, XSLTVisitable
41{
42    static final long serialVersionUID = 5167607155517042691L;
43
44  /**
45    *We are not going to use the object pool if USE_OBJECT_POOL == false.
46  */
47  private final static boolean USE_OBJECT_POOL = false;
48
49  /**
50    * INIT_BUFFER_CHUNK_BITS is used to set initial size of
51    * of the char m_array in FastStringBuffer if USE_OBJECT_POOL == false.
52    * size = 2^ INIT_BUFFER_CHUNK_BITS, INIT_BUFFER_CHUNK_BITS = 7
53    * corresponds size = 256.
54  */
55  private final static int INIT_BUFFER_CHUNK_BITS = 8;
56
57  /**
58   * If the AVT is not complex, just hold the simple string.
59   * @serial
60   */
61  private String m_simpleString = null;
62
63  /**
64   * If the AVT is complex, hold a Vector of AVTParts.
65   * @serial
66   */
67  private Vector m_parts = null;
68
69
70
71  /**
72   * The name of the attribute.
73   * @serial
74   */
75  private String m_rawName;
76
77  /**
78   * Get the raw name of the attribute, with the prefix unprocessed.
79   *
80   * @return non-null reference to prefixed name.
81   */
82  public String getRawName()
83  {
84    return m_rawName;
85  }
86
87  /**
88   * Get the raw name of the attribute, with the prefix unprocessed.
89   *
90   * @param rawName non-null reference to prefixed name.
91   */
92  public void setRawName(String rawName)
93  {
94    m_rawName = rawName;
95  }
96
97  /**
98   * The name of the attribute.
99   * @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}
615