1 // 2 // This software is now distributed according to 3 // the Lesser Gnu Public License. Please see 4 // http://www.gnu.org/copyleft/lesser.txt for 5 // the details. 6 // -- Happy Computing! 7 // 8 package com.stevesoft.pat; 9 10 import java.util.Hashtable; 11 12 /** 13 * ReplaceRule is a singly linked list of Objects which describe how to replace 14 * the matched portion of a String. The only member method that you absolutely 15 * need to define to use this class is apply(StringBuffer,RegRes) -- although 16 * you may want define toString1() and clone1() (if you are unhappy with the 17 * default methods) that are needed by the clone() or toString() methods on this 18 * class. During the replacement process, each ReplaceRule tells the replacer 19 * what to add to StringBuffer and uses the contents of the Regular expression 20 * result to get the information it needs to do this. Here is an <a 21 * href="http://javaregex.com/code/fancy.java.html">example</a> 22 * 23 * @see com.stevesoft.pat.NullRule 24 * @see com.stevesoft.pat.AmpersandRule 25 * @see com.stevesoft.pat.BackRefRule 26 * @see com.stevesoft.pat.LeftRule 27 * @see com.stevesoft.pat.RightRule 28 * @see com.stevesoft.pat.StringRule 29 */ 30 public abstract class ReplaceRule 31 { 32 /** points to the next ReplaceRule in the linked list. */ 33 protected ReplaceRule next = null; 34 35 /** 36 * This function appends to the StringBufferLike the text you want to replaced 37 * the portion of the String last matched. 38 */ apply(StringBufferLike sb, RegRes r)39 public abstract void apply(StringBufferLike sb, RegRes r); 40 41 /** 42 * A rule describing how to clone only the current ReplaceRule, and none of 43 * the others in this linked list. It is called by clone() for each item in 44 * the list. 45 */ clone1()46 public Object clone1() 47 { 48 return new RuleHolder(this); 49 } 50 clone()51 public final Object clone() 52 { 53 ReplaceRule x = (ReplaceRule) clone1(); 54 ReplaceRule xsav = x; 55 ReplaceRule y = this; 56 while (y.next != null) 57 { 58 x.next = (ReplaceRule) y.next.clone1(); 59 x.name = y.name; 60 x = x.next; 61 y = y.next; 62 } 63 return xsav; 64 } 65 add(ReplaceRule head, ReplaceRule adding)66 static ReplaceRule add(ReplaceRule head, ReplaceRule adding) 67 { 68 if (head == null) 69 { 70 return head = adding; 71 } 72 head.addRule(adding); 73 return head; 74 } 75 add(ReplaceRule adding)76 public ReplaceRule add(ReplaceRule adding) 77 { 78 return add(this, adding); 79 } 80 81 /** Add another ReplaceRule to the linked list. */ addRule(ReplaceRule r)82 public void addRule(ReplaceRule r) 83 { 84 if (next == null) 85 { 86 next = r; 87 } 88 else 89 { 90 next.addRule(r); 91 } 92 } 93 94 static Regex getvar = null; 95 getv()96 final static Regex getv() 97 { 98 // Thanks to Michael Jimenez for pointing out the need 99 // to clone getvar rather than simply returning it. 100 // Previously this was not thread safe. 101 // if(getvar != null) return getvar; 102 if (getvar != null) 103 { 104 return (Regex) getvar.clone(); 105 } 106 getvar = new Regex("(?:\\\\(\\d+)|" + // ref 1 107 "\\$(?:" + "(\\d+)|" + // ref 2 108 "(\\w+)|" + // ref 3 109 "([&'`])|" + // ref 4 110 "\\{(?:(\\d+)|" + // ref 5 111 "([^\n}\\\\]+))}" + // ref 6 112 ")|" + "\\\\([nrbtaef])|" + // ref 7 113 "\\\\c([\u0000-\uFFFF])|" + // ref 8 114 "\\\\x([A-Fa-f0-9]{2})|" + // ref 9 115 "\\\\([\u0000-\uFFFF])" + // ref 10 116 ")"); 117 getvar.optimize(); 118 return getvar; 119 } 120 121 /** 122 * Compile a ReplaceRule using the text that would go between the second and 123 * third /'s in a typical substitution pattern in Perl: s/ ... / <i>The 124 * argument to ReplaceRule.perlCode</i> /. 125 */ perlCode(String s)126 public static ReplaceRule perlCode(String s) 127 { 128 // String sav_backGs = Regex.backGs; 129 // int sav_backGto = Regex.backGto; 130 try 131 { 132 int mf = 0, mt = 0; 133 Regex gv = getv(); 134 ReplaceRule head = null; 135 Object tmp = null; 136 while (gv.searchFrom(s, mt)) 137 { 138 int off = Regex.BackRefOffset - 1; 139 mf = gv.matchedFrom(); 140 if (mf > mt) 141 { 142 head = add(head, new StringRule(s.substring(mt, mf))); 143 } 144 String var = null; 145 if ((var = gv.stringMatched(1 + off)) != null 146 || (var = gv.stringMatched(2 + off)) != null 147 || (var = gv.stringMatched(5 + off)) != null) 148 { 149 int d = 0; 150 for (int i = 0; i < var.length(); i++) 151 { 152 d = 8 * d + (var.charAt(i) - '0'); 153 } 154 if (var.length() == 1) 155 { 156 head = add(head, new BackRefRule(d)); 157 } 158 else 159 { 160 head = new StringRule("" + (char) d); 161 } 162 } 163 else if ((var = gv.stringMatched(10 + off)) != null) 164 { 165 if ("QELlUu".indexOf(var) >= 0) 166 { 167 head = add(head, new CodeRule(var.charAt(0))); 168 } 169 else 170 { 171 head = add(head, new StringRule(var)); 172 } 173 } 174 else if ((var = gv.stringMatched(3 + off)) != null 175 || (var = gv.stringMatched(4 + off)) != null 176 || (var = gv.stringMatched(6 + off)) != null) 177 { 178 String arg = ""; 179 int pc; 180 if ((pc = var.indexOf(':')) > 0) 181 { 182 arg = var.substring(pc + 1); 183 var = var.substring(0, pc); 184 } 185 if (var.equals("&") || var.equals("MATCH")) 186 { 187 head = add(head, new AmpersandRule()); 188 } 189 else if (var.equals("`") || var.equals("PREMATCH")) 190 { 191 head = add(head, new LeftRule()); 192 } 193 else if (var.equals("'") || var.equals("POSTMATCH")) 194 { 195 head = add(head, new RightRule()); 196 } 197 else if (var.equals("WANT_MORE_TEXT")) 198 { 199 head = add(head, new WantMoreTextReplaceRule()); 200 } 201 else if (var.equals("POP")) 202 { 203 head = add(head, new PopRule()); 204 } 205 else if (var.startsWith("+") 206 && (tmp = defs.get(var.substring(1))) != null) 207 { 208 if (tmp instanceof Regex) 209 { 210 head = add(head, new PushRule(var.substring(1), (Regex) tmp)); 211 } 212 else if (tmp instanceof Transformer) 213 { 214 head = add(head, new PushRule(var.substring(1), 215 (Transformer) tmp)); 216 } 217 else 218 { 219 head = add(head, new StringRule("${" + var + "}")); 220 } 221 } 222 else if (var.startsWith("=") 223 && (tmp = defs.get(var.substring(1))) != null) 224 { 225 if (tmp instanceof Regex) 226 { 227 head = add(head, 228 new ChangeRule(var.substring(1), (Regex) tmp)); 229 } 230 else if (tmp instanceof Transformer) 231 { 232 head = add(head, new ChangeRule(var.substring(1), 233 (Transformer) tmp)); 234 } 235 else 236 { 237 head = add(head, new StringRule("${" + var + "}")); 238 } 239 } 240 else if ((tmp = defs.get(var)) != null) 241 { 242 if (tmp instanceof ReplaceRule) 243 { 244 ReplaceRule alt = ((ReplaceRule) tmp).arg(arg); 245 if (alt == null) 246 { 247 alt = ((ReplaceRule) tmp); 248 } 249 head = add(head, (ReplaceRule) (alt.clone())); 250 } 251 } 252 else 253 // can't figure out how to transform this thing... 254 { 255 head = add(head, new StringRule("${" + var + "}")); 256 } 257 } 258 else if ((var = gv.stringMatched(7 + off)) != null) 259 { 260 char c = var.charAt(0); 261 if (c == 'n') 262 { 263 head = add(head, new StringRule("\n")); 264 } 265 else if (c == 't') 266 { 267 head = add(head, new StringRule("\t")); 268 } 269 else if (c == 'r') 270 { 271 head = add(head, new StringRule("\r")); 272 } 273 else if (c == 'b') 274 { 275 head = add(head, new StringRule("\r")); 276 } 277 else if (c == 'a') 278 { 279 head = add(head, new StringRule("" + (char) 7)); 280 } 281 else if (c == 'e') 282 { 283 head = add(head, new StringRule("" + (char) 27)); 284 } 285 else if (c == 'f') 286 { 287 head = add(head, new StringRule("" + (char) 12)); 288 } 289 } 290 else if ((var = gv.stringMatched(8 + off)) != null) 291 { 292 char c = var.charAt(0); 293 if (c < Ctrl.cmap.length) 294 { 295 c = Ctrl.cmap[c]; 296 } 297 head = add(head, new StringRule("" + c)); 298 } 299 else if ((var = gv.stringMatched(9 + off)) != null) 300 { 301 int d = 16 * getHexDigit(var.charAt(0)) 302 + getHexDigit(var.charAt(1)); 303 head = add(head, new StringRule("" + (char) d)); 304 } 305 mt = gv.matchedTo(); 306 } 307 if (mt <= s.length()) 308 { 309 head = add(head, new StringRule(s.substring(mt))); 310 } 311 return head; 312 } finally 313 { 314 // Regex.backGs = sav_backGs; 315 // Regex.backGto = sav_backGto; 316 } 317 } 318 319 static Hashtable defs = new Hashtable(); 320 isDefined(String s)321 public static boolean isDefined(String s) 322 { 323 return defs.get(s) != null; 324 } 325 define(String s, Regex r)326 public static void define(String s, Regex r) 327 { 328 defs.put(s, r); 329 } 330 define(String s, ReplaceRule r)331 public static void define(String s, ReplaceRule r) 332 { 333 defs.put(s, r); 334 r.name = s; 335 } 336 337 String name = getClass().getName(); 338 define(String s, Transformer t)339 public static void define(String s, Transformer t) 340 { 341 defs.put(s, t); 342 } 343 undefine(String s)344 public static void undefine(String s) 345 { 346 defs.remove(s); 347 } 348 349 /** 350 * This tells how to convert just the current element (and none of the other 351 * items in the linked list) to a String. This method is called by toString() 352 * for each item in the linked list. 353 */ toString1()354 public String toString1() 355 { 356 return "${" + name + "}"; 357 } 358 359 /** Convert to a String. */ toString()360 public final String toString() 361 { 362 StringBuffer sb = new StringBuffer(); 363 sb.append(toString1()); 364 ReplaceRule rr = this.next; 365 while (rr != null) 366 { 367 sb.append(rr.toString1()); 368 rr = rr.next; 369 } 370 return sb.toString(); 371 } 372 373 /** 374 * Modified the behavior of a ReplaceRule by supplying an argument. If a 375 * ReplaceRule named "foo" is defined and the pattern "s/x/${foo:5}/" is given 376 * to Regex.perlCode, then the "foo" the definition of "foo" will be retrieved 377 * and arg("5") will be called. If the result is non-null, that is the 378 * ReplaceRule that will be used. If the result is null, then the pattern 379 * works just as if it were "s/x/${foo}/". 380 * 381 * @see com.stevesoft.pat.Validator#arg(java.lang.String) 382 */ arg(String s)383 public ReplaceRule arg(String s) 384 { 385 return null; 386 } 387 getHexDigit(char c)388 static int getHexDigit(char c) 389 { 390 if (c >= '0' && c <= '9') 391 { 392 return c - '0'; 393 } 394 if (c >= 'a' && c <= 'f') 395 { 396 return c - 'a' + 10; 397 } 398 return c - 'A' + 10; 399 } 400 } 401