使用正则表达式提取标记名称和值


Using regex to extract tag names and values

我希望能够提取查询的标签名称和值。

给定以下查询:

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)

我该怎么做?regex是最好的方法吗?我还能怎么解决这个问题?

UPDATE修复了预期输出中的一个错误

不可能像您那样用一个正则表达式完全解析所有内容,因为您对所有对(键,值)没有相同的规则。例如,标签author中间可以使用右括号,但标签title中间不能使用右括号。单引号可以出现在title中间,但不能出现在author中间,等等。因此,即使您的规则在大多数情况下都有效,您的第二个捕获组也不能正确定义。

改进解决方案的一种方法是为每个标记使用不同的正则表达式。然后你可以这样做:

$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";
  }
}

但是,请注意此解决方案并非万无一失。例如,如果书名包含字符串author: 'JOHN',就会出现问题。

在我看来,避免这种问题的最好方法是为您的输入字符串定义一个语法规则,并拒绝所有不符合您规则的字符串。嗯,我想这也取决于你的要求和你的申请。


编辑

注意标签值可以包含在'..,"……"或(……)。不管用哪个

在这种情况下,你的问题仍然是

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

是不正确的。相反,您希望每对分隔符都匹配。在subpattern中有一个选项

(?|''(?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