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
8 package com.stevesoft.pat;
10 import java.util.Hashtable;
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>
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
30 public abstract class ReplaceRule
32 /** points to the next ReplaceRule in the linked list. */
33 protected ReplaceRule next = null;
36 * This function appends to the StringBufferLike the text you want to replaced
37 * the portion of the String last matched.
39 public abstract void apply(StringBufferLike sb, RegRes r);
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
46 public Object clone1()
48 return new RuleHolder(this);
51 public final Object clone()
53 ReplaceRule x = (ReplaceRule) clone1();
56 while (y.next != null)
58 x.next = (ReplaceRule) y.next.clone1();
66 static ReplaceRule add(ReplaceRule head, ReplaceRule adding)
76 public ReplaceRule add(ReplaceRule adding)
78 return add(this, adding);
81 /** Add another ReplaceRule to the linked list. */
82 public void addRule(ReplaceRule r)
94 static Regex getvar = null;
96 final static Regex getv()
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;
104 return (Regex) getvar.clone();
106 getvar = new Regex("(?:\\\\(\\d+)|" + // ref 1
107 "\\$(?:" + "(\\d+)|" + // ref 2
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
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> /.
126 public static ReplaceRule perlCode(String s)
128 // String sav_backGs = Regex.backGs;
129 // int sav_backGto = Regex.backGto;
134 ReplaceRule head = null;
136 while (gv.searchFrom(s, mt))
138 int off = Regex.BackRefOffset - 1;
139 mf = gv.matchedFrom();
142 head = add(head, new StringRule(s.substring(mt, mf)));
145 if ((var = gv.stringMatched(1 + off)) != null
146 || (var = gv.stringMatched(2 + off)) != null
147 || (var = gv.stringMatched(5 + off)) != null)
150 for (int i = 0; i < var.length(); i++)
152 d = 8 * d + (var.charAt(i) - '0');
154 if (var.length() == 1)
156 head = add(head, new BackRefRule(d));
160 head = new StringRule("" + (char) d);
163 else if ((var = gv.stringMatched(10 + off)) != null)
165 if ("QELlUu".indexOf(var) >= 0)
167 head = add(head, new CodeRule(var.charAt(0)));
171 head = add(head, new StringRule(var));
174 else if ((var = gv.stringMatched(3 + off)) != null
175 || (var = gv.stringMatched(4 + off)) != null
176 || (var = gv.stringMatched(6 + off)) != null)
180 if ((pc = var.indexOf(':')) > 0)
182 arg = var.substring(pc + 1);
183 var = var.substring(0, pc);
185 if (var.equals("&") || var.equals("MATCH"))
187 head = add(head, new AmpersandRule());
189 else if (var.equals("`") || var.equals("PREMATCH"))
191 head = add(head, new LeftRule());
193 else if (var.equals("'") || var.equals("POSTMATCH"))
195 head = add(head, new RightRule());
197 else if (var.equals("WANT_MORE_TEXT"))
199 head = add(head, new WantMoreTextReplaceRule());
201 else if (var.equals("POP"))
203 head = add(head, new PopRule());
205 else if (var.startsWith("+")
206 && (tmp = defs.get(var.substring(1))) != null)
208 if (tmp instanceof Regex)
210 head = add(head, new PushRule(var.substring(1), (Regex) tmp));
212 else if (tmp instanceof Transformer)
214 head = add(head, new PushRule(var.substring(1),
219 head = add(head, new StringRule("${" + var + "}"));
222 else if (var.startsWith("=")
223 && (tmp = defs.get(var.substring(1))) != null)
225 if (tmp instanceof Regex)
228 new ChangeRule(var.substring(1), (Regex) tmp));
230 else if (tmp instanceof Transformer)
232 head = add(head, new ChangeRule(var.substring(1),
237 head = add(head, new StringRule("${" + var + "}"));
240 else if ((tmp = defs.get(var)) != null)
242 if (tmp instanceof ReplaceRule)
244 ReplaceRule alt = ((ReplaceRule) tmp).arg(arg);
247 alt = ((ReplaceRule) tmp);
249 head = add(head, (ReplaceRule) (alt.clone()));
253 // can't figure out how to transform this thing...
255 head = add(head, new StringRule("${" + var + "}"));
258 else if ((var = gv.stringMatched(7 + off)) != null)
260 char c = var.charAt(0);
263 head = add(head, new StringRule("\n"));
267 head = add(head, new StringRule("\t"));
271 head = add(head, new StringRule("\r"));
275 head = add(head, new StringRule("\r"));
279 head = add(head, new StringRule("" + (char) 7));
283 head = add(head, new StringRule("" + (char) 27));
287 head = add(head, new StringRule("" + (char) 12));
290 else if ((var = gv.stringMatched(8 + off)) != null)
292 char c = var.charAt(0);
293 if (c < Ctrl.cmap.length)
297 head = add(head, new StringRule("" + c));
299 else if ((var = gv.stringMatched(9 + off)) != null)
301 int d = 16 * getHexDigit(var.charAt(0))
302 + getHexDigit(var.charAt(1));
303 head = add(head, new StringRule("" + (char) d));
307 if (mt <= s.length())
309 head = add(head, new StringRule(s.substring(mt)));
314 // Regex.backGs = sav_backGs;
315 // Regex.backGto = sav_backGto;
319 static Hashtable defs = new Hashtable();
321 public static boolean isDefined(String s)
323 return defs.get(s) != null;
326 public static void define(String s, Regex r)
331 public static void define(String s, ReplaceRule r)
337 String name = getClass().getName();
339 public static void define(String s, Transformer t)
344 public static void undefine(String s)
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.
354 public String toString1()
356 return "${" + name + "}";
359 /** Convert to a String. */
360 public final String toString()
362 StringBuffer sb = new StringBuffer();
363 sb.append(toString1());
364 ReplaceRule rr = this.next;
367 sb.append(rr.toString1());
370 return sb.toString();
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}/".
381 * @see com.stevesoft.pat.Validator#arg(java.lang.String)
383 public ReplaceRule arg(String s)
388 static int getHexDigit(char c)
390 if (c >= '0' && c <= '9')
394 if (c >= 'a' && c <= 'f')