2009-09-11 14 views
14

Próbuję utworzyć ogólną kombinację formater/parser.Parsowanie sformatowanego ciągu

Przykład scenariusz:

  • mam string.Format łańcuch znaków) (na przykład var format = "{0}-{1}"
  • Mam tablicę obiektu (ciąg) dla danych wejściowych, np. var arr = new[] { "asdf", "qwer" }
  • Formatuję tablicę za pomocą ciągu formatów, np. var res = string.Format(format, arr)

Co staram się zrobić, to wrócić sformatowany ciąg z powrotem do tablicy obiektów (string). Coś jak (pseudo kod):

var arr2 = string.Unformat(format, res) 

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

Ktoś ma doświadczenie w robieniu czegoś takiego? Zastanawiam się nad użyciem wyrażeń regularnych (zmodyfikuj oryginalny ciąg formatu, a następnie przekaż go do Regex.Matches, aby uzyskać tablicę) i uruchom go dla każdego elementu zastępczego w ciągu formatu. Czy jest to możliwe, czy jest jakieś inne bardziej wydajne rozwiązanie?

+0

Jak długo ciąg jest niesformatowany? –

+0

@Chris: w rozsądnych granicach. ATM, używam tego tylko na nazwach plików. –

+0

Należy pamiętać, że podana ogólność wyniki mogą być niejednoznaczne - np. 'format = '{0} - {1}'' i 'arr = {" as-df "," qw-er "}'. Może być niesformatowana na trzy różne sposoby. Musisz zdefiniować sposób radzenia sobie z niejasnościami lub ograniczyć zawartość ciągu formatu i wartości. – peterchen

Odpowiedz

9

Nie można anulować formatowania, ponieważ informacje zostały utracone. String.Format jest algorytmem "destrukcyjnym", co oznacza, że ​​nie można (zawsze) powracać.

Tworzenie nowej klasy dziedziczenie z string, gdzie można dodać element, który śledzi "{0}-{1}" i { "asdf", "qwer" }, zastępują ToString() i modyfikować trochę kodu.

Jeśli jest zbyt skomplikowany, po prostu utwórz tę samą klasę, ale nie dziedzicz z string i zmodyfikuj nieco więcej kodu.

IMO, to najlepszy sposób na zrobienie tego.

+0

Trochę więcej pracy, ale bardzo wykonalne. –

2

Po prostu nie jest to możliwe w przypadku ogólnym. Niektóre informacje zostaną "utracone" (granice ciągów znaków) w metodzie Format. Załóżmy:

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

Jak byś to "sformulował"?

+0

Dobra uwaga. Co powiesz na stworzenie mniejszego niż ogólne rozwiązania, które ma założenie, że nie ma postaci w tym formacie, byłoby obecne w tablicy obiektów? –

+4

Adrian: Byłoby to niejednoznaczne w niektórych przypadkach: 'String.Format (" {0} {1} "," 12 "," 3 ")' zwróci "123", ale nie można wywnioskować z ciągu formatu to było "12", "3" lub "12", "3" lub ... –

+0

Zwróciłbyś tablicę wyników i pozwoliłbyś klientowi sobie z tym poradzić. – toddmo

2

Zakładając, że "-" nie znajduje się w oryginalnych ciągach znaków, czy nie można użyć Splitu?

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

Należy zauważyć, że dotyczy to tylko przedstawionego przykładu z założeniem. Każdy odwrócony algorytm zależy od rodzaju zastosowanego formatowania; operacja odwrotna może nie być możliwa, o czym wspominają inne odpowiedzi.

+0

Format może być dowolny. Ale tak, musimy się zgodzić, że cokolwiek w tym formacie nie powinno się pojawiać w formatowanej macierzy. –

+0

Dodano trochę wyjaśnień do odpowiedzi. –

1

Prostym rozwiązaniem mogłoby być

  • zastąpić wszystkie znaki formującego (. *)
  • ucieczka wszystkie inne specjalne charaters w format
  • sprawiają, że mecz regex non-chciwy

To rozwiązałoby niejednoznaczności do najkrótszego dopasowania.

(nie jestem dobry w RegEx, więc proszę mnie poprawić, ludzie :))

0

Po zakończeniu formatowania, które można umieścić wynikowy ciąg i tablicę obiektów do słownika z ciągiem jako klucz:

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

aw metodzie UNFORMAT, można po prostu przejść ciąg i patrzeć ten ciąg i zwraca tablicę używany:

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

Podczas gdy komentarze na temat utraty danych są ważne, kiedyś s po prostu chcesz uzyskać wartości ciągów znaków o znanym formatowaniu.

Jedna z metod to this blog post napisana przez mojego przyjaciela. Wdrożył metodę rozszerzenia o nazwie string[] ParseExact(), podobną do DateTime.ParseExact(). Dane są zwracane jako tablica łańcuchów, ale jeśli możesz z tym żyć, jest to bardzo przydatne.

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

Co jest zwracane w tej sytuacji: '" a-b-c ".ParseExact (" {0} - {1} - {0} ")"? – Zarepheth

+0

Sugestia - zamień 'format = format.Replace (token, string.Format (" (? 'Grupa {0}'. *) ", TokenCount));' z 'format = format.ReplaceFirst (token, string.Format ("(?" grupa {0} ". *)", tokenCount)); format = format.Replace (token, string.Format ("\\ {0}", tokenCount)); '. Powinno to lepiej obsługiwać ciągi formatów, które wielokrotnie używają parametrów wejściowych. ReplaceFirst pochodzi z: http://stackoverflow.com/questions/141045/how-do-i-replace-the-fi-stance-of-a--net-in-net#141076 – Zarepheth

+0

Nie lubi "abc" .ParseExact ("{0} {1} {2}") i @ "a $ - \ & * b^c" .ParseExact (@ "{0} $ - \\ & * {1}^{ 2} ") – CRice

Powiązane problemy