【问题标题】:C# Extension Method - String Split that also accepts an Escape CharacterC# 扩展方法 - 也接受转义字符的字符串拆分
【发布时间】:2009-03-11 14:36:39
【问题描述】:

我想为 .NET String 类编写一个扩展方法。我希望它是 Split 方法的一个特殊变体——当在分隔符之前使用转义字符时,它采用转义字符来防止拆分字符串。

写这个的最好方法是什么?我很好奇处理它的最佳非正则表达式方法。
带有签名的东西...

public static string[] Split(this string input, string separator, char escapeCharacter)
{
   // ...
}

更新: 因为它在一个 cmets 中出现,逃跑的......

在 C# 中转义非特殊字符时会出现错误 - CS1009: Unrecognized escape sequence。

在 IE JScript 中,转义字符被丢弃。除非你尝试 \u 然后你得到一个“预期的十六进制数字”错误。我测试了 Firefox,它具有相同的行为。

我希望这种方法非常宽容并遵循 JavaScript 模型。如果您在非分隔符上转义,则应该“友好地”删除转义字符。

【问题讨论】:

标签: c# .net parsing extension-methods split


【解决方案1】:

怎么样:

public static IEnumerable<string> Split(this string input, 
                                        string separator,
                                        char escapeCharacter)
{
    int startOfSegment = 0;
    int index = 0;
    while (index < input.Length)
    {
        index = input.IndexOf(separator, index);
        if (index > 0 && input[index-1] == escapeCharacter)
        {
            index += separator.Length;
            continue;
        }
        if (index == -1)
        {
            break;
        }
        yield return input.Substring(startOfSegment, index-startOfSegment);
        index += separator.Length;
        startOfSegment = index;
    }
    yield return input.Substring(startOfSegment);
}

这似乎可行(使用一些快速测试字符串),但它不会删除转义字符 - 我怀疑这将取决于您的具体情况。

【讨论】:

  • 看起来你假设只要出现转义字符,它就会跟着分隔符字符串。如果不是呢?
  • 我只讨论问题中的内容 - 如果转义字符出现在分隔符之前,它应该防止该分隔符用于拆分。我不会尝试删除转义字符或以任何其他方式处理它。也许天真,但这就是我们所掌握的所有信息。
  • 酷,ienumberable 比返回字符串数组有什么好处?
  • 延迟执行和流式传输 - 我们不需要缓冲所有内容。
  • @Bruno:我会在单独的方法中处理取消转义,特别是如果转义字符不仅仅用于“不要转义分隔符”。它可以非常参与。话虽如此,如果转义字符自行逃脱,它可能会变得很棘手。例如"foo\\,bar" 是 "foo\" "bar"
【解决方案2】:

这需要稍微清理一下,但本质上就是这样......

