replace.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. //
  2. // assembly: System
  3. // namespace: System.Text.RegularExpressions
  4. // file: replace.cs
  5. //
  6. // author: Dan Lewis ([email protected])
  7. // (c) 2002
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. using System;
  29. using System.Text;
  30. using System.Collections;
  31. using Parser = System.Text.RegularExpressions.Syntax.Parser;
  32. namespace System.Text.RegularExpressions {
  33. class ReplacementEvaluator {
  34. public static string Evaluate (string replacement, Match match) {
  35. ReplacementEvaluator ev = new ReplacementEvaluator (match.Regex, replacement);
  36. return ev.Evaluate (match);
  37. }
  38. public ReplacementEvaluator (Regex regex, string replacement) {
  39. this.regex = regex;
  40. this.replacement = replacement;
  41. this.pieces = null;
  42. this.n_pieces = 0;
  43. Compile ();
  44. }
  45. public string Evaluate (Match match)
  46. {
  47. StringBuilder sb = new StringBuilder ();
  48. EvaluateAppend (match, sb);
  49. return sb.ToString ();
  50. }
  51. public void EvaluateAppend (Match match, StringBuilder sb)
  52. {
  53. int i = 0, k, count;
  54. if (n_pieces == 0) {
  55. sb.Append (replacement);
  56. return;
  57. }
  58. while (i < n_pieces) {
  59. k = pieces [i++];
  60. if (k >= 0) {
  61. count = pieces [i++];
  62. sb.Append (replacement, k, count);
  63. } else if (k < -3) {
  64. Group group = match.Groups [-(k + 4)];
  65. sb.Append (group.Text, group.Index, group.Length);
  66. } else if (k == -1) {
  67. sb.Append (match.Text);
  68. } else if (k == -2) {
  69. sb.Append (match.Text, 0, match.Index);
  70. } else { // k == -3
  71. int matchend = match.Index + match.Length;
  72. sb.Append (match.Text, matchend, match.Text.Length - matchend);
  73. }
  74. }
  75. }
  76. void Ensure (int size)
  77. {
  78. int new_size;
  79. if (pieces == null) {
  80. new_size = 4;
  81. if (new_size < size)
  82. new_size = size;
  83. pieces = new int [new_size];
  84. } else if (size >= pieces.Length) {
  85. new_size = pieces.Length + (pieces.Length >> 1);
  86. if (new_size < size)
  87. new_size = size;
  88. int [] new_pieces = new int [new_size];
  89. Array.Copy (pieces, new_pieces, n_pieces);
  90. pieces = new_pieces;
  91. }
  92. }
  93. void AddFromReplacement (int start, int end)
  94. {
  95. if (start == end)
  96. return;
  97. Ensure (n_pieces + 2);
  98. pieces [n_pieces++] = start;
  99. pieces [n_pieces++] = end - start;
  100. }
  101. void AddInt (int i)
  102. {
  103. Ensure (n_pieces + 1);
  104. pieces [n_pieces++] = i;
  105. }
  106. // private
  107. private void Compile () {
  108. replacement = Parser.Unescape (replacement);
  109. int anchor = 0, ptr = 0, saveptr;
  110. char c;
  111. while (ptr < replacement.Length) {
  112. c = replacement [ptr++];
  113. if (c != '$')
  114. continue;
  115. // If the '$' was the last character, just emit it as is
  116. if (ptr == replacement.Length)
  117. break;
  118. // If we saw a '$$'
  119. if (replacement [ptr] == '$') {
  120. // Everthing from 'anchor' upto and including the first '$' is copied from the replacement string
  121. AddFromReplacement (anchor, ptr);
  122. // skip over the second '$'.
  123. anchor = ++ptr;
  124. continue;
  125. }
  126. saveptr = ptr - 1;
  127. int from_match = CompileTerm (ref ptr);
  128. // We couldn't recognize the term following the '$'. Just treat it as a literal.
  129. // 'ptr' has already been advanced, no need to rewind it back
  130. if (from_match >= 0)
  131. continue;
  132. AddFromReplacement (anchor, saveptr);
  133. AddInt (from_match);
  134. anchor = ptr;
  135. }
  136. // If we never needed to advance anchor, it means the result is the whole replacement string.
  137. // We optimize that case by never allocating the pieces array.
  138. if (anchor != 0)
  139. AddFromReplacement (anchor, ptr);
  140. }
  141. private int CompileTerm (ref int ptr) {
  142. char c = replacement [ptr];
  143. if (Char.IsDigit (c)) { // numbered group
  144. int n = Parser.ParseDecimal (replacement, ref ptr);
  145. if (n < 0 || n > regex.GroupCount)
  146. return 0;
  147. return -n - 4;
  148. }
  149. ++ ptr;
  150. switch (c) {
  151. case '{': { // named group
  152. string name;
  153. int n = -1;
  154. try {
  155. // The parser is written such that there are few explicit range checks
  156. // and depends on 'IndexOutOfRangeException' being thrown.
  157. if (Char.IsDigit (replacement [ptr])) {
  158. n = Parser.ParseDecimal (replacement, ref ptr);
  159. name = "";
  160. } else {
  161. name = Parser.ParseName (replacement, ref ptr);
  162. }
  163. } catch (IndexOutOfRangeException) {
  164. ptr = replacement.Length;
  165. return 0;
  166. }
  167. if (ptr == replacement.Length || replacement[ptr] != '}' || name == null)
  168. return 0;
  169. ++ptr; // Swallow the '}'
  170. if (name != "")
  171. n = regex.GroupNumberFromName (name);
  172. if (n < 0 || n > regex.GroupCount)
  173. return 0;
  174. return -n - 4;
  175. }
  176. case '&': // entire match. Value should be same as $0
  177. return -4;
  178. case '`': // text before match
  179. return -2;
  180. case '\'': // text after match
  181. return -3;
  182. case '+': // last group
  183. return -regex.GroupCount - 4;
  184. case '_': // entire text
  185. return -1;
  186. default:
  187. return 0;
  188. }
  189. }
  190. private Regex regex;
  191. int n_pieces;
  192. private int [] pieces;
  193. string replacement;
  194. }
  195. }