/**
* Copyright (c) 2001, Sergey A. Samokhodkin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form
* must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
* - Neither the name of jregex nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
* WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @version 1.2_01
*/
package totalcross.util.regex;
import totalcross.sys.*;
/**
* An implementation of the Substitution interface. Performs substitutions in accordance with Perl-like substitution scripts.<br>
* The latter is a string, containing a mix of memory register references and plain text blocks.<br>
* It may look like "some_chars $1 some_chars$2some_chars" or "123${1}45${2}67".<br>
* A tag consisting of '$',not preceeded by the escape character'\' and followed by some digits (possibly enclosed in the curled brackets) is interpreted as a memory register reference, the digits forming a register ID.
* All the rest is considered as a plain text.<br>
* Upon the Replacer has found a text block that matches the pattern, a references in a replacement string are replaced by the contents of
* corresponding memory registers, and the resulting text replaces the matched block.<br>
* For example, the following code:
* <pre>
* System.out.println("\""+
* new Replacer(new Pattern("\\b(\\d+)\\b"),new PerlSubstitution("'$1'")).replace("abc 123 def")
* +"\"");
* </pre>
* will print <code>"abc '123' def"</code>.<br>
* @see Substitution
* @see Replacer
* @see Pattern
*/
public class PerlSubstitution implements Substitution{
//private static Pattern refPtn,argsPtn;
private static Pattern refPtn;
private static int NAME_ID;
private static int ESC_ID;
//private static int FN_NAME_ID;
//private static int FN_ARGS_ID;
//private static int ARG_NAME_ID;
private static final String groupRef="\\$(?:\\{({=name}\\w+)\\}|({=name}\\d+|&))|\\\\({esc}.)";
//private static final String fnRef="\\&({fn_name}\\w+)\\(({fn_args}"+groupRef+"(?:,"+groupRef+")*)*\\)";
static{
try{
//refPtn=new Pattern("(?<!\\\\)"+fnRef+"|"+groupRef);
//argsPtn=new Pattern(groupRef);
//refPtn=new Pattern("(?<!\\\\)"+groupRef);
refPtn=new Pattern(groupRef);
NAME_ID=refPtn.groupId("name").intValue();
ESC_ID=refPtn.groupId("esc").intValue();
//ARG_NAME_ID=argsPtn.groupId("name").intValue();
//FN_NAME_ID=refPtn.groupId("fn_name").intValue();
//FN_ARGS_ID=refPtn.groupId("fn_args").intValue();
}
catch(PatternSyntaxException e){
e.printStackTrace();
}
}
private Element queueEntry;
//It seems we should somehow throw an IllegalArgumentException if an expression
//holds a reference to a non-existing group. Such checking will require a Pattern instance.
public PerlSubstitution(String s){
Matcher refMatcher=new Matcher(refPtn);
refMatcher.setTarget(s);
queueEntry=makeQueue(refMatcher);
}
public String value(MatchResult mr){
TextBuffer dest=Replacer.wrap(new StringBuffer(mr.length()));
appendSubstitution(mr,dest);
return dest.toString();
}
private static Element makeQueue(Matcher refMatcher){
if(refMatcher.find()){
Element element;
if(refMatcher.isCaptured(NAME_ID)){
char c=refMatcher.charAt(0,NAME_ID);
if(c=='&'){
element=new IntRefHandler(refMatcher.prefix(),new Integer(0));
}
else if(Character.isDigit(c)){
int v = 0;
try
{
v = Convert.toInt(refMatcher.group(NAME_ID));
}
catch (InvalidNumberException e)
{
}
element=new IntRefHandler(refMatcher.prefix(),new Integer(v));
}
else
element=new StringRefHandler(refMatcher.prefix(),refMatcher.group(NAME_ID));
}
else{
//escaped char
element=new PlainElement(refMatcher.prefix(),refMatcher.group(ESC_ID));
}
refMatcher.setTarget(refMatcher,MatchResult.SUFFIX);
element.next=makeQueue(refMatcher);
return element;
}
else return new PlainElement(refMatcher.target());
}
public void appendSubstitution(MatchResult match,TextBuffer dest){
for(Element element=this.queueEntry; element!=null; element=element.next){
element.append(match,dest);
}
}
public String toString(){
StringBuffer sb=new StringBuffer();
for(Element element=this.queueEntry;element!=null;element=element.next){
sb.append(element.toString());
}
return sb.toString();
}
private static abstract class Element{
protected String prefix;
Element next;
abstract void append(MatchResult match,TextBuffer dest);
}
private static class PlainElement extends Element{
private String str;
PlainElement(String s){
str=s;
}
PlainElement(String pref,String s){
prefix=pref;
str=s;
}
void append(MatchResult match,TextBuffer dest){
if(prefix!=null)dest.append(prefix);
if(str!=null)dest.append(str);
}
}
private static class IntRefHandler extends Element{
private Integer index;
IntRefHandler(String s,Integer ind){
prefix=s;
index=ind;
}
void append(MatchResult match,TextBuffer dest){
if(prefix!=null) dest.append(prefix);
if(index==null) return;
int i=index.intValue();
if(i>=match.pattern().groupCount()) return;
if(match.isCaptured(i))match.getGroup(i,dest);
}
}
private static class StringRefHandler extends Element{
private String index;
StringRefHandler(String s,String ind){
prefix=s;
index=ind;
}
void append(MatchResult match,TextBuffer dest){
if(prefix!=null) dest.append(prefix);
if(index==null) return;
Integer id=match.pattern().groupId(index);
//if(id==null) return; //???
int i=id.intValue();
if(match.isCaptured(i))match.getGroup(i,dest);
}
}
}
abstract class GReference{
public abstract String stringValue(MatchResult match);
public static GReference createInstance(MatchResult match,int grp){
if(match.length(grp)==0) throw new IllegalArgumentException("arg name cannot be an empty string");
if(Character.isDigit(match.charAt(0,grp))){
try{
return new IntReference(totalcross.sys.Convert.toInt(match.group(grp)));
}
catch(Exception e){
throw new IllegalArgumentException("illegal arg name, starts with digit but is not a number");
}
}
return new StringReference((match.group(grp)));
}
}
class IntReference extends GReference{
protected int id;
IntReference(int id){
this.id=id;
}
public String stringValue(MatchResult match){
return match.group(id);
}
}
class StringReference extends GReference{
protected String name;
StringReference(String name){
this.name=name;
}
public String stringValue(MatchResult match){
return match.group(name);
}
}