2016-09-16 148 views
-1

我希望能夠提取查詢的標籤名稱和值。使用正則表達式來提取標籤名稱和值

考慮以下查詢:

title:(Harry Potter) abc def author:'John' rating:5 jhi cost:"2.20" lmnop qrs 

我希望能夠提取以下信息:

title => Harry Potter 
author => John 
rating => 5 
cost => 2.20 
rest => abc def jhi lmnop qrs 

注意標籤值可以被包含在「..」。「 ...「 要麼 (...)。它的劑量很重要。

此問題已得到解決使用以下:

$query = "..."; // User input 

while (preg_match(
    '@(?P<key>title|author|rating|cost):(?P<value>[^\'"(\s]+)@', 
    $query, 
    $matches 
)) { 
    echo $matches['key'] . " => " . $matches['value']; 
    $query = trim(str_replace($matches[0], '', $query)); 
} 

while (preg_match(
    '@(?P<key>title|author|rating|cost):[\'"(](?P<value>[^\'")]+)[\'")]@', 
    $query, 
    $matches 
)) { 
    echo $matches['key'] . " => " . $matches['value']; 
    $query = trim(str_replace($matches[0], '', $query)); 
} 

現在,這是正常的情況很多。但是,也有相當多的極端案例:

1)例如考慮:

title:(John's) abc 

應該去:

title => John's 
rest => abc 

而是去

title => (John' 
rest => s) abc 

2 )還要考慮:

title: (foo (: bar) 

應該去:

title => foo (: bar 

去:

rest => (foo (bar) 

我怎樣才能做到這一點?正則表達式甚至是最好的方式嗎?我還能如何解決這個問題?

UPDATE修正了一個錯誤的預期產出的一個

+2

你如何定義你的分隔符和一個選項你的逃生/特殊字符?當你說'標題:(John's)abc'應該轉到'title =>(John's)abc'時,這讓我認爲兩個標籤之間的每個字符都是標籤的一部分。然而,當你寫'title:(foo(:bar)'應該到'title => foo(:bar')時,必須刪除突然的括號,所以括號看起來是某種分隔符/分隔符......什麼是規則? –

+0

@ThomasWilmotte對不起我的錯誤,現在就修正它! –

回答

2

這是不可能像你一樣用一個正則表達式正好解析一切,因爲你不必爲所有對同一規則(鍵,值)。事實上,例如,在標記作者的中間可以接受一個左括號,但不在標題的中間。在標題中間接受單引號標記,但不在作者等中間。因此,即使您的規則適用於大多數情況,您的第二個捕獲組也無法正確定義。

改進解決方案的一種方法是對每個標籤使用不同的正則表達式。然後你可以做這樣的事情:

$str = "title:(foo (: bar) abc def ". 
     "author:'John' "    . 
     "rating:5 jhi "    . 
     "cost:\"2.20\""    . 
     "lmnop qrs "; 


$regex = array(
    "title" => "/(?P<key>title):[[:space:]]*\((?P<value>[^\)]*)\)/"  , 
    "author" => "/(?P<key>author):[[:space:]]*'(?P<value>[^']*)'/"   , 
    "rating" => "/(?P<key>rating):[[:space:]]*(?P<value>[\d]+)/"   , 
    "cost" => "/(?P<key>cost):[[:space:]]*\"(?P<value>[\d]+\.[\d]{2})\"/" 
); 

foreach($regex as $k => $r) 
{ 
    if(preg_match($r, $str, $matches)) 
    { 
    echo $matches['key'] . " => " . $matches['value'] . "\n"; 
    } 
    else 
    { 
    echo "Nothing found for " . $k . "\n"; 
    } 
} 

但是,請注意,這種解決方案是不是防彈。例如,如果書的標題包含字符串作者:'JOHN',那麼您將遇到問題。

在我看來,避免這種問題的最好方法是爲輸入字符串定義一個語法規則,並拒絕所有不符合規則的字符串。那麼,這也取決於你的要求和你的應用程序,我猜。


編輯

注意標籤值可以被包含在 '..', 「...」 或(......)。這件事dosent這

在這種情況下,你的問題仍然是

[\'\"\(](?P<value>[^\'\"\)]+)[\'\"\)] 

不正確。相反,你希望每對分隔符匹配。有沒有在子模式爲(參考here

(?|\'(?P<value>[^\']+)\'|\"(?P<value>[^\"]+)+\"|\((?P<value>[^\)]+)\)) 

如果使用\作爲逃生焦炭,代碼變得

$str = 'title:"foo \" bar" abc def '. 
     'author:(Joh\)n) '   . 
     'rating:\'5\\\'4\' jhi '  . 
     'cost:"2.20"'    . 
     'lmnop qrs '; 

$regex = "/(?P<key>title|author|rating|cost):[[:space:]]*" . 
     "(?|" . 
      "\"(?P<value>(?:(?:\\\\\")|[^\"])+)\"" . "|" . // matches "..." 
      "\'(?P<value>(?:(?:\\\\\')|[^\'])+)\'" . "|" . // matches '...' 
      "\((?P<value>(?:(?:\\\\\))|[^\)])+)\)" .  // matches (...) 
     ")/"; // close (?|... 


while(preg_match($regex, $str, $matches)) 
{ 
    echo $matches['key'] . " => " $matches['value'] . "\n"; 
    $str = str_replace($matches[0], '', $str); 
} 

輸出

title => foo \" bar 
author => Joh\)n 
rating => 5\'4 
cost => 2.20 
+0

關鍵值可以包含在一個qoute或括號中,它關鍵在於標記鍵是什麼或是什麼,我編輯了這個問題以使其更清晰 –

+0

是I可能有一個轉義字符「\」,但我在這個問題中沒有提到這個。 –

+1

如果你不需要它(':(?:\\\\\「)| [^ \」])'只是變成了'[^ \「]',並且相同的修改適用於其他分隔符 –