もう 1 つの方法は、一種の修正されたレクサーを使用して、特定の置換が保証されるテキスト内の個別の領域をそれぞれ分離し、そのブロックで置換が再度実行されないようにマークすることです。
これを行う方法の例を次に示します。
まず、特定の文字列が使用されているかどうかを示すクラスを作成します
public class UsageIndicator
{
public string Value { get; private set; }
public bool IsUsed { get; private set; }
public UsageIndicator(string value, bool isUsed)
{
Value = value;
IsUsed = isUsed;
}
public override string ToString()
{
return Value;
}
}
次に、テキスト内の「トークン」を見つける方法と、見つかった場合の対処方法の両方を表すクラスを定義します。
public class TokenOperation
{
public Regex Pattern { get; private set; }
public Func<string, string> Mutator { get; private set; }
public TokenOperation(string pattern, Func<string, string> mutator)
{
Pattern = new Regex(pattern);
Mutator = mutator;
}
private List<UsageIndicator> ExtractRegions(string source, int index, int length, out int matchedIndex)
{
var result = new List<UsageIndicator>();
var head = source.Substring(0, index);
matchedIndex = 0;
if (head.Length > 0)
{
result.Add(new UsageIndicator(head, false));
matchedIndex = 1;
}
var body = source.Substring(index, length);
body = Mutator(body);
result.Add(new UsageIndicator(body, true));
var tail = source.Substring(index + length);
if (tail.Length > 0)
{
result.Add(new UsageIndicator(tail, false));
}
return result;
}
public void Match(List<UsageIndicator> source)
{
for (var i = 0; i < source.Count; ++i)
{
if (source[i].IsUsed)
{
continue;
}
var value = source[i];
var match = Pattern.Match(value.Value);
if (match.Success)
{
int modifyIBy;
source.RemoveAt(i);
var regions = ExtractRegions(value.Value, match.Index, match.Length, out modifyIBy);
for (var j = 0; j < regions.Count; ++j)
{
source.Insert(i + j, regions[j]);
}
i += modifyIBy;
}
}
}
}
これらのことを処理した後、交換を行うために何かを組み立てるのは非常に簡単です
public class Rewriter
{
private readonly List<TokenOperation> _definitions = new List<TokenOperation>();
public void AddPattern(string pattern, Func<string, string> mutator)
{
_definitions.Add(new TokenOperation(pattern, mutator));
}
public void AddLiteral(string pattern, string replacement)
{
AddPattern(Regex.Escape(pattern), x => replacement);
}
public string Rewrite(string value)
{
var workingValue = new List<UsageIndicator> { new UsageIndicator(value, false) };
foreach (var definition in _definitions)
{
definition.Match(workingValue);
}
return string.Join("", workingValue);
}
}
デモ コード (以下) では、パターンまたはリテラル式を追加する順序が重要であることに注意してください。最初に追加されたものは最初にトークン化されるため、://
URL 内の が顔文字とスラッシュとして選択されるのを防ぐために、最初に画像ブロックを処理します。これは、タグの間に URL が含まれ、次のようにマークされるためです。絵文字ルールがそれを取得しようとする前に使用されます。
class Program
{
static void Main(string[] args)
{
var rewriter = new Rewriter();
rewriter.AddPattern(@"\[img\].*?\[/img\]", x => x.Replace("[img]", "<img src=\"").Replace("[/img]", "\"/>"));
rewriter.AddLiteral(":/", "<img src=\"emote-sigh.png\"/>");
rewriter.AddLiteral(":(", "<img src=\"emote-frown.png\"/>");
rewriter.AddLiteral(":P", "<img src=\"emote-tongue.png\"/>");
const string str = "Stacks be [img]http://example.com/overflowing.png[/img] :/";
Console.WriteLine(rewriter.Rewrite(str));
}
}
サンプルは以下を出力します。
Stacks be <img src="http://example.com/overflowing.png"/> <img src="emote-sigh.png"/>