2009-03-11 5 views
7

Mi piacerebbe scrivere un metodo di estensione per la classe .NET String. Mi piacerebbe che fosse una varazione speciale per il metodo Split, uno che accetta un carattere di escape per evitare di dividere la stringa quando viene utilizzato un carattere di escape prima del separatore.C# Extension Method - String Split che accetta anche un carattere di escape

Qual è il modo migliore per scrivere questo? Sono curioso del miglior modo non regex di avvicinarlo.
Qualcosa con una firma come ...

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

UPDATE: Perché è venuto in uno dei commenti, la fuga ...

In C#, quando la fuga non speciale caratteri si ottiene l'errore - CS1009: sequenza di escape non riconosciuta.

In IE JScript i caratteri di escape vengono espulsi. A meno che tu non provi e ottieni un errore "Digitazione esadecimale prevista". Ho provato Firefox e ha lo stesso comportamento.

Mi piacerebbe che questo metodo fosse piuttosto indulgente e seguisse il modello JavaScript. Se fuggi su un non-separatore dovrebbe semplicemente "gentilmente" rimuovere il carattere di escape.

risposta

12

ne dite:

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

Sembra funzionare (con alcune stringhe di prova rapide), ma non rimuove il carattere di escape, che dipenderà dalla tua esatta situazione, sospetto.

+0

Sembra che tu stia partendo dal presupposto che ogni volta che compare il carattere di escape è seguito dalla stringa di separazione. Cosa succede se non lo è? – tvanfosson

+0

Sto andando solo su cosa c'è nella domanda - se il carattere di escape appare prima del separatore, dovrebbe impedire che quel separatore venga usato per la divisione. Non cerco di rimuovere il carattere di escape o di elaborarlo in altro modo. Ingenua, forse, ma questa è tutta l'informazione che abbiamo. –

+0

cool, qual è il vantaggio di ienumberable rispetto alla restituzione di un array di stringhe? – rizzle

7

Questo dovrà essere ripulito un po ', ma questo è essenzialmente ....

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 non è corretta, è necessario restituire un array di stringhe

estensioni Warnig mai usato , quindi perdonatemi su alcuni errori;)

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

bel pescato. Vado a correggerlo nella domanda originale. – BuddyJoe

4

La mia prima osservazione è che il separatore dovrebbe essere un char e non una stringa, poiché sfuggire a una stringa usando un singolo carattere può essere difficile: quanta parte della stringa seguente ricopre il carattere di escape? A parte questo, la risposta di @James Curran è più o meno come la gestirò, anche se, come dice lui, ha bisogno di essere ripulito. Inizializzazione da j a 0 nell'inizializzatore di loop, ad esempio. Capire come gestire input nulli, ecc.

Probabilmente si desidera anche supportare StringSplitOptions e specificare se la stringa vuota deve essere restituita nella raccolta.

+0

+1 Tutti i punti positivi – BuddyJoe

1

Personalmente mi piacerebbe barare e avere uno sguardo a String.split utilizzando riflettore ... InternalSplitOmitEmptyEntries sembra utile ;-)

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

Probabilmente il miglior modo di farlo, ma è un'altra alternativa. Fondamentalmente, ovunque si trovi la sequenza di escape + separatore, sostituiscilo con un GUID (puoi usare qualsiasi altra merda casuale qui, non importa). Quindi utilizzare la funzione di divisione integrata. Quindi sostituire il guid in ciascun elemento dell'array con il separatore di fuga +.

+0

Dopo la chiamata split, non si sostituisce g solo con il separatore e non si include la fuga? Ciò ti farebbe risparmiare la fatica di dover rimuovere l'escape dalla stringa restituita. – rjrapson

+2

Questo è il classico pattern "segnaposto". Mi piace l'uso del GUID come segnaposto. Direi che questo è abbastanza buono per il codice "hobby", ma non per il codice "Global Thermonuclear War". – BuddyJoe

+0

@rjrapson: buon punto. Immagino dipenda da cosa l'OP voleva. Immagino che tu possa estendere questo metodo per prendere un bool, includendo o meno il carattere di [email protected]: l'unico vero problema che vedo con questo approccio è che un Guid include un "-" che può essere il separatore. – BFree

4

Ecco la soluzione se si desidera rimuovere il carattere di 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

Si può provare qualcosa di simile. Sebbene, suggerirei l'implementazione con codice non sicuro per le attività critiche per le prestazioni.

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

due dei tuoi sovraccarichi prendono il conteggio ma non è utilizzato – innominate227

1

Ho avuto anche questo problema e non ho trovato una soluzione. Così ho scritto un tale metodo me stesso:

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 in combinazione con fuga e Unescape, che sfugge il separatore e la fuga carattere e rimuove caratteri di escape ancora:

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

Esempi di fuga/unescape

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

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

Split:

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

Quindi usarlo, Escape before Join, e Unescape dopo Split.