2009-03-11 14 views
7

Me gustaría escribir un método de extensión para la clase .NET String. Me gustaría que fuera una variación especial en el método Split, que toma un carácter de escape para evitar la división de la cadena cuando se usa un carácter de escape antes del separador.Método de extensión C# - Separación de cadena que también acepta un carácter de escape

¿Cuál es la mejor manera de escribir esto? Tengo curiosidad sobre la mejor forma no regex de abordarlo.
Algo con una firma como ...

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

ACTUALIZACIÓN: Debido a que surgió en uno de los comentarios, el escape ...

En C# cuando se escapan caracteres no especiales que presentamos lo mejor el error - CS1009: secuencia de escape no reconocida.

En IE JScript los personajes de escape se descartan. A menos que intente \ uy luego obtenga un error de "dígito hexadecimal esperado". Probé Firefox y tiene el mismo comportamiento.

Me gustaría que este método sea bastante indulgente y siga el modelo de JavaScript. Si escapa en un no separador, debe eliminar "amablemente" el carácter de escape.

Respuesta

12

¿Qué tal:

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); 
} 

Parece que funciona (con algunas cadenas de prueba rápidas), pero no elimina el carácter de escape, eso dependerá de su situación exacta, sospecho.

+0

Parece que está asumiendo que cada vez que aparece el carácter de escape es seguido por la cadena de separación. ¿Qué pasa si no es? – tvanfosson

+0

Solo estoy hablando de lo que hay en la pregunta: si el carácter de escape aparece antes del separador, debería evitar que ese separador se utilice para dividir. No intento eliminar el carácter de escape ni procesarlo de ninguna otra manera. Ingenua, tal vez, pero esa es toda la información que tenemos. –

+0

cool, ¿cuál es el beneficio de ienumberable sobre devolver una matriz de cadenas? – rizzle

7

Esto necesita ser limpiado un poco, pero esto es esencialmente ....

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(); 
1

La firma es incorrecta, es necesario devolver una matriz de cadenas

extensiones de warnig nunca utilizado , por lo que me perdone acerca de algunos errores;)

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; 
} 
+0

nice catch. Iré a arreglar eso en la pregunta original. – BuddyJoe

4

Mi primera observación es que el separador debe ser un char, no una cadena, ya que escaparse de una cadena con un solo carácter puede ser difícil. ¿Cuánta de la siguiente cuerda cubre el carácter de escape? Aparte de eso, la respuesta de @James Curran es más o menos cómo lo manejaría, aunque, como él dice, necesita una limpieza. Inicializando j a 0 en el inicializador de bucle, por ejemplo. Averiguar cómo manejar entradas nulas, etc.

Probablemente también desee admitir StringSplitOptions y especificar si se debe devolver una cadena vacía en la colección.

+0

+1 Todos los puntos buenos – BuddyJoe

1

Personalmente me engañar y tener un vistazo a string.split usando reflector ... InternalSplitOmitEmptyEntries parece útil ;-)

3
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; 
} 
Probablemente no

la mejor forma de hacerlo, pero es otra alternativa. Básicamente, en cualquier lugar donde se encuentre la secuencia de escape + separador, reemplácela con un GUID (puede usar cualquier otra mierda aleatoria aquí, no importa). Luego use la función dividida incorporada. Luego reemplace el guid en cada elemento de la matriz con el escape + separador.

+0

Después de la llamada dividida, ¿no reemplazarías g con solo el separador y no incluirías el escape? Eso le ahorrará el problema de tener que eliminar el escape de la cadena devuelta. – rjrapson

+2

Este es el patrón clásico de "marcador de posición". Me gusta el uso del GUID como marcador de posición. Diría que esto es lo suficientemente bueno para el código "hobby", pero no para el código "Global Thermonuclear War". – BuddyJoe

+0

@rjrapson: Buen punto. Supongo que depende de lo que el OP quería. Supongo que puedes extender este método para tomar un bool si incluir o no el carácter de [email protected] Bruno: El único problema real que veo con este enfoque, es que un Guid incluye un "-" que PUEDE ser el separador. – BFree

4

Aquí está la solución si desea eliminar el carácter de escape.

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(); 
} 
0
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; 
} 
3

Usted puede intentar algo como esto. Aunque, sugeriría implementar con código inseguro para tareas críticas de rendimiento.

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(); 
    } 
} 
+0

dos de tus sobrecargas cuentan pero no se usan – innominate227

1

Tuve este problema también y no encontré una solución. Así que escribí un procedimiento de este tipo a mí mismo:

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(); 
    } 

Va en combinación con escape y unescape, que se escapa el separador y el carácter de escape y elimina caracteres de escape de nuevo:

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(); 
    } 

Ejemplos de fuga/unescape

separator = ',' 
escapeCharacter = '\\' 
//controlCharacters is always separator + escapeCharacter 

@"AB,CD\EF\," <=> @"AB\,CD\\EF\\\," 

de Split:

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

Para usarlo, Escape before Join, y Unescape after Split.

Cuestiones relacionadas