【问题标题】:Multiple String.Replace without interference?多个 String.Replace 无干扰?
【发布时间】:2013-10-08 05:53:56
【问题描述】:

在不替换已替换文本的情况下执行多个String.Replace 的谨慎方法是什么?例如,假设我有这个字符串:

str = "Stacks be [img]http://example.com/overflowing.png[/img] :/";

我编写的正则表达式将匹配 [img]url[/img],并让我将其替换为正确的 HTML <img> 格式。

str = "Stacks be <img src=\"http://example.com/overflowing.png\"/> :/";

之后我执行String.Replace 以用&lt;img&gt; 标签替换表情符号代码(:/:(:P 等)。但是,有意想不到的结果:

预期结果

str = "Stacks be <img src=\"http://example.com/overflowing.png\"/> " + 
    "<img src=\"emote-sigh.png\"/>";

实际(且显而易见)结果

str = "Stacks be <img src=\"http<img src=\"emote-sigh.png"/> " + 
    "/example.com/overflowing.png\"/>" + 
    "<img src=\"emote-sigh.png\"/>";

不幸的是,由于我计划进行大量替换,尝试在单个 Regex 表达式中完成所有操作似乎是不切实际的(尽管我认为这将是最高效的解决方案)。什么是(较慢但)更易于维护的方法?

【问题讨论】:

  • 请显示您正在使用的替换代码
  • 在我看来,迁移到一个实际解析输入文本不同部分的系统会更有用。然后你就会知道什么是图像 URL,什么是文本。然后,您可以在正确的位上执行替换...
  • @JonSkeet 非常同意,以这种方式进行操作也应该会产生最可维护的结果,您所要做的就是指定要匹配的令牌的优先级以及当它们匹配时如何处理它们'被发现而不是担心替换之间的相互作用

标签: c# .net regex replace


【解决方案1】:

不幸的是,由于我计划进行的替换数量众多,尝试在单个 Regex 表达式中完成所有操作似乎是不切实际的(尽管我认为这将是最高效的解决方案)。什么是(较慢但)更易于维护的方法?

可能看起来如此,但事实并非如此。看看this article

tl;dr:Replace 接受委托作为其第二个参数。因此,匹配您要同时替换的所有不同事物的析取模式,并在委托中使用Dictionaryswitch 或类似策略来为当前元素选择正确的替换。

文章中的策略依赖于键是静态字符串;如果键中有正则表达式运算符,则该概念失败。有一种更好的方法,通过将键包装在捕获括号中,您可以测试是否存在适当的捕获组以查看匹配的大括号。

【讨论】:

    【解决方案2】:

    最明显的方法是使用正则表达式来替换您需要的任何文本。简而言之,您可以使用这样的正则表达式::/[^/] 匹配 :/ 但不匹配 ://

    您还可以使用组来了解您匹配的模式,从而让您知道要放置什么。

    【讨论】:

      【解决方案3】:

      另一种选择是使用一种修改后的Lexer 来隔离文本中需要进行特定替换的每个离散区域,并标记该块,以便不再在其中运行替换

      这里有一个例子说明你会如何做到这一点:

      首先,我们将创建一个指示是否使用特定字符串的类

      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"/>
      

      【讨论】:

        【解决方案4】:

        如果您不想使用任何复杂的正则表达式,例如将文本拆分到任何类型的容器中。

        您应该根据在文本中找到的标记进行拆分:在您的情况下,标记是[img] [/img](包括那些[img] 标签)之间的文本,即[img]http://example.com/overflowing.png[/img]

        然后你可以对这些标记应用[img]替换方法,对上述容器中的其余元素应用表情替换方法。然后你只需输出一个包含所有容器元素的字符串。

        在下面填写拆分过程后此类容器的示例内容:

         1. "Stacks be " 
         2. "[img]http://example.com/overflowing.png[/img]" 
         3. " :/" 
        

        对元素 1 和 3 应用表情符号替换,如果是标记元素编号 2,则应用 [img] 替换。

        【讨论】:

          【解决方案5】:

          你可以像下面这样替换

          string.replace( string.replace("[img]","<img src=\""),"[/img]","\"/>")
          

          它应该可以工作。

          【讨论】:

            【解决方案6】:

            这是我旧项目中的代码 sn-p:

            private string Emoticonize(string originalStr)
            {
                StringBuilder RegExString = new StringBuilder(@"(?<=^|\s)(?:");
                foreach (KeyValuePair<string, string> e in Emoticons)
                {
                    RegExString.Append(Regex.Escape(e.Key) + "|");
                }
                RegExString.Replace("|", ")", RegExString.Length - 1, 1);
                RegExString.Append(@"(?=$|\s)");
                MatchCollection EmoticonsMatches = Regex.Matches(originalStr, RegExString.ToString());
            
                RegExString.Clear();
                RegExString.Append(originalStr);
                for (int i = EmoticonsMatches.Count - 1; i >= 0; i--)
                {
                    RegExString.Replace(EmoticonsMatches[i].Value, Emoticons[EmoticonsMatches[i].Value], EmoticonsMatches[i].Index, EmoticonsMatches[i].Length);
                }
            
                return RegExString.ToString();
            }
            

            Emoticons 是一个字典,我将表情符号代码存储为键,并将相应的图像存储为值。

            【讨论】:

              【解决方案7】:
                      string[] emots = { ":/", ":(", ":)" };
                      string[] emotFiles = { "emote-sigh", "emot-sad.png", "emot-happy.png" };
              
                      string replaceEmots(string val)
                      {
                          string res = val;
                          for (int i = 0; i < emots.Length; i++)
                              res = res.Replace(emots[i], "<img src=\"" + emotFiles[i] + ".png\"/>");
                          return res;
                      }
              
                      void button1_click()
                      {
                          string str = "Stacks be <img src=\"http://example.com/overflowing.png\"/> :/";
                          str = replaceEmots(str);
                      }
              

              【讨论】:

                【解决方案8】:

                这是在我的情况下进行替换的代码。输出正是您想要的。

                    str = "Stacks be <img src=\"http://example.com/overflowing.png\"/> :/";
                
                
                        // check if the htmltemplate hold any template then set it or else hide the div data.
                        if (!String.IsNullOrEmpty(str))
                        {
                            divStaticAsset.InnerHtml = str.Replace("[img]", "<img src=\'").
                                                                    Replace("[/img]", "\'/>") + "<img src=\'emote-sigh.png'/>";
                
                        }
                

                【讨论】:

                • 这将输出Stacks be &lt;img src="http://example.com/overflowing.png"/&gt; :/&lt;img src="emote-sigh.png"/&gt;,注意:/出现在第二个&lt;img&gt;标签之前
                • 然后在这个 Replace("[/img]", "\'/>") 中添加 :/
                猜你喜欢
                • 1970-01-01
                • 2013-05-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-10-15
                • 2019-09-25
                • 2022-01-16
                相关资源
                最近更新 更多