不同的正则表达式preg_match_all在实时测试和我的脚本中产生

时间:2018-12-22 12:27:04

标签: php regex string bibtex

我有以下字符串:

{ Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, } 

我的目标是在关联数组中获取这些值。我正在尝试此正则表达式:

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{(.*)\}|\d{4})/

使用preg_match_all,不带其他参数(仅用于正则表达式,输入和输出),但在this之类的在线测试仪上正常运行时,它不会返回.strong中的所有值。脚本,仅其中一些。特别是抽象 author 根本无法匹配。我尝试更改参数(当前使用U(默认情况下为非贪婪匹配),但是它不能解决我的问题。非常感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

从此更改模式:

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{(.*)\}|\d{4})/

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{[^}]+\}|\d{4})/

或在代码中

$s = '{Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, }';
$p = '/(\b[-\w]+)\s*=\s*(\{([^}]+)\}|\d{4})/';

preg_match_all($p, $s, $m);
print_r($m);

Sandbox

这将使您更接近,但是还需要进一步完善。基本上发生的事情是您将第一个{与最后一个}进行匹配,因为.*匹配了任何“贪婪”,这意味着它会消耗所有匹配项。

您可以通过使像这样的\{[^}]+\}而不是原始的\{(.*?)\}变得非贪婪,而获得高于\{(.*)\}的类似结果,但我认为它的含义也不尽人意。

输出

 ...
[1] => Array
    (
        [0] => Author
        [1] => Title
        [2] => Journal
 ...

[2] => Array
    (
        [0] => {Smith, John and James, Paul and Hanks, Tom}
        [1] => {{Some title} //<--- lost }
        [2] => {{Journal name text} //<--- lost }

最简单的方法是在其中添加几个可选的{}\}?,然后至少可以收集完整的标签:

  //note the \{\{? and \}?\}
  $p = '/(\b[-\w]+)\s*=\s*(\{\{?([^}]+)\}?\}|\d{4})/';

这会将2索引更改为此:

[2] => Array
    (
        [0] => {Smith, John and James, Paul and Hanks, Tom}
        [1] => {{Some title}}
        [2] => {{Journal name text}}

但是,由于没有理想结果的例子,这是我所能做到的。

作为一面:

执行此操作的另一种方法(非正则表达式)是修剪{},然后将其展开},,然后在=上循环并爆炸。烦躁的格式。

类似这样的东西:

$s = '{Author = {Smith, John and James, Paul and Hanks, Tom}, Title = {{Some title}}, Journal = {{Journal name text}}, Year = {{2022}}, Volume = {{10}}, Number = {{11}}, Month = {{DEC}}, Abstract = {{Abstract text abstract text, abstract. Abstract text - abstract text? Abstract text! Abstract text abstract text abstract text abstract text abstract text abstract text abstract text abstract text, abstract text. Abstract text abstract text abstract text abstract text abstract text.}}, DOI = {{10.3390/ijms19113496}}, Article-Number = {{1234}}, ISSN = {{1234-5678}}, ORCID-Numbers = {{}}, Unique-ID = {{ISI:1234567890}}, }';

function f($s,$o=[]){$e=array_map(function($v)use(&$o){if(strlen($v))$o[]=preg_split("/\s*=\s*/",$v."}");},explode('},',trim($s,'}{')));return$o;}

print_r(f($s));

输出

Array
(
    [0] => Array
        (
            [0] => Author
            [1] => {Smith, John and James, Paul and Hanks, Tom}
        )

    [1] => Array
        (
            [0] =>  Title
            [1] => {{Some title}}
        )

    [2] => Array
        (
            [0] =>  Journal
            [1] => {{Journal name text}}
        )
   ...

Sandbox

未压缩版本:

/* uncompressed */
function f($s, $o=[]){
    $e = array_map(
        function($v) use (&$o){
            if(strlen($v)) $o[] = preg_split("/\s*=\s*/", $v."}");
        },
        //could use preg_split for more flexibility  '/\s*\}\s*,\s*/`
        explode(
            '},',
            trim($s, '}{')
        )
    );
    return $o;
}

这不是“稳健”的解决方案,但是如果格式始终像示例一样,则可能就足够了。无论如何看起来都很酷。输出格式要好一些,但是您可以array_combine($m[1],$m[2])来修复Regex版本。

您还可以向其提供一个数组并将其附加到数组中,例如:

print_r(f($s,[["foo","{bar}"]]));

输出:

Array
(
[0] => Array
    (
        [0] => foo
        [1] => {bar}
    )

[1] => Array
    (
        [0] => Author
        [1] => {Smith, John and James, Paul and Hanks, Tom}
    )

然后,如果您想要其他格式:

//get an array of keys  ['foo', 'Author']
print_r(array_column($a,0));

//get an array of values ['{bar}', '{Smith, John ...}']
print_r(array_column($a,1));

//get an array with keys=>values ['foo'=>'{bar}', 'Author'=>'{Smith, John ...}']
print_r(array_column($a,1,0));

当然,您可以直接进入函数返回。

无论如何,这很有趣,享受。

更新

正则表达式(\{[^}]+\}|\d{4})的含义是:

  • (...)捕获组,捕获()
  • 中包含的所有匹配项
  • \{从字面上匹配{
  • [^}]+一次或多次匹配非}的任何内容
  • \}从字面上匹配}
  • |
  • \d{4}匹配0-9 4次。

基本上,此(\{(.*)\}而非\{[^}]+\}的问题在于,.*也与}{匹配,并且因为它很贪心(不是结尾{ {1}}(例如?)将匹配所有可能的内容。因此,实际上它将与该\{(.*?)\}匹配,从而匹配第一个fname={foo}, lname={bar}和最后一个{}之间的所有内容。但是,带有“非” {foo}, lname={bar}的正则表达式只能匹配第一个},因为}将不匹配[^}]+中结尾的}foo}代替,从而完成了模式。如果我们使用了另一个\},则它实际上匹配最后一个(.*),并捕获字符串中第一个}和最后一个{之间的所有内容。

关于乐兴的话

对于正则表达式而言,嵌套可能真的很困难。正如我在评论中所说,词法分析器更好。所涉及的不是匹配大的模式,例如:}您匹配的是较小的模式

