/** * 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); } }