15
15
*/
16
16
package com .arpnetworking .utility ;
17
17
18
- import scala .Int ;
19
-
20
18
import java .util .Map ;
21
19
import java .util .regex .Matcher ;
22
20
@@ -33,7 +31,7 @@ public final class RegexAndMapReplacer {
33
31
* @param input input string to match against
34
32
* @param replace replacement string
35
33
* @param variables map of variables to include
36
- * @return
34
+ * @return a string with replacement tokens replaced
37
35
*/
38
36
public static String replaceAll (final Matcher matcher , final String input , final String replace , final Map <String , String > variables ) {
39
37
@@ -59,73 +57,87 @@ public static String replaceAll(final Matcher matcher, final String input, final
59
57
60
58
private static void appendReplacement (final Matcher matcher , final String replacement , final StringBuilder replacementBuilder ,
61
59
final Map <String , String > variables ) {
62
- final StringBuilder tokenStringBuilder = new StringBuilder ();
63
- boolean inEscape = false ;
64
- boolean readingReplaceToken = false ;
65
- boolean inReplaceBrackets = false ;
66
- boolean replaceTokenNumeric = true ; // assume token is numeric, any non-numeric character will set this to false
67
- for (int x = 0 ; x < replacement .length (); x ++) {
60
+ final StringBuilder tokenBuilder = new StringBuilder ();
61
+ int x = -1 ;
62
+ while (x < replacement .length () - 1 ) {
63
+ x ++;
68
64
final char c = replacement .charAt (x );
69
- if (!inEscape && c == '\\' ) {
70
- inEscape = true ;
71
- continue ;
72
- }
73
- if (inEscape ) {
74
- final StringBuilder builder ;
75
- if (readingReplaceToken ) {
76
- builder = tokenStringBuilder ;
65
+ if (c == '\\' ) {
66
+ x ++;
67
+ processEscapedCharacter (replacement , x , replacementBuilder );
68
+ } else {
69
+ if (c == '$' ) {
70
+ x += writeReplacementToken (replacement , x , replacementBuilder , matcher , variables , tokenBuilder );
77
71
} else {
78
- builder = replacementBuilder ;
72
+ replacementBuilder . append ( c ) ;
79
73
}
80
- if (c == '\\' || c == '$' || c == '}' ) {
81
- builder .append (c );
82
- inEscape = false ;
74
+ }
75
+ }
76
+ }
77
+
78
+ private static void processEscapedCharacter (final String replacement , final int x , final StringBuilder builder ) {
79
+ if (x >= replacement .length ()) {
80
+ throw new IllegalArgumentException (
81
+ String .format ("Improper escaping in replacement, must not have trailing '\\ ' at col %d: %s" , x , replacement ));
82
+ }
83
+ final Character c = replacement .charAt (x );
84
+ if (c == '\\' || c == '$' || c == '}' ) {
85
+ builder .append (c );
86
+ } else {
87
+ throw new IllegalArgumentException (
88
+ String .format ("Improperly escaped '%s' in replacement at col %d: %s" , c , x , replacement ));
89
+ }
90
+ }
91
+
92
+ private static int writeReplacementToken (final String replacement , final int offset , final StringBuilder output ,
93
+ final Matcher matcher , final Map <String , String > variables , final StringBuilder tokenBuilder ) {
94
+ boolean inReplaceBrackets = false ;
95
+ boolean tokenNumeric = true ;
96
+ tokenBuilder .setLength (0 ); // reset the shared builder
97
+ int x = offset + 1 ;
98
+ char c = replacement .charAt (x );
99
+
100
+ // Optionally consume the opening brace
101
+ if (c == '{' ) {
102
+ inReplaceBrackets = true ;
103
+ x ++;
104
+ c = replacement .charAt (x );
105
+ }
106
+
107
+ if (inReplaceBrackets ) {
108
+ // Consume until we hit the }
109
+ while (x < replacement .length () - 1 && c != '}' ) {
110
+ if (c == '\\' ) {
111
+ x ++;
112
+ processEscapedCharacter (replacement , x , tokenBuilder );
83
113
} else {
84
- throw new IllegalArgumentException ( "Improperly escaped '" + String . valueOf ( c ) + "' in replacement at col " + x + ": " + replacement );
114
+ tokenBuilder . append ( c );
85
115
}
86
- } else if (readingReplaceToken ) {
87
- if (c == '{' ) {
88
- inReplaceBrackets = true ;
89
- continue ;
90
- } else if (c == '}' && inReplaceBrackets ) {
91
- final String replaceToken = tokenStringBuilder .toString ();
92
- replacementBuilder .append (getReplacement (matcher , replaceToken , replaceTokenNumeric , variables ));
93
- tokenStringBuilder .setLength (0 );
94
- inReplaceBrackets = false ;
95
- readingReplaceToken = false ;
96
- continue ;
97
- } else if (!Character .isDigit (c )) {
98
- if (inReplaceBrackets ) {
99
- replaceTokenNumeric = false ;
100
- tokenStringBuilder .append (c );
101
- } else {
102
- final String replaceToken = tokenStringBuilder .toString ();
103
- if (replaceToken .isEmpty ()) {
104
- throw new IllegalArgumentException ("Non-numeric replacements must be of the form ${val}. Missing '{' at col "
105
- + x + ": " + replacement );
106
- }
107
- replacementBuilder .append (getReplacement (matcher , replaceToken , replaceTokenNumeric , variables ));
108
- tokenStringBuilder .setLength (0 );
109
- readingReplaceToken = false ;
110
- x --; // We can't process the character because we are no longer in the numeric group syntax, we need to process the
111
- // $n replacement and then evaluate this character again.
112
- continue ;
113
- }
114
- } else {
115
- tokenStringBuilder .append (c );
116
+ if (tokenNumeric && !Character .isDigit (c )) {
117
+ tokenNumeric = false ;
116
118
}
117
- } else {
118
- if (c == '$' ) {
119
- readingReplaceToken = true ;
119
+ x ++;
120
+ c = replacement .charAt (x );
121
+ }
122
+ if (c != '}' ) {
123
+ throw new IllegalArgumentException ("Invalid replacement token, expected '}' at col " + x + ": " + replacement );
124
+ }
125
+ x ++; // Consume the }
126
+ output .append (getReplacement (matcher , tokenBuilder .toString (), tokenNumeric , variables ));
127
+ } else {
128
+ // Consume until we hit a non-digit character
129
+ while (x < replacement .length ()) {
130
+ c = replacement .charAt (x );
131
+ if (Character .isDigit (c )) {
132
+ tokenBuilder .append (c );
120
133
} else {
121
- replacementBuilder . append ( c ) ;
134
+ break ;
122
135
}
136
+ x ++;
123
137
}
138
+ output .append (getReplacement (matcher , tokenBuilder .toString (), true , variables ));
124
139
}
125
- if (tokenStringBuilder .length () > 0 && replaceTokenNumeric ) {
126
- final String replaceToken = tokenStringBuilder .toString ();
127
- replacementBuilder .append (getReplacement (matcher , replaceToken , true , variables ));
128
- }
140
+ return x - offset - 1 ;
129
141
}
130
142
131
143
private static String getReplacement (final Matcher matcher , final String replaceToken , final boolean numeric ,
@@ -142,5 +154,5 @@ private static String getReplacement(final Matcher matcher, final String replace
142
154
}
143
155
}
144
156
145
- private RegexAndMapReplacer () { }
157
+ private RegexAndMapReplacer () { }
146
158
}
0 commit comments