/([a-zA-Z0-9\-\_]+)\s*=\s*(\{[^}]+\}|\d{4})/

您可以将它们与或放在一起

[
  '(?P<T_WORDS>\w+)', ///matches a-zA-Z0-9_
  '(?P<T_OPEN_BRACKET>\{)', ///matches {
  '(?P<T_CLOSE_BRACKET>\})',  //matches }
  '(?P<T_EQUAL>=)',  //matches =
  '(?P<T_WHITESPACE>\s+)', //matches \r\n\t\s
  '(?P<T_EOF>\Z+)', //matches end of string
];

"(?P<T_WORD>\w+)|(?P<T_OPEN_BRACKET>'{')|(?P<T_CLOSE_BRACKET>'}')|(?P<T_EQUAL>'=')|(?P<T_WHITESPACE)\s+|(?P<T_EOF)\Z+", 是一个命名的捕获组,只是使事情变得简单。不仅仅是像这样的匹配:

(?P<name>..)

您还将拥有这个:

[
   1 => [ 0 => 'Title', 1 => ''],
]

这使得将令牌名称分配回匹配变得更加容易。

无论如何,这个阶段的目标是赌最终(最终)获得一个带有“令牌”或匹配名称(例如)的数组: [ 1 => [ 0 => 'Title', 1 => ''], 'T_WORD' => [ 0 => 'Title', 1 => ''] ]

Title = {{Some title}}

这应该是相当困难的事情,但是主要区别在于,在纯正则表达式中,您无法计算 //token stream [ 'T_WORD' => 'Title', //keyword 'T_WHITESPACE' => ' ', //ignore 'T_EQUAL' => '=', //instruction to end key, 'T_WHITESPACE' => ' ', //ignore 'T_OPEN_BRACKET' => '{', //inc a counter for open brackets 'T_OPEN_BRACKET' => '{', //inc a counter for open brackets 'T_WORD' => 'Some', //capture as value 'T_WHITESPACE' => ' ', //capture as value 'T_WORD' => 'title', //capture as value 'T_CLOSE_BRACKET' => '}', //dec a counter for open brackets 'T_CLOST_BRACKET' => '}', //dec a counter for open brackets ] {,因此您无法验证字符串的语法,它要么匹配,要么不匹配。

使用lexer版本,您可以计算这些事情并采取适当的措施。这是因为您可以迭代令牌是否匹配,然后“测试”字符串。例如,我们可以说这些话:

后跟}的单词是属性名称。 =中一个或两个{中的所有内容必须以与}相同的{结尾,}{中的任何内容都必须以{ {1}}是我们需要的一些“信息”。忽略}对之外的任何空间...等等。它使用了我们需要用来验证此类数据的“粒度”。

之所以提及这一点,是因为即使我给您的示例}也会在诸如此类的字符串上失败

{}

它将在其中返回

的匹配项
/(\b[-\w]+)\s*=\s*(\{\{?([^}]+)\}?\}|\d{4})/

另一个例子是,这将不会引起问题:

 Author = {Smith, John and James, {Paul and Hanks}, Tom}

哪个会给出这样的匹配项:

 Author 
{Smith, John and James, {Paul and Hanks}

这看起来是正确的,但这不是因为Title = {{Some title}, Journal = {{Journal name text}} 缺少Title Some title //and Journal Journal name text 。您如何处理字符串中的无效语法取决于您自己,但是在Regex版本中,我们对此无能为力。我应该提到,即使递归正则表达式(“括号匹配对”)在这里也会失败,返回以下内容:

{{Some title},Journal = {{Journal name text}

但是在词法分析器版本中,我们可以增加一个计数器{{Some title} +1 } +1然后是单词{然后是{ -1,然后剩下1而不是0。因此在我们的代码中,我们知道我们缺少应该在其中的Some title

下面是我写过的词法分析器的一些示例(其中甚至有一个空的)

https://github.com/ArtisticPhoenix/MISC/tree/master/Lexers

实现一个词法分析器(甚至是一个基本的词法分析器)要比单纯的正则表达式解决方案要困难得多,但是将来使用和维护它会更加容易。希望有必要解释匹配分析和词法分析之间的区别。

从本质上讲,复杂的模式很大,所有的复杂性都融入了模式,因此很难更改。对于较小的模式,模式的复杂性是由于其解析方式(您的代码指令)而出现的,这使得调整边缘情况等更加容易。

祝你好运!