2009-09-11 18 views
14

Sto tentando di creare una combinazione generica di formattatore/parser.Parsing stringa formattata

Scenario di esempio:

  • Ho una stringa per string.Format(), ad esempio var format = "{0}-{1}"
  • Ho una matrice di oggetto (stringa) per l'input, ad es. var arr = new[] { "asdf", "qwer" }
  • Sto formattando la matrice utilizzando la stringa di formato, ad es. var res = string.Format(format, arr)

Quello che sto cercando di fare è di riportare indietro la stringa formattata nella matrice di oggetto (stringa). Qualcosa come (pseudo codice):

var arr2 = string.Unformat(format, res) 

// when: res = "asdf-qwer"  
// arr2 should be equal to arr 

Qualcuno ha esperienza nel fare qualcosa del genere? Sto pensando di usare le espressioni regolari (modificare la stringa di formato originale e quindi passarla a Regex.Match per ottenere l'array) ed eseguirla per ogni segnaposto nella stringa di formato. È fattibile o esiste un'altra soluzione più efficiente?

+0

Quanto dura la stringa non formattata? –

+0

@Chris: entro limiti ragionevoli. ATM, sto solo usando questo su nomi di file. –

+0

Nota che con la generalità fornita, i risultati potrebbero essere ambigui, ad es. 'format = '{0} - {1}'' e 'arr = {" as-df "," qw-er "}'. Potrebbe non essere formattato in tre modi diversi. È necessario definire come gestire le ambiguità o limitare il contenuto della stringa di formato e il valore. – peterchen

risposta

9

Non è possibile annullare la formattazione in quanto le informazioni vengono perse. String.Format è un algoritmo "distruttivo", il che significa che non puoi (sempre) tornare indietro.

creare una nuova classe che eredita da string, in cui si aggiunge un membro che tiene traccia del "{0}-{1}" e la { "asdf", "qwer" }, override ToString(), e modificare un po 'il vostro codice.

Se diventa troppo complicato, è sufficiente creare la stessa classe, ma non ereditare da string e modificare un po 'di più il codice.

IMO, questo è il modo migliore per farlo.

+0

Un po 'più di lavoro, ma molto fattibile. –

2

Semplicemente non è possibile nel caso generico. Alcune informazioni saranno "perse" (limiti di stringa) nel metodo Format. Assumere:

String.Format("{0}-{1}", "hello-world", "stack-overflow"); 

Come si "Unformat"?

+0

Buon punto. Che ne dici di creare una soluzione meno generica che presuppone l'assenza di caratteri nel formato nella matrice di oggetti? –

+4

Adrian: In alcuni casi sarebbe anche ambiguo: 'String.Format (" {0} {1} "," 12 "," 3 ")' restituirà "123" ma non puoi dedurre dalla stringa di formato che era "12", "3" o "12", "3" o ... –

+0

Si restituisce una serie di risultati e si lascia che il cliente si occupi di esso. – toddmo

2

Presumendo che "-" non sia nelle stringhe originali, non puoi semplicemente usare Split?

var arr2 = formattedString.Split('-'); 

Si noti che questo si applica solo all'esempio presentato con un'ipotesi. Qualsiasi algoritmo inverso dipende dal tipo di formattazione utilizzata; un'operazione inversa potrebbe anche non essere possibile, come rilevato dalle altre risposte.

+0

Il formato può essere qualsiasi cosa. Ma sì, dovremo essere d'accordo sul fatto che qualsiasi cosa nel formato non dovrebbe apparire sulla matrice che viene formattata. –

+0

Aggiunto qualche chiarimento alla risposta. –

1

Una soluzione semplice potrebbe essere quella

  • sostituire tutti i token formato con (*).
  • fuga tutti gli altri charaters speciali a format
  • fare la partita regex non avido

Questo risolverebbe le ambiguità con la corrispondenza più breve possibile.

(io non sono bravo a RegEx, quindi per favore mi corregga, gente :))

0

Dopo la formattazione, si può mettere la stringa risultante e l'array di oggetti in un dizionario con la stringa come chiave:

Dictionary<string,string []> unFormatLookup = new Dictionary<string,string []> 
... 
var arr = new string [] {"asdf", "qwer" }; 
var res = string.Format(format, arr); 
unFormatLookup.Add(res,arr); 

e nel metodo di Unformat, si può semplicemente passare una stringa e cercare quella stringa e restituire la matrice usata:

string [] Unformat(string res) 
{ 
    string [] arr; 
    unFormatLoopup.TryGetValue(res,out arr); //you can also check the return value of TryGetValue and throw an exception if the input string is not in. 
    return arr; 
} 
14

Mentre i commenti su informazioni perse sono validi, a volte s vuoi solo ottenere i valori stringa di una stringa con formattazione nota.

Un metodo è this blog post scritto da un mio amico. Ha implementato un metodo di estensione chiamato string[] ParseExact(), simile a DateTime.ParseExact(). I dati vengono restituiti come una serie di stringhe, ma se riesci a conviverci, è terribilmente utile.

public static class StringExtensions 
{ 
    public static string[] ParseExact(
     this string data, 
     string format) 
    { 
     return ParseExact(data, format, false); 
    } 

    public static string[] ParseExact(
     this string data, 
     string format, 
     bool ignoreCase) 
    { 
     string[] values; 

     if (TryParseExact(data, format, out values, ignoreCase)) 
      return values; 
     else 
      throw new ArgumentException("Format not compatible with value."); 
    } 

    public static bool TryExtract(
     this string data, 
     string format, 
     out string[] values) 
    { 
     return TryParseExact(data, format, out values, false); 
    } 

    public static bool TryParseExact(
     this string data, 
     string format, 
     out string[] values, 
     bool ignoreCase) 
    { 
     int tokenCount = 0; 
     format = Regex.Escape(format).Replace("\\{", "{"); 

     for (tokenCount = 0; ; tokenCount++) 
     { 
      string token = string.Format("{{{0}}}", tokenCount); 
      if (!format.Contains(token)) break; 
      format = format.Replace(token, 
       string.Format("(?'group{0}'.*)", tokenCount)); 
     } 

     RegexOptions options = 
      ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None; 

     Match match = new Regex(format, options).Match(data); 

     if (tokenCount != (match.Groups.Count - 1)) 
     { 
      values = new string[] { }; 
      return false; 
     } 
     else 
     { 
      values = new string[tokenCount]; 
      for (int index = 0; index < tokenCount; index++) 
       values[index] = 
        match.Groups[string.Format("group{0}", index)].Value; 
      return true; 
     } 
    } 
} 
+0

Cosa viene restituito in questa situazione: '" a-b-c ".ParseExact (" {0} - {1} - {0} ")'? – Zarepheth

+0

Suggerimento: sostituire 'format = format.Replace (token, string.Format (" (? 'Group {0}'. *) ", TokenCount));' con 'format = format.ReplaceFirst (token, string.Format ("(? 'gruppo {0}'. *)", tokenCount)); format = format.Replace (token, string.Format ("\\ {0}", tokenCount)); '. Questo dovrebbe gestire meglio le stringhe di formato che utilizzano i parametri di input più volte. ReplaceFirst proviene da: http://stackoverflow.com/questions/141045/how-do-i-replace-the-first-instance-of-a-string-in-net#141076 – Zarepheth

+0

Non mi piace "abc" .ParseExact ("{0} {1} {2}"), e @ "a $ - \ & * b^c" .ParseExact (@ "{0} $ - \\ & * {1}^{ 2} ") – CRice

Problemi correlati