001/** 002 * Copyright (c) 2001, Sergey A. Samokhodkin 003 * All rights reserved. 004 * <p> 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * <p> 008 * - Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * - Redistributions in binary form 011 * must reproduce the above copyright notice, this list of conditions and the following 012 * disclaimer in the documentation and/or other materials provided with the distribution. 013 * - Neither the name of jregex nor the names of its contributors may be used 014 * to endorse or promote products derived from this software without specific prior 015 * written permission. 016 * <p> 017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 018 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 019 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 020 * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 021 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 022 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 023 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 025 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 026 * 027 * @version 1.2_01 028 */ 029 030package regexodus; 031 032import java.io.Serializable; 033 034/** 035 * An implementation of the Substitution interface. Performs substitutions in accordance with Perl-like substitution scripts.<br> 036 * The latter is a string, containing a mix of memory register references and plain text blocks.<br> 037 * It may look like "some_chars $1 some_chars$2some_chars" or "123${1}45${2}67".<br> 038 * A tag consisting of '$',not preceded 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. 039 * All the rest is considered as a plain text.<br> 040 * Upon the Replacer has found a text block that matches the pattern, a references in a replacement string are replaced by the contents of 041 * corresponding memory registers, and the resulting text replaces the matched block.<br> 042 * For example, the following code: 043 * <pre> 044 * System.out.println("\""+ 045 * new Replacer(new Pattern("\\b(\\d+)\\b"),new PerlSubstitution("'$1'")).replace("abc 123 def") 046 * +"\""); 047 * </pre> 048 * will print <code>"abc '123' def"</code>.<br> 049 * @see Substitution 050 * @see Replacer 051 * @see Pattern 052 */ 053 054 055public class PerlSubstitution implements Substitution, Serializable { 056 //private static Pattern refPtn,argsPtn; 057 private static final long serialVersionUID = -1537346657932720807L; 058 059 private static Pattern refPtn; 060 private static int NAME_ID; 061 private static int ESC_ID; 062 //private static int FN_NAME_ID; 063 //private static int FN_ARGS_ID; 064 //private static int ARG_NAME_ID; 065 066 private static final String groupRef = "\\$(?:\\{({=name}\\w+)\\}|({=name}\\d+|&))|\\\\({esc}.)"; 067 //private static final String fnRef="\\&({fn_name}\\w+)\\(({fn_args}"+groupRef+"(?:,"+groupRef+")*)*\\)"; 068 069 static { 070 try { 071 //refPtn=new Pattern("(?<!\\\\)"+fnRef+"|"+groupRef); 072 //argsPtn=new Pattern(groupRef); 073 //refPtn=new Pattern("(?<!\\\\)"+groupRef); 074 refPtn = new Pattern(groupRef); 075 NAME_ID = refPtn.groupId("name"); 076 ESC_ID = refPtn.groupId("esc"); 077 //ARG_NAME_ID=argsPtn.groupId("name").intValue(); 078 //FN_NAME_ID=refPtn.groupId("fn_name").intValue(); 079 //FN_ARGS_ID=refPtn.groupId("fn_args").intValue(); 080 } catch (PatternSyntaxException e) { 081 e.printStackTrace(); 082 } 083 } 084 085 private Element queueEntry; 086 087 //It seems we should somehow throw an IllegalArgumentException if an expression 088 //holds a reference to a non-existing group. Such checking will require a Pattern instance. 089 public PerlSubstitution(String s) { 090 Matcher refMatcher = new Matcher(refPtn); 091 refMatcher.setTarget(s); 092 queueEntry = makeQueue(refMatcher); 093 } 094 095 public String value(MatchResult mr) { 096 TextBuffer dest = Replacer.wrap(new StringBuilder(mr.length())); 097 appendSubstitution(mr, dest); 098 return dest.toString(); 099 } 100 101 private static Element makeQueue(Matcher refMatcher) { 102 if (refMatcher.find()) { 103 Element element; 104 if (refMatcher.isCaptured(NAME_ID)) { 105 char c = refMatcher.charAt(0, NAME_ID); 106 if (c == '&') { 107 element = new IntRefHandler(refMatcher.prefix(), 0); 108 } else if (Character.isDigit(c)) { 109 element = new IntRefHandler(refMatcher.prefix(), new Integer(refMatcher.group(NAME_ID))); 110 } else 111 element = new StringRefHandler(refMatcher.prefix(), refMatcher.group(NAME_ID)); 112 } else { 113 //escaped char 114 element = new PlainElement(refMatcher.prefix(), refMatcher.group(ESC_ID)); 115 } 116 refMatcher.setTarget(refMatcher, MatchResult.SUFFIX); 117 element.next = makeQueue(refMatcher); 118 return element; 119 } else return new PlainElement(refMatcher.target()); 120 } 121 122 public void appendSubstitution(MatchResult match, TextBuffer dest) { 123 for (Element element = this.queueEntry; element != null; element = element.next) { 124 element.append(match, dest); 125 } 126 } 127 128 public String toString() { 129 StringBuilder sb = new StringBuilder(); 130 for (Element element = this.queueEntry; element != null; element = element.next) { 131 sb.append(element.toString()); 132 } 133 return sb.toString(); 134 } 135 136 private static abstract class Element { 137 String prefix; 138 Element next; 139 140 abstract void append(MatchResult match, TextBuffer dest); 141 } 142 143 private static class PlainElement extends Element { 144 private String str; 145 146 PlainElement(String s) { 147 str = s; 148 } 149 150 PlainElement(String pref, String s) { 151 prefix = pref; 152 str = s; 153 } 154 155 void append(MatchResult match, TextBuffer dest) { 156 if (prefix != null) dest.append(prefix); 157 if (str != null) dest.append(str); 158 } 159 } 160 161 private static class IntRefHandler extends Element { 162 private Integer index; 163 164 IntRefHandler(String s, Integer ind) { 165 prefix = s; 166 index = ind; 167 } 168 169 void append(MatchResult match, TextBuffer dest) { 170 if (prefix != null) dest.append(prefix); 171 if (index == null) return; 172 int i = index; 173 if (i >= match.pattern().groupCount()) return; 174 if (match.isCaptured(i)) match.getGroup(i, dest); 175 } 176 } 177 178 private static class StringRefHandler extends Element { 179 private String index; 180 181 StringRefHandler(String s, String ind) { 182 prefix = s; 183 index = ind; 184 } 185 186 void append(MatchResult match, TextBuffer dest) { 187 if (prefix != null) dest.append(prefix); 188 if (index == null) return; 189 //if(id==null) return; //??? 190 int i = match.pattern().groupId(index); 191 if (match.isCaptured(i)) match.getGroup(i, dest); 192 } 193 } 194 195 @Override 196 public boolean equals(Object o) { 197 if (this == o) return true; 198 if (o == null || getClass() != o.getClass()) return false; 199 200 PerlSubstitution that = (PerlSubstitution) o; 201 202 return queueEntry != null ? queueEntry.equals(that.queueEntry) : that.queueEntry == null; 203 204 } 205 206 @Override 207 public int hashCode() { 208 return queueEntry != null ? queueEntry.hashCode() : 0; 209 } 210} 211 212abstract class GReference { 213 public abstract String stringValue(MatchResult match); 214 215 public static GReference createInstance(MatchResult match, int grp) { 216 if (match.length(grp) == 0) throw new IllegalArgumentException("arg name cannot be an empty string"); 217 if (Character.isDigit(match.charAt(0, grp))) { 218 try { 219 return new IntReference(Integer.parseInt(match.group(grp))); 220 } catch (NumberFormatException e) { 221 throw new IllegalArgumentException("illegal arg name, starts with digit but is not a number"); 222 } 223 } 224 return new StringReference((match.group(grp))); 225 } 226 227} 228 229class IntReference extends GReference { 230 private int id; 231 232 IntReference(int id) { 233 this.id = id; 234 } 235 236 public String stringValue(MatchResult match) { 237 return match.group(id); 238 } 239 240 @Override 241 public boolean equals(Object o) { 242 if (this == o) return true; 243 if (o == null || getClass() != o.getClass()) return false; 244 245 IntReference that = (IntReference) o; 246 247 return id == that.id; 248 249 } 250 251 @Override 252 public int hashCode() { 253 return id; 254 } 255 256 @Override 257 public String toString() { 258 return "IntReference{" + 259 "id=" + id + 260 '}'; 261 } 262} 263 264class StringReference extends GReference { 265 private String name; 266 267 StringReference(String name) { 268 this.name = name; 269 } 270 271 public String stringValue(MatchResult match) { 272 return match.group(name); 273 } 274 275 @Override 276 public boolean equals(Object o) { 277 if (this == o) return true; 278 if (o == null || getClass() != o.getClass()) return false; 279 280 StringReference that = (StringReference) o; 281 282 return name != null ? name.equals(that.name) : that.name == null; 283 284 } 285 286 @Override 287 public int hashCode() { 288 return name != null ? name.hashCode() : 0; 289 } 290 291 @Override 292 public String toString() { 293 return "StringReference{" + 294 "name='" + name + '\'' + 295 '}'; 296 } 297}