1515 */
1616package com .arpnetworking .utility ;
1717
18- import scala .Int ;
19-
2018import java .util .Map ;
2119import java .util .regex .Matcher ;
2220
@@ -33,7 +31,7 @@ public final class RegexAndMapReplacer {
3331 * @param input input string to match against
3432 * @param replace replacement string
3533 * @param variables map of variables to include
36- * @return
34+ * @return a string with replacement tokens replaced
3735 */
3836 public static String replaceAll (final Matcher matcher , final String input , final String replace , final Map <String , String > variables ) {
3937
@@ -59,73 +57,87 @@ public static String replaceAll(final Matcher matcher, final String input, final
5957
6058 private static void appendReplacement (final Matcher matcher , final String replacement , final StringBuilder replacementBuilder ,
6159 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 ++;
6864 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 );
7771 } else {
78- builder = replacementBuilder ;
72+ replacementBuilder . append ( c ) ;
7973 }
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 );
83113 } else {
84- throw new IllegalArgumentException ( "Improperly escaped '" + String . valueOf ( c ) + "' in replacement at col " + x + ": " + replacement );
114+ tokenBuilder . append ( c );
85115 }
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 ;
116118 }
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 );
120133 } else {
121- replacementBuilder . append ( c ) ;
134+ break ;
122135 }
136+ x ++;
123137 }
138+ output .append (getReplacement (matcher , tokenBuilder .toString (), true , variables ));
124139 }
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 ;
129141 }
130142
131143 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
142154 }
143155 }
144156
145- private RegexAndMapReplacer () { }
157+ private RegexAndMapReplacer () { }
146158}
0 commit comments