001/* Replace an instance of a string with another input string according 002 to a regular expression. 003 004 Copyright (c) 2003-2014 The Regents of the University of California. 005 All rights reserved. 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the above 009 copyright notice and the following two paragraphs appear in all copies 010 of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 028 */ 029package ptolemy.actor.lib.string; 030 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033import java.util.regex.PatternSyntaxException; 034 035import ptolemy.actor.TypedAtomicActor; 036import ptolemy.actor.TypedIOPort; 037import ptolemy.actor.parameters.PortParameter; 038import ptolemy.data.BooleanToken; 039import ptolemy.data.StringToken; 040import ptolemy.data.expr.Parameter; 041import ptolemy.data.expr.SingletonParameter; 042import ptolemy.data.type.BaseType; 043import ptolemy.kernel.CompositeEntity; 044import ptolemy.kernel.util.Attribute; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.NameDuplicationException; 047import ptolemy.kernel.util.Workspace; 048 049/////////////////////////////////////////////////////////////////// 050//// StringReplace 051 052/** 053 On each firing, look for instances of the pattern specified by <i>pattern</i> 054 in <i>stringToEdit</i> and replace them with the string given by 055 <i>replacement</i>. If <i>replaceAll</i> is true, then replace 056 all instances that match <i>pattern</i>. Otherwise, replace only 057 the first instance that matches. If there is no match, then the 058 output is the string provided by <i>stringToEdit</i>, unchanged. 059 The <i>pattern</i> is given by a regular expression. 060 For a reference on regular expression syntax see: 061 <a href="http://download.oracle.com/javase/tutorial/essential/regex/#in_browser"> 062 http://download.oracle.com/javase/tutorial/essential/regex/</a>. 063 064 <p> 065 The <i>replacement</i> string, as usual with string-valued parameters 066 in Ptolemy II, can include references to parameter values in scope. 067 E.g., if the enclosing composite actor has a parameter named "x" 068 with value 1, say, then the replacement string a${x}b will become 069 "a1b". 070 <p> 071 In addition, the <i>replacement</i> string can reference the pattern 072 that is matched using the syntax "$$0". For example, the regular 073 expression "t[a-z]+" in <i>pattern</i> will match the character t followed by a 074 sequence of one or more lower-case letters. 075 If <i>replacement</i> is "p$$0" then "this is a test" becomes 076 "pthis is a ptest". 077 <p> 078 If the <i>pattern</i> contains parenthesized subpatterns, such 079 as "(t[a-z]+)|(T([a-z]+))", then the value of <i>replacement</i> 080 can reference the match of each parenthesized subpattern with 081 the syntax "$$n", where "n" is an integer between 1 and 9. 082 For example, if <i>pattern</i>="(t[a-z]+)|(T([a-z]+))" 083 and <i>replacement</i>="p$$1$$3", then "this is a Test" becomes 084 "pthis is a pest". The index "n" corresponds to the order 085 of opening parentheses in the pattern. 086 <p> 087 To get a "$" into the replacement string, use 088 "\$$". To get a "\" into the replacement string, use "\\". 089 090 @author Antonio Yordan-Nones, Neil E. Turner, Edward A. Lee 091 @version $Id$ 092 @since Ptolemy II 4.0 093 @Pt.ProposedRating Green (djstone) 094 @Pt.AcceptedRating Green (net) 095 */ 096public class StringReplace extends TypedAtomicActor { 097 /** Construct an actor with the given container and name. 098 * @param container The container. 099 * @param name The name of this actor. 100 * @exception IllegalActionException If the actor cannot be contained 101 * by the proposed container. 102 * @exception NameDuplicationException If the container already has an 103 * actor with this name. 104 */ 105 public StringReplace(CompositeEntity container, String name) 106 throws NameDuplicationException, IllegalActionException { 107 super(container, name); 108 109 // Create new parameters and ports. 110 // Set default values of the parameters and type constraints. 111 pattern = new PortParameter(this, "pattern"); 112 pattern.setStringMode(true); 113 pattern.setExpression(""); 114 new SingletonParameter(pattern.getPort(), "_showName") 115 .setToken(BooleanToken.TRUE); 116 117 replacement = new PortParameter(this, "replacement"); 118 replacement.setStringMode(true); 119 replacement.setExpression(""); 120 new SingletonParameter(replacement.getPort(), "_showName") 121 .setToken(BooleanToken.TRUE); 122 123 stringToEdit = new PortParameter(this, "stringToEdit"); 124 stringToEdit.setStringMode(true); 125 stringToEdit.setExpression(""); 126 new SingletonParameter(stringToEdit.getPort(), "_showName") 127 .setToken(BooleanToken.TRUE); 128 129 output = new TypedIOPort(this, "output", false, true); 130 output.setTypeEquals(BaseType.STRING); 131 132 replaceAll = new Parameter(this, "replaceAll"); 133 replaceAll.setExpression("true"); 134 replaceAll.setTypeEquals(BaseType.BOOLEAN); 135 136 regularExpression = new Parameter(this, "regularExpression"); 137 regularExpression.setExpression("true"); 138 regularExpression.setTypeEquals(BaseType.BOOLEAN); 139 } 140 141 /////////////////////////////////////////////////////////////////// 142 //// ports and parameters //// 143 144 /** The string to edit by replacing substrings that match the 145 * specified pattern with the specified replacement. This is 146 * a string that defaults to the empty string. 147 */ 148 public PortParameter stringToEdit; 149 150 /** The output port on which the edited string is produced. 151 * This has type string. 152 */ 153 public TypedIOPort output; 154 155 /** The pattern used to pattern match and replace the stringToEdit 156 * string. It is an empty string by default. 157 */ 158 public PortParameter pattern; 159 160 /** The replacement string that replaces any matched instance of the 161 * pattern. It is an empty string by default. 162 */ 163 public PortParameter replacement; 164 165 /** When the boolean value is true, replace all instances that match the 166 * pattern, and when false, replace the first instance. 167 */ 168 public Parameter replaceAll; 169 170 /** If true, interpret the pattern as a regular expression. Otherwise, 171 * interpret it as the literal string to replace. This is a boolean 172 * that defaults to true. 173 */ 174 public Parameter regularExpression; 175 176 /////////////////////////////////////////////////////////////////// 177 //// public methods //// 178 179 /** Override the base class to compile a regular expression when 180 * it is changed. 181 * @param attribute The attribute that changed. 182 * @exception IllegalActionException If the specified attribute 183 * is <i>pattern</i> and the regular expression fails to 184 * compile. 185 */ 186 @Override 187 public void attributeChanged(Attribute attribute) 188 throws IllegalActionException { 189 if (attribute == pattern) { 190 _patternValue = ((StringToken) pattern.getToken()).stringValue(); 191 // FIXME: What is the following about??? 192 if (_patternValue.equals("\\r")) { 193 _patternValue = "\r"; 194 } 195 _pattern = null; 196 } else { 197 super.attributeChanged(attribute); 198 } 199 } 200 201 /** Clone the attribute into the specified workspace. The resulting 202 * object has no base directory name nor any reference to any open stream. 203 * @param workspace The workspace for the new object. 204 * @return A new attribute. 205 * @exception CloneNotSupportedException If a derived class contains 206 * an attribute that cannot be cloned. 207 */ 208 @Override 209 public Object clone(Workspace workspace) throws CloneNotSupportedException { 210 StringReplace newObject = (StringReplace) super.clone(workspace); 211 try { 212 newObject.attributeChanged(newObject.pattern); 213 } catch (IllegalActionException e) { 214 // Should not occur. 215 throw new CloneNotSupportedException("Cloning failed"); 216 } 217 return newObject; 218 } 219 220 /** Perform pattern matching and substring replacement, and output 221 * the modified string. If no match is found, output the 222 * unmodified stringToEdit string. 223 * @exception IllegalActionException If there is no director. 224 */ 225 @Override 226 public void fire() throws IllegalActionException { 227 super.fire(); 228 229 replacement.update(); 230 stringToEdit.update(); 231 pattern.update(); 232 233 String replacementValue = ((StringToken) replacement.getToken()) 234 .stringValue(); 235 String stringToEditValue = ((StringToken) stringToEdit.getToken()) 236 .stringValue(); 237 boolean replaceAllTokens = ((BooleanToken) replaceAll.getToken()) 238 .booleanValue(); 239 boolean regularExpressionValue = ((BooleanToken) regularExpression 240 .getToken()).booleanValue(); 241 242 if (regularExpressionValue) { 243 if (_pattern == null) { 244 try { 245 String patternValue = ((StringToken) pattern.getToken()) 246 .stringValue(); 247 _pattern = Pattern.compile(patternValue); 248 } catch (PatternSyntaxException ex) { 249 String patternValue = ((StringToken) pattern.getToken()) 250 .stringValue(); 251 throw new IllegalActionException(this, ex, 252 "Failed to compile regular expression \"" 253 + patternValue + "\""); 254 } 255 } 256 Matcher match = _pattern.matcher(stringToEditValue); 257 String outputString = ""; 258 259 // Unfortunately, the String class throws runtime exceptions 260 // if something goes wrong, so we have to catch them. 261 try { 262 if (replaceAllTokens) { 263 outputString = match.replaceAll(replacementValue); 264 } else { 265 outputString = match.replaceFirst(replacementValue); 266 } 267 } catch (Exception ex) { 268 throw new IllegalActionException(this, ex, 269 "String replace failed."); 270 } 271 272 output.send(0, new StringToken(outputString)); 273 } else { 274 // No regular expression. 275 String outputString; 276 if (replaceAllTokens) { 277 outputString = stringToEditValue.replaceAll(_patternValue, 278 replacementValue); 279 } else { 280 outputString = stringToEditValue.replace(_patternValue, 281 replacementValue); 282 } 283 output.send(0, new StringToken(outputString)); 284 } 285 } 286 287 /////////////////////////////////////////////////////////////////// 288 //// private variables //// 289 290 // The compiled regular expression. 291 private Pattern _pattern; 292 293 // The replacement string. 294 private String _patternValue; 295}