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 */