2010-10-26 151 views
1

我有一個文本字段,接受用戶輸入的字符串的形式列表的形式。我有兩個主要的分隔符,一個空格和一個逗號。c#正則表達式列表解析

如果列表中的項目包含多個單詞,用戶可以通過將其用引號引起來對其進行定製。

樣品輸入:

Apple, Banana Cat, "Dog starts with a D" Elephant Fox "G is tough", "House" 

所需的輸出:

Apple 
Banana 
Cat 
Dog starts with a D 
Elephant 
Fox 
G is a tough one 
House 

我一直努力讓一個正則表達式這一點,我無法弄清楚如何讓逗號。以下是我迄今爲止:

Regex.Matches(input, @"(?<match>\w+)|\""(?<match>[\w\s]*)""") 
      .Cast<Match>() 
      .Select(m => m.Groups["match"].Value.Replace("\"", "")) 
      .Where(x => x != "") 
      .Distinct() 
      .ToList() 

回答

2

這正則表達式是相當聰明的,如果它可以把"G is tough"G is a tough one :-)

在一個更嚴重的是,碼了一個解析器和不要試圖依靠一個奇異的正則表達式來爲你做這件事。

你會發現你瞭解更多,代碼將更具可讀性,而且你不會有,你甚至還沒有想通了邊緣的情況下擔心自己還沒有,如:

Apple, Banana Cat, "Dog, not elephant, starts with a D" Elephant Fox 

針對這種情況一個簡單的解析器是:

state = whitespace 
word = "" 
for each character in (string + " "): 
    if state is whitespace: 
     if character is not whitespace: 
      word = character 
      state = inword 
    else: 
     if character is whitespace: 
      process word 
      word = "" 
      state = whitespace 
     else: 
      word = word + character 

,它是相對容易的添加對引用的支持:

state = whitespace 
quote = no 
word = "" 
for each character in (string + " "): 
    if state is whitespace: 
     if character is not whitespace: 
      word = character 
      state = inword 
    else: 
     if character is whitespace and quote is no: 
      process word 
      word = "" 
      state = whitespace 
     else: 
      if character is quote: 
       quote = not quote 
      else: 
       word = word + character 

請注意,我沒有對這些進行徹底的測試,但是我在過去做了很多,所以我很自信。這只是一個很短的步驟,也可以允許轉義(例如,如果您想要在引號內使用引號(如"The \" character is inside"))。

要獲得能夠處理多個分離的單個正則表達式是不是努力,得到它的監控狀態,比如當你在引號內,這樣你就可以區別對待分離,是另一個層次。

+0

感謝您的支持。我基本上希望不必寫一個解析器。我肯定認爲你是正確的,但需要做到這一點。看起來像很好的僞代碼。我非常擅長編寫解析器,我只是希望能夠使用正則表達式。再次感謝。 – Mark 2010-10-26 04:29:48

+0

@Mark,我會認真考慮使用正則表達式來獲得下一個項目,然後按照這個數量減少項目列表,例如:(1)去掉'^ [,] *',如果字符串空; (2)如果下一個字符是''',得到'^「[^」] *「'並且移除''''然後去掉那個長度並返回1;(3)得到'^ [^,] * [,]',刪除結尾字符,去掉這個長度並回到1。這可能會大大簡化解析器。 – paxdiablo 2010-10-26 10:50:57

0

您應該選擇使用空格還是逗號作爲分隔符。使用兩者都有點令人困惑。如果這個選擇不是你的選擇,我會首先在引號之間抓取東西。當它們消失時,您可以用空格替換所有逗號並將空行分割。

0

你可以執行兩個正則表達式。第一個匹配引用的部分,然後刪除它們。有了第二個正則表達式,你可以匹配剩餘的單詞。

string pat = "\"(.*?)\"", pat2 = "(\\w+)"; 
string x = "Apple, Banana Cat, \"Dog starts with a D\" Elephant Fox \"G is tough\", \"House\""; 

IEnumerable<Match> combined = Regex.Matches(Regex.Replace(x, pat, ""), pat2).OfType<Match>().Union(Regex.Matches(x, pat).OfType<Match>()).Where(m => m.Success); 

foreach (Match m in combined) 
    Console.WriteLine(m.Groups[1].ToString()); 

讓我知道如果這不是你在找什麼。

+0

喜歡簡單,但順序搞砸了,我認爲這是對這樣的事情的要求。 – 2010-10-26 09:50:27

0

我喜歡paxdiablo的解析器,但是如果您想使用單個正則表達式,那麼請考慮我的修改版本CSV regex parser

第1步:原

string regex = "((?<field>[^\",\\r\\n]+)|\"(?<field>([^\"]|\"\")+)\")(,|(?<rowbreak>\\r\\n|\\n|$))"; 

第2步:使用多個分隔符

char quoter = '"';  // quotation mark 
string delimiter = " ,"; // either space or comma 
string regex = string.Format("((?<field>[^\\r\\n{1}{0}]*)|[{1}](?<field>([^{1}]|[{1}][{1}])*)[{1}])([{0}]|(?<rowbreak>\\r\\n|\\n|$))", delimiter, quoter); 

使用一個簡單的循環測試:

Regex re = new Regex(regex); 
foreach (Match m in re.Matches(input)) 
{ 
    string field = m.Result("${field}").Replace("\"\"", "\"").Trim(); 
    // string rowbreak = m.Result("${rowbreak}"); 
    if (field != string.Empty) 
    { 
     // Print(field); 
    } 
} 

我們得到的輸出:

Apple 
Banana 
Cat 
Dog starts with a D 
Elephant 
Fox 
G is tough 
House 

就是這樣!

查看原始CSV regex parser,瞭解如何處理匹配的正則表達式數據。你可能需要稍微修改它,但你會明白。

只是爲了感興趣,如果你足夠瘋狂,想要使用多個字符作爲單個分隔符,則考慮this answer