249 lines
6.0 KiB
C#
249 lines
6.0 KiB
C#
|
//
|
||
|
// assembly: System
|
||
|
// namespace: System.Text.RegularExpressions
|
||
|
// file: replace.cs
|
||
|
//
|
||
|
// author: Dan Lewis (dlewis@gmx.co.uk)
|
||
|
// (c) 2002
|
||
|
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||
|
// a copy of this software and associated documentation files (the
|
||
|
// "Software"), to deal in the Software without restriction, including
|
||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||
|
// the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be
|
||
|
// included in all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
//
|
||
|
|
||
|
using System;
|
||
|
using System.Text;
|
||
|
using System.Collections;
|
||
|
|
||
|
using Parser = System.Text.RegularExpressions.Syntax.Parser;
|
||
|
|
||
|
namespace System.Text.RegularExpressions {
|
||
|
|
||
|
class ReplacementEvaluator {
|
||
|
public static string Evaluate (string replacement, Match match)
|
||
|
{
|
||
|
ReplacementEvaluator ev = new ReplacementEvaluator (match.Regex, replacement);
|
||
|
return ev.Evaluate (match);
|
||
|
}
|
||
|
|
||
|
public ReplacementEvaluator (Regex regex, string replacement)
|
||
|
{
|
||
|
this.regex = regex;
|
||
|
this.replacement = replacement;
|
||
|
this.pieces = null;
|
||
|
this.n_pieces = 0;
|
||
|
Compile ();
|
||
|
}
|
||
|
|
||
|
public string Evaluate (Match match)
|
||
|
{
|
||
|
if (n_pieces == 0)
|
||
|
return replacement;
|
||
|
StringBuilder sb = new StringBuilder ();
|
||
|
EvaluateAppend (match, sb);
|
||
|
return sb.ToString ();
|
||
|
}
|
||
|
|
||
|
public void EvaluateAppend (Match match, StringBuilder sb)
|
||
|
{
|
||
|
if (n_pieces == 0) {
|
||
|
sb.Append (replacement);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int i = 0;
|
||
|
while (i < n_pieces) {
|
||
|
int k = pieces [i++];
|
||
|
if (k >= 0) {
|
||
|
int count = pieces [i++];
|
||
|
sb.Append (replacement, k, count);
|
||
|
} else if (k < -3) {
|
||
|
Group group = match.Groups [-(k + 4)];
|
||
|
sb.Append (group.Text, group.Index, group.Length);
|
||
|
} else if (k == -1) {
|
||
|
sb.Append (match.Text);
|
||
|
} else if (k == -2) {
|
||
|
sb.Append (match.Text, 0, match.Index);
|
||
|
} else { // k == -3
|
||
|
int matchend = match.Index + match.Length;
|
||
|
sb.Append (match.Text, matchend, match.Text.Length - matchend);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool NeedsGroupsOrCaptures {
|
||
|
get {
|
||
|
if (n_pieces == 0)
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Ensure (int size)
|
||
|
{
|
||
|
int new_size;
|
||
|
if (pieces == null) {
|
||
|
new_size = 4;
|
||
|
if (new_size < size)
|
||
|
new_size = size;
|
||
|
pieces = new int [new_size];
|
||
|
} else if (size >= pieces.Length) {
|
||
|
new_size = pieces.Length + (pieces.Length >> 1);
|
||
|
if (new_size < size)
|
||
|
new_size = size;
|
||
|
int [] new_pieces = new int [new_size];
|
||
|
Array.Copy (pieces, new_pieces, n_pieces);
|
||
|
pieces = new_pieces;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AddFromReplacement (int start, int end)
|
||
|
{
|
||
|
if (start == end)
|
||
|
return;
|
||
|
Ensure (n_pieces + 2);
|
||
|
pieces [n_pieces++] = start;
|
||
|
pieces [n_pieces++] = end - start;
|
||
|
}
|
||
|
|
||
|
void AddInt (int i)
|
||
|
{
|
||
|
Ensure (n_pieces + 1);
|
||
|
pieces [n_pieces++] = i;
|
||
|
}
|
||
|
|
||
|
// private
|
||
|
private void Compile ()
|
||
|
{
|
||
|
int anchor = 0, ptr = 0, saveptr;
|
||
|
char c;
|
||
|
while (ptr < replacement.Length) {
|
||
|
c = replacement [ptr++];
|
||
|
|
||
|
if (c != '$')
|
||
|
continue;
|
||
|
|
||
|
// If the '$' was the last character, just emit it as is
|
||
|
if (ptr == replacement.Length)
|
||
|
break;
|
||
|
|
||
|
// If we saw a '$$'
|
||
|
if (replacement [ptr] == '$') {
|
||
|
// Everthing from 'anchor' upto and including the first '$' is copied from the replacement string
|
||
|
AddFromReplacement (anchor, ptr);
|
||
|
// skip over the second '$'.
|
||
|
anchor = ++ptr;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
saveptr = ptr - 1;
|
||
|
|
||
|
int from_match = CompileTerm (ref ptr);
|
||
|
|
||
|
// We couldn't recognize the term following the '$'. Just treat it as a literal.
|
||
|
// 'ptr' has already been advanced, no need to rewind it back
|
||
|
if (from_match >= 0)
|
||
|
continue;
|
||
|
|
||
|
AddFromReplacement (anchor, saveptr);
|
||
|
AddInt (from_match);
|
||
|
anchor = ptr;
|
||
|
}
|
||
|
|
||
|
// If we never needed to advance anchor, it means the result is the whole replacement string.
|
||
|
// We optimize that case by never allocating the pieces array.
|
||
|
if (anchor != 0)
|
||
|
AddFromReplacement (anchor, ptr);
|
||
|
}
|
||
|
|
||
|
private int CompileTerm (ref int ptr)
|
||
|
{
|
||
|
char c = replacement [ptr];
|
||
|
|
||
|
if (Char.IsDigit (c)) { // numbered group
|
||
|
int n = Parser.ParseDecimal (replacement, ref ptr);
|
||
|
if (n < 0 || n > regex.GroupCount)
|
||
|
return 0;
|
||
|
|
||
|
return -n - 4;
|
||
|
}
|
||
|
|
||
|
++ ptr;
|
||
|
|
||
|
switch (c) {
|
||
|
case '{': { // named group
|
||
|
string name;
|
||
|
int n = -1;
|
||
|
|
||
|
try {
|
||
|
// The parser is written such that there are few explicit range checks
|
||
|
// and depends on 'IndexOutOfRangeException' being thrown.
|
||
|
|
||
|
if (Char.IsDigit (replacement [ptr])) {
|
||
|
n = Parser.ParseDecimal (replacement, ref ptr);
|
||
|
name = "";
|
||
|
} else {
|
||
|
name = Parser.ParseName (replacement, ref ptr);
|
||
|
}
|
||
|
} catch (IndexOutOfRangeException) {
|
||
|
ptr = replacement.Length;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (ptr == replacement.Length || replacement[ptr] != '}' || name == null)
|
||
|
return 0;
|
||
|
++ptr; // Swallow the '}'
|
||
|
|
||
|
if (name != "")
|
||
|
n = regex.GroupNumberFromName (name);
|
||
|
|
||
|
if (n < 0 || n > regex.GroupCount)
|
||
|
return 0;
|
||
|
|
||
|
return -n - 4;
|
||
|
}
|
||
|
|
||
|
case '&': // entire match. Value should be same as $0
|
||
|
return -4;
|
||
|
|
||
|
case '`': // text before match
|
||
|
return -2;
|
||
|
|
||
|
case '\'': // text after match
|
||
|
return -3;
|
||
|
|
||
|
case '+': // last group
|
||
|
return -regex.GroupCount - 4;
|
||
|
|
||
|
case '_': // entire text
|
||
|
return -1;
|
||
|
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Regex regex;
|
||
|
int n_pieces;
|
||
|
private int [] pieces;
|
||
|
string replacement;
|
||
|
}
|
||
|
}
|