2016-09-16 151 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

如果你不需要它(':(?:\\\\\“)| [^ \”])'只是变成了'[^ \“]',并且相同的修改适用于其他分隔符 –