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