--- /dev/null
+//\r
+// This software is now distributed according to\r
+// the Lesser Gnu Public License. Please see\r
+// http://www.gnu.org/copyleft/lesser.txt for\r
+// the details.\r
+// -- Happy Computing!\r
+//\r
+package com.stevesoft.pat;\r
+\r
+import java.util.*;\r
+\r
+/** ReplaceRule is a singly linked list of Objects which describe how\r
+ to replace the matched portion of a String. The only member method\r
+ that you absolutely need to define to use this class is apply(StringBuffer,RegRes) --\r
+ although you may want define toString1() and clone1() (if you are\r
+ unhappy with the default methods) that are needed by\r
+ the clone() or toString() methods on this class.\r
+ During the replacement process, each ReplaceRule tells the replacer\r
+ what to add to StringBuffer and uses the contents of the Regular\r
+ expression result to get the information it needs to\r
+ do this. Here is an <a href="http://javaregex.com/code/fancy.java.html">example</a>\r
+\r
+ @see com.stevesoft.pat.NullRule\r
+ @see com.stevesoft.pat.AmpersandRule\r
+ @see com.stevesoft.pat.BackRefRule\r
+ @see com.stevesoft.pat.LeftRule\r
+ @see com.stevesoft.pat.RightRule\r
+ @see com.stevesoft.pat.StringRule\r
+ */\r
+public abstract class ReplaceRule {\r
+ /** points to the next ReplaceRule in the linked list. */\r
+ protected ReplaceRule next = null;\r
+ /** This function appends to the StringBufferLike the text you want\r
+ to replaced the portion of the String last matched. */\r
+ public abstract void apply(StringBufferLike sb,RegRes r);\r
+\r
+ /** A rule describing how to clone only the current ReplaceRule,\r
+ and none of the others in this linked list. It is called by\r
+ clone() for each item in the list. */\r
+ public Object clone1() {\r
+ return new RuleHolder(this);\r
+ }\r
+ public final Object clone() {\r
+ ReplaceRule x = (ReplaceRule)clone1();\r
+ ReplaceRule xsav = x;\r
+ ReplaceRule y = this;\r
+ while(y.next != null) {\r
+ x.next = (ReplaceRule)y.next.clone1();\r
+ x.name = y.name;\r
+ x = x.next;\r
+ y = y.next;\r
+ }\r
+ return xsav;\r
+ }\r
+ static ReplaceRule add(ReplaceRule head,ReplaceRule adding) {\r
+ if(head == null)\r
+ return head = adding;\r
+ head.addRule(adding);\r
+ return head;\r
+ }\r
+ public ReplaceRule add(ReplaceRule adding) {\r
+ return add(this,adding);\r
+ }\r
+ /** Add another ReplaceRule to the linked list. */\r
+ public void addRule(ReplaceRule r) {\r
+ if(next == null) next = r;\r
+ else next.addRule(r);\r
+ }\r
+ static Regex getvar = null;\r
+ final static Regex getv() {\r
+ // Thanks to Michael Jimenez for pointing out the need\r
+ // to clone getvar rather than simply returning it.\r
+ // Previously this was not thread safe.\r
+ //if(getvar != null) return getvar;\r
+ if(getvar != null) return (Regex)getvar.clone();\r
+ getvar=\r
+ new Regex(\r
+ "(?:\\\\(\\d+)|"+ // ref 1\r
+ "\\$(?:"+\r
+ "(\\d+)|"+ // ref 2\r
+ "(\\w+)|"+ // ref 3\r
+ "([&'`])|"+ // ref 4\r
+ "\\{(?:(\\d+)|"+ // ref 5\r
+ "([^\n}\\\\]+))}"+ // ref 6\r
+ ")|"+ \r
+ "\\\\([nrbtaef])|"+ // ref 7\r
+ "\\\\c([\u0000-\uFFFF])|"+ // ref 8\r
+ "\\\\x([A-Fa-f0-9]{2})|"+ // ref 9\r
+ "\\\\([\u0000-\uFFFF])"+ // ref 10\r
+ ")");\r
+ getvar.optimize();\r
+ return getvar;\r
+ }\r
+ /** Compile a ReplaceRule using the text that would go between\r
+ the second and third /'s in a typical substitution pattern\r
+ in Perl: s/ ... / <i>The argument to ReplaceRule.perlCode</i> /.\r
+ */\r
+ public static ReplaceRule perlCode(String s) {\r
+ //String sav_backGs = Regex.backGs;\r
+ //int sav_backGto = Regex.backGto;\r
+ try {\r
+ int mf = 0, mt = 0;\r
+ Regex gv = getv();\r
+ ReplaceRule head = null;\r
+ Object tmp = null;\r
+ while(gv.searchFrom(s,mt)) {\r
+ int off=Regex.BackRefOffset-1;\r
+ mf = gv.matchedFrom();\r
+ if(mf > mt)\r
+ head=add(head,\r
+ new StringRule(s.substring(mt,mf)));\r
+ String var = null;\r
+ if((var=gv.stringMatched(1+off)) != null\r
+ || (var=gv.stringMatched(2+off)) != null\r
+ || (var=gv.stringMatched(5+off)) != null) {\r
+ int d=0;\r
+ for(int i=0;i<var.length();i++)\r
+ d = 8*d+( var.charAt(i)-'0' );\r
+ if(var.length() == 1)\r
+ head=add(head,new BackRefRule(d));\r
+ else\r
+ head=new StringRule(""+(char)d);\r
+ } else if(\r
+ (var=gv.stringMatched(10+off)) != null) {\r
+ if("QELlUu".indexOf(var) >= 0)\r
+ head=add(head,new CodeRule(var.charAt(0)) );\r
+ else\r
+ head=add(head,new StringRule(var) );\r
+ } else if(\r
+ (var=gv.stringMatched(3+off)) != null\r
+ || (var=gv.stringMatched(4+off)) != null\r
+ || (var=gv.stringMatched(6+off)) != null) {\r
+ String arg = "";\r
+ int pc;\r
+ if((pc=var.indexOf(':')) > 0) {\r
+ arg = var.substring(pc+1);\r
+ var = var.substring(0,pc);\r
+ }\r
+ if(var.equals("&")||var.equals("MATCH")) {\r
+ head=add(head,new AmpersandRule());\r
+ } else if(var.equals("`")||var.equals("PREMATCH")) {\r
+ head=add(head,new LeftRule());\r
+ } else if(var.equals("'")||var.equals("POSTMATCH")) {\r
+ head=add(head,new RightRule());\r
+ } else if(var.equals("WANT_MORE_TEXT")) {\r
+ head=add(head,new WantMoreTextReplaceRule());\r
+ } else if(var.equals("POP")) {\r
+ head=add(head,new PopRule());\r
+ } else if(var.startsWith("+") && (tmp=defs.get(var.substring(1))) != null) {\r
+ if(tmp instanceof Regex)\r
+ head=add(head,new PushRule(var.substring(1),(Regex)tmp));\r
+ else if(tmp instanceof Transformer)\r
+ head=add(head,new PushRule(var.substring(1),(Transformer)tmp));\r
+ else head=add(head,new StringRule("${"+var+"}"));\r
+ } else if(var.startsWith("=") && (tmp=defs.get(var.substring(1))) != null) {\r
+ if(tmp instanceof Regex)\r
+ head=add(head,new ChangeRule(var.substring(1),(Regex)tmp));\r
+ else if(tmp instanceof Transformer)\r
+ head=add(head,new ChangeRule(var.substring(1),(Transformer)tmp));\r
+ else head=add(head,new StringRule("${"+var+"}"));\r
+ } else if( (tmp=defs.get(var)) != null) {\r
+ if(tmp instanceof ReplaceRule) {\r
+ ReplaceRule alt = ((ReplaceRule)tmp).arg(arg);\r
+ if(alt == null) alt = ((ReplaceRule)tmp);\r
+ head=add(head,(ReplaceRule)(alt.clone()));\r
+ }\r
+ } else // can't figure out how to transform this thing...\r
+ head=add(head,new StringRule("${"+var+"}"));\r
+ } else if(\r
+ (var = gv.stringMatched(7+off)) != null) {\r
+ char c = var.charAt(0);\r
+ if(c == 'n')\r
+ head=add(head,new StringRule("\n"));\r
+ else if(c == 't')\r
+ head=add(head,new StringRule("\t"));\r
+ else if(c == 'r')\r
+ head=add(head,new StringRule("\r"));\r
+ else if(c == 'b')\r
+ head=add(head,new StringRule("\r"));\r
+ else if(c == 'a')\r
+ head=add(head,new StringRule(""+(char)7));\r
+ else if(c == 'e')\r
+ head=add(head,new StringRule(""+(char)27));\r
+ else if(c == 'f')\r
+ head=add(head,new StringRule(""+(char)12));\r
+ } else if(\r
+ (var = gv.stringMatched(8+off)) != null) {\r
+ char c = var.charAt(0);\r
+ if(c < Ctrl.cmap.length)\r
+ c = Ctrl.cmap[c];\r
+ head=add(head,new StringRule(""+c));\r
+ } else if(\r
+ (var = gv.stringMatched(9+off)) != null) {\r
+ int d =\r
+ 16*getHexDigit(var.charAt(0))+\r
+ getHexDigit(var.charAt(1));\r
+ head=add(head,new StringRule(""+(char)d));\r
+ }\r
+ mt = gv.matchedTo();\r
+ }\r
+ if(mt <= s.length())\r
+ head=add(head,new StringRule(s.substring(mt)));\r
+ return head;\r
+ } finally {\r
+ //Regex.backGs = sav_backGs;\r
+ //Regex.backGto = sav_backGto;\r
+ }\r
+ }\r
+ static Hashtable defs = new Hashtable();\r
+ public static boolean isDefined(String s) { return defs.get(s) != null; }\r
+ public static void define(String s,Regex r) { defs.put(s,r); }\r
+ public static void define(String s,ReplaceRule r) {\r
+ defs.put(s,r);\r
+ r.name = s;\r
+ }\r
+ String name = getClass().getName();\r
+ public static void define(String s,Transformer t) { defs.put(s,t); }\r
+ public static void undefine(String s) { defs.remove(s); }\r
+ /** This tells how to convert just the current element (and none\r
+ of the other items in the linked list) to a String. This\r
+ method is called by toString() for each item in the linked\r
+ list. */\r
+ public String toString1() {\r
+ return "${"+name+"}";\r
+ }\r
+ /** Convert to a String. */\r
+ public final String toString() {\r
+ StringBuffer sb = new StringBuffer();\r
+ sb.append(toString1());\r
+ ReplaceRule rr = this.next;\r
+ while(rr != null) {\r
+ sb.append(rr.toString1());\r
+ rr = rr.next;\r
+ }\r
+ return sb.toString();\r
+ }\r
+ /** Modified the behavior of a ReplaceRule by supplying\r
+ an argument. If a ReplaceRule named "foo" is defined\r
+ and the pattern "s/x/${foo:5}/" is given to Regex.perlCode,\r
+ then the "foo" the definition of "foo" will be retrieved\r
+ and arg("5") will be called. If the result is non-null,\r
+ that is the ReplaceRule that will be used. If the result\r
+ is null, then the pattern works just as if it were\r
+ "s/x/${foo}/".\r
+ @see com.stevesoft.pat.Validator#arg(java.lang.String)\r
+ */\r
+ public ReplaceRule arg(String s) { return null; }\r
+ static int getHexDigit(char c) {\r
+ if(c >= '0' && c <= '9')\r
+ return c - '0';\r
+ if(c >= 'a' && c <= 'f')\r
+ return c - 'a'+10;\r
+ return c - 'A'+10;\r
+ }\r
+}\r