List<string> output = new List<string>();
for(int i=0; i<input.length; ++i)
{
    if (input[i] == separator && (i==0 || input[i-1] != escapeChar))
    {
        output.Add(input.substring(j, i-j);
        j=i;
    }
}

return output.ToArray();

【讨论】:

    【解决方案3】:

    我的第一个观察是分隔符应该是字符而不是字符串,因为使用单个字符转义字符串可能很困难——转义字符覆盖了以下字符串的多少?除此之外,@James Curran 的回答几乎就是我将如何处理它 - 不过,正如他所说,它需要一些清理。例如,在循环初始化程序中将 j 初始化为 0。弄清楚如何处理空输入等。

    您可能还希望支持 StringSplitOptions 并指定是否应在集合中返回空字符串。

    【讨论】:

      【解决方案4】:

      如果您想删除转义字符,这里是解决方案。

      public static IEnumerable<string> Split(this string input, 
                                              string separator, 
                                              char escapeCharacter) {
          string[] splitted = input.Split(new[] { separator });
          StringBuilder sb = null;
      
          foreach (string subString in splitted) {
              if (subString.EndsWith(escapeCharacter.ToString())) {
                  if (sb == null)
                      sb = new StringBuilder();
                  sb.Append(subString, 0, subString.Length - 1);
              } else {
                  if (sb == null)
                      yield return subString;
                  else {
                      sb.Append(subString);
                      yield return sb.ToString();
                      sb = null;
                  }
              }
          }
          if (sb != null)
              yield return sb.ToString();
      }
      

      【讨论】:

        【解决方案5】:

        你可以试试这样的。不过,我建议使用不安全的代码来执行性能关键任务。

        public static class StringExtensions
        {
            public static string[] Split(this string text, char escapeChar, params char[] seperator)
            {
                return Split(text, escapeChar, seperator, int.MaxValue, StringSplitOptions.None);
            }
        
            public static string[] Split(this string text, char escapeChar, char[] seperator, int count)
            {
                return Split(text, escapeChar, seperator, count, StringSplitOptions.None);
            }
        
            public static string[] Split(this string text, char escapeChar, char[] seperator, StringSplitOptions options)
            {
                return Split(text, escapeChar, seperator, int.MaxValue, options);
            }
        
            public static string[] Split(this string text, char escapeChar, char[] seperator, int count, StringSplitOptions options)
            {
                if (text == null)
                {
                    throw new ArgumentNullException("text");
                }
        
                if (text.Length == 0)
                {
                    return new string[0];
                }
        
                var segments = new List<string>();
        
                bool previousCharIsEscape = false;
                var segment = new StringBuilder();
        
                for (int i = 0; i < text.Length; i++)
                {
                    if (previousCharIsEscape)
                    {
                        previousCharIsEscape = false;
        
                        if (seperator.Contains(text[i]))
                        {
                            // Drop the escape character when it escapes a seperator character.
                            segment.Append(text[i]);
                            continue;
                        }
        
                        // Retain the escape character when it escapes any other character.
                        segment.Append(escapeChar);
                        segment.Append(text[i]);
                        continue;
                    }
        
                    if (text[i] == escapeChar)
                    {
                        previousCharIsEscape = true;
                        continue;
                    }
        
                    if (seperator.Contains(text[i]))
                    {
                        if (options != StringSplitOptions.RemoveEmptyEntries || segment.Length != 0)
                        {
                            // Only add empty segments when options allow.
                            segments.Add(segment.ToString());
                        }
        
                        segment = new StringBuilder();
                        continue;
                    }
        
                    segment.Append(text[i]);
                }
        
                if (options != StringSplitOptions.RemoveEmptyEntries || segment.Length != 0)
                {
                    // Only add empty segments when options allow.
                    segments.Add(segment.ToString());
                }
        
                return segments.ToArray();
            }
        }
        

        【讨论】:

        • 你的两个重载计数但没有使用
        【解决方案6】:
        public static string[] Split(this string input, string separator, char escapeCharacter)
        {
            Guid g = Guid.NewGuid();
            input = input.Replace(escapeCharacter.ToString() + separator, g.ToString());
            string[] result = input.Split(new string []{separator}, StringSplitOptions.None);
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = result[i].Replace(g.ToString(), escapeCharacter.ToString() + separator);
            }
        
            return result;
        }
        

        可能不是最好的方法,但它是另一种选择。基本上,无论在哪里找到转义+分隔符的序列,都用 GUID 替换它(你可以在这里使用任何其他随机废话,没关系)。然后使用内置的拆分功能。然后用escape+分隔符替换数组每个元素中的guid。

        【讨论】:

        • 在 split 调用之后,你不会只用分隔符替换 g 而不包括转义吗?这样可以省去您必须从返回的字符串中删除转义符的麻烦。
        • 这是经典的“占位符”模式。我喜欢使用 GUID 作为占位符。我会说这对于“爱好”代码来说已经足够了,但对于“全球热核战争”代码来说就足够了。
        • @rjrapson:好点。我想这取决于OP想要什么。我想您可以扩展此方法以获取布尔值是否包含转义字符。 @Bruno:我看到这种方法的唯一真正问题是 Guid 包含一个可以作为分隔符的“-”。
        【解决方案7】:

        签名不正确,需要返回一个字符串数组

        警告从未使用过扩展,所以请原谅我的一些错误;)

        public static List<String> Split(this string input, string separator, char escapeCharacter)
        {
            String word = "";
            List<String> result = new List<string>();
            for (int i = 0; i < input.Length; i++)
            {
        //can also use switch
                if (input[i] == escapeCharacter)
                {
                    break;
                }
                else if (input[i] == separator)
                {
                    result.Add(word);
                    word = "";
                }
                else
                {
                    word += input[i];    
                }
            }
            return result;
        }
        

        【讨论】:

        • 不错的收获。我会在原来的问题中解决这个问题。
        【解决方案8】:

        我个人会作弊,看看 string.Split 使用反射器...InternalSplitOmitEmptyEntries 看起来很有用 ;-)

        【讨论】:

          【解决方案9】:

          我也遇到了这个问题,没有找到解决办法。于是我自己写了这样一个方法:

              public static IEnumerable<string> Split(
                  this string text, 
                  char separator, 
                  char escapeCharacter)
              {
                  var builder = new StringBuilder(text.Length);
          
                  bool escaped = false;
                  foreach (var ch in text)
                  {
                      if (separator == ch && !escaped)
                      {
                          yield return builder.ToString();
                          builder.Clear();
                      }
                      else
                      {
                          // separator is removed, escape characters are kept
                          builder.Append(ch);
                      }
                      // set escaped for next cycle, 
                      // or reset unless escape character is escaped.
                      escaped = escapeCharacter == ch && !escaped;
                  }
                  yield return builder.ToString();
              }
          

          它与 Escape 和 Unescape 结合使用,将分隔符和转义字符转义并再次删除转义字符:

              public static string Escape(this string text, string controlChars, char escapeCharacter)
              {
                  var builder = new StringBuilder(text.Length + 3);
                  foreach (var ch in text)
                  {
                      if (controlChars.Contains(ch))
                      {
                          builder.Append(escapeCharacter);
                      }
                      builder.Append(ch);
                  }
                  return builder.ToString();
              }
          
              public static string Unescape(string text, char escapeCharacter)
              {
                  var builder = new StringBuilder(text.Length);
                  bool escaped = false;
                  foreach (var ch in text)
                  {
                      escaped = escapeCharacter == ch && !escaped;
                      if (!escaped)
                      {
                          builder.Append(ch);
                      }
                  }
                  return builder.ToString();
              }
          

          escape / unescape 的示例

          separator = ','
          escapeCharacter = '\\'
          //controlCharacters is always separator + escapeCharacter
          
          @"AB,CD\EF\," <=> @"AB\,CD\\EF\\\,"
          

          拆分:

          @"AB,CD\,EF\\,GH\\\,IJ" => [@"AB", @"CD\,EF\\", @"GH\\\,IJ"]
          

          所以要使用它,在 Join 之前 Escape,在 Split 之后 Unescape。

          【讨论】:

            【解决方案10】:
            public string RemoveMultipleDelimiters(string sSingleLine)
            {
                string sMultipleDelimitersLine = "";
                string sMultipleDelimitersLine1 = "";
                int iDelimeterPosition = -1;
                iDelimeterPosition = sSingleLine.IndexOf('>');
                iDelimeterPosition = sSingleLine.IndexOf('>', iDelimeterPosition + 1);
                if (iDelimeterPosition > -1)
                {
                    sMultipleDelimitersLine = sSingleLine.Substring(0, iDelimeterPosition - 1);
                    sMultipleDelimitersLine1 = sSingleLine.Substring(sSingleLine.IndexOf('>', iDelimeterPosition) - 1);
                    sMultipleDelimitersLine1 = sMultipleDelimitersLine1.Replace('>', '*');
                    sSingleLine = sMultipleDelimitersLine + sMultipleDelimitersLine1;
                }
                return sSingleLine;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多