001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: TemplateFormat.java,v $
023   Revision 1.7  2004/05/05 22:47:37  markl
024   comment block updates
025
026   Revision 1.6  2003/01/19 09:34:27  markl
027   Javadoc & comment header updates.
028
029   Revision 1.5  2001/06/26 06:10:04  markl
030   Added getVariables() method.
031
032   Revision 1.4  2001/03/12 02:18:29  markl
033   Source code cleanup.
034
035   Revision 1.3  1999/11/15 03:32:34  markl
036   Fix to parsing loop.
037
038   Revision 1.2  1999/07/19 04:09:34  markl
039   Removed debug statement.
040
041   Revision 1.1  1999/06/30 08:17:46  markl
042   Initial revision
043   ----------------------------------------------------------------------------
044*/
045
046package kiwi.text;
047
048import java.io.*;
049import java.text.*;
050import java.util.*;
051
052/** A formatter somewhat similar to <code>java.text.MessageFormat</code>.
053 * <code>TemplateFormat</code> formats a template string, replacing variable
054 * placeholders with the corresponding values. There is no limit on the
055 * number of variables or placeholders, and the relationship between variables
056 * and placeholders does not need to be one-to-one, though each placeholder
057 * must have a corresponding variable definition.
058 * <p>
059 * Here is an example template string:
060 * <pre>
061 * My name is {name}, and I am {age} years old. Are you also {age} years old?
062 * </pre>
063 * A placeholder is a variable name surrounded by braces. Since the parser is
064 * lenient, a variable name can consist of a sequence of any characters except
065 * '{' or '}'. A null variable placeholder (<tt>{}</tt>) is not allowed.
066 * <p>
067 * Literal braces can be inserted into the template by doubling
068 * them. So for example, <tt>{{</tt> will be interpreted as a literal single
069 * open brace.
070 * <p>
071 * Variables may appear in more than one place in a template; every occurrence
072 * of a given variable placeholder is replaced with the value of that variable.
073 * A <code>ParsingException</code> is thrown if a variable placeholder is
074 * encountered for which there is no binding.
075 * <p>
076 * The <code>bind()</code> method, in its various forms, may be used to bind
077 * a variable name to its value. Though values of various types are supported,
078 * all values are converted to strings during substitution; the string form of
079 * the value is substituted in place of the variable's placeholder.
080 *
081 * @author Mark Lindner
082 *
083 * @see java.text.MessageFormat
084 * @see kiwi.text.ParsingException
085 */
086
087public class TemplateFormat
088  {
089  private Hashtable varmap;
090  private String text;
091  private StreamTokenizer st = null;
092
093  /** Construct a new <code>TemplateFormat</code> for the given template
094   * string. The string is not actually parsed until the <code>format()</code>
095   * method is called. Once a <code>TemplateFormat</code> is created, it can
096   * be used over and over, with different variable bindings each time, if
097   * so desired.
098   *
099   * @param text The template string to use.
100   */
101  
102  public TemplateFormat(String text)
103    {
104    this.text = text;
105    varmap = new Hashtable();
106    }
107
108  /** Bind a variable to a string value. If the named variable is already
109   * defined, the old value is replaced with the new value.
110   *
111   * @param var The variable name.
112   * @param value The value.
113   */
114  
115  public void bind(String var, String value)
116    {
117    varmap.put(var, value);
118    }
119
120  /** Bind a variable to a boolean value. If the named variable is already
121   * defined, the old value is replaced with the new value.
122   *
123   * @param var The variable name.
124   * @param value The value.
125   */
126  
127  public void bind(String var, boolean value)
128    {
129    varmap.put(var, new Boolean(value));
130    }
131
132  /** Bind a variable to an integer value. If the named variable is already
133   * defined, the old value is replaced with the new value.
134   *
135   * @param var The variable name.
136   * @param value The value.
137   */
138  
139  public void bind(String var, int value)
140    {
141    varmap.put(var, new Integer(value));
142    }
143
144  /** Bind a variable to a long value. If the named variable is already
145   * defined, the old value is replaced with the new value.
146   *
147   * @param var The variable name.
148   * @param value The value.
149   */
150  
151  public void bind(String var, long value)
152    {
153    varmap.put(var, new Long(value));
154    }
155
156  /** Bind a variable to a float value. If the named variable is already
157   * defined, the old value is replaced with the new value.
158   *
159   * @param var The variable name.
160   * @param value The value.
161   */
162  
163  public void bind(String var, float value)
164    {
165    varmap.put(var, new Float(value));
166    }
167
168  /** Bind a variable to a double value. If the named variable is already
169   * defined, the old value is replaced with the new value.
170   *
171   * @param var The variable name.
172   * @param value The value.
173   */
174  
175  public void bind(String var, double value)
176    {
177    varmap.put(var, new Double(value));
178    }
179
180  /** Clear all variable bindings.
181   */
182  
183  public void clear()
184    {
185    varmap.clear();
186    }
187
188  /** Format the template, substituting the variable values into their
189   * placeholders.
190   *
191   * @param dest The <code>StringBuffer</code> to write the formatted
192   * template to.
193   *
194   * @exception kiwi.text.ParsingException If an error occurred during parsing,
195   * such as a mismatched brace, unterminated placeholder, or undefined
196   * variable.
197   */
198  
199  public void format(StringBuffer dest) throws ParsingException
200    {
201    try
202      {
203      StringReader source = new StringReader(text);
204      _format(source, dest, null);
205      source.close();
206      }
207    catch(IOException ex) { /* won't happen */ }
208    }
209
210  /** Compile a list of all variables referenced in the template.
211   *
212   * @return An array of variable names.
213   *
214   * @exception kiwi.text.ParsingException If an error occurred during parsing,
215   * such as a mismatched brace, unterminated placeholder, or undefined
216   * variable.
217   *
218   * @since Kiwi 1.3
219   */
220  
221  public String [] getVariables() throws ParsingException
222    {
223    Vector v = new Vector();
224
225    try
226      {
227      StringReader source = new StringReader(text);
228      _format(source, null, v);
229      source.close();
230      }
231    catch(IOException ex) { /* won't happen */ }
232
233    String s[] = new String[v.size()];
234
235    v.toArray(s);
236
237    return(s);
238    }
239
240  /* parsing engine */
241  
242  private void _format(StringReader source, StringBuffer dest, Vector vars)
243    throws ParsingException, IOException
244    {
245    st = new StreamTokenizer(source);
246    st.resetSyntax();
247    st.wordChars(0, 255);
248    st.ordinaryChar('{');
249    st.ordinaryChar('}');
250    int lastTok = 0;
251    String var = null;
252    boolean forget;
253    
254    loop:
255    for(;;)
256      {
257      int tok = st.nextToken();
258      forget = false;
259      
260      switch(tok)
261        {
262        case StreamTokenizer.TT_EOF:
263          break loop;
264
265        case StreamTokenizer.TT_WORD:
266          if(lastTok == '{')
267            var = st.sval;
268          else if(lastTok == '}')
269            flagMismatch(lastTok);
270          else
271            {
272            if(dest != null)
273              dest.append(st.sval);
274            }
275          break;
276          
277        case '{':
278          if(lastTok == '{')
279            {
280            if(dest != null)
281              dest.append('{');
282            forget = true;
283            }
284          else if((lastTok != StreamTokenizer.TT_WORD) && (lastTok != 0))
285            flagMismatch(tok);
286
287          break;
288
289        case '}':
290          if(lastTok == '}')
291            {
292            if(dest != null)
293              dest.append('}');
294            forget = true;
295            }
296          else
297            {
298            if(var != null)
299              {
300              if(dest != null)
301                dest.append(lookup(var));
302              if(vars != null)
303                vars.addElement(var);
304              forget = true;
305              var = null;
306              }
307            }
308          break;
309
310        default:
311          throw(new ParsingException("Unrecognized token " + tok,
312                                     st.lineno()));
313        }
314
315      lastTok = (forget ? 0 : tok);
316      }
317
318    // check to see if something is left
319
320    if(var != null)
321      throw(new ParsingException("Premature end of input", st.lineno()));
322    }
323
324  /* catch mismatched braces */
325  
326  private void flagMismatch(int c) throws ParsingException
327    {
328    throw(new ParsingException("Misplaced '" + (char)c + "'", st.lineno()));
329    }
330
331  /* look up a variable, & catch undefined vars */
332  
333  private String lookup(String var) throws ParsingException
334    {
335    Object val = varmap.get(var);
336    if(val == null)
337      throw(new ParsingException("Unbound variable '" + var + "'",
338                                 st.lineno()));
339
340    return(val.toString());
341    }
342  
343  }
344
345/* end of source file */