使用Perl 6从.bib文件中提取

问题描述 投票:16回答:1

在LaTeX中写论文时,我有这个.bib file用于参考管理:

@article{garg2017patch,
  title={Patch testing in patients with suspected cosmetic dermatitis: A retrospective study},
  author={Garg, Taru and Agarwal, Soumya and Chander, Ram and Singh, Aashim and Yadav, Pravesh},
  journal={Journal of Cosmetic Dermatology},
  year={2017},
  publisher={Wiley Online Library}
}

@article{hauso2008neuroendocrine,
  title={Neuroendocrine tumor epidemiology},
  author={Hauso, Oyvind and Gustafsson, Bjorn I and Kidd, Mark and Waldum, Helge L and Drozdov, Ignat and Chan, Anthony KC and Modlin, Irvin M},
  journal={Cancer},
  volume={113},
  number={10},
  pages={2655--2664},
  year={2008},
  publisher={Wiley Online Library}
}

@article{siperstein1997laparoscopic,
  title={Laparoscopic thermal ablation of hepatic neuroendocrine tumor metastases},
  author={Siperstein, Allan E and Rogers, Stanley J and Hansen, Paul D and Gitomirsky, Alexis},
  journal={Surgery},
  volume={122},
  number={6},
  pages={1147--1155},
  year={1997},
  publisher={Elsevier}
}

如果有人想知道什么是bib文件,你可以找到详细的here

我想用Perl 6解析这个以提取密钥和标题,如下所示:

garg2017patch: Patch testing in patients with suspected cosmetic dermatitis: A retrospective study

hauso2008neuroendocrine: Neuroendocrine tumor epidemiology

siperstein1997laparoscopic: Laparoscopic thermal ablation of hepatic neuroendocrine tumor metastases

你可以帮助我这样做,可能有两种方式:

  1. 使用基本的Perl 6
  2. 使用Perl 6语法
latex perl6 tex bibtex raku
1个回答
29
投票

这个答案旨在成为两者:

  • 一般性回答“我想用Perl解析X.任何人都可以帮忙吗?”
  • 一个完整而详细的答案,完全像@Suman所要求的那样。

In a single statement (power user)

"$_[0]: $_[1]\n" .put
  for (slurp 'derm.bib')
    ~~ m:g/ '@article{' (<-[,]>+) ',' \s+ 'title={' ~ '}' (<-[}]>+) /

(Qazxswpoi。)

我决定从熟悉P6的开发人员开始写几分钟就可以完成你在问题中指定的简单任务,如果他们不太关心新手的可读性。

我不打算提供解释。它只是做到了这一点。如果你是P6新手,它可能是压倒性的。如果是这样,请阅读我的其余答案 - 它需要更慢并且有全面的评论。也许回到这里看看其余的阅读后是否更有意义。

A "basic Perl 6" solution

Run this code at glot.io

这几乎与“单一语句(高级用户)”代码完全相同 - 分为四个语句而不是一个语句。我可以让它更紧密地复制代码的第一个版本,但我做了一些改变,我将解释。我这样做是为了更清楚地表明P6故意将其功能作为可扩展且可重构的连续体,因此可以混合使用,并且呃匹配最适合给定用例的任何功能。

my \input      = slurp 'derm.bib' ;

my \pattern    = rule { '@article{'       ( <-[,]>+ ) ','
                          'title={' ~ '}' ( <-[}]>+ ) }

my \articles   = input.match: pattern, :global ;

for articles -> $/ { print "$0: $1\n\n" }

Perls以他们的印记而闻名。在P6中,如果你不需要它们,你可以“削减”它们。 Perls也以简洁的做事方式而闻名。 my \input = slurp 'derm.bib' ; 一次性完整地读取文件。

slurp

Perl 6模式通常称为regex或my \pattern = rule { '@article{' ( <-[,]>+ ) ',' 'title={' ~ '}' ( <-[}]>+ ) } 。有几种类型的正则表达式/规则。模式语言是一样的;不同类型只是指示匹配引擎修改它处理给定模式的方式。

一个正则表达式/规则类型是P6相当于经典正则表达式。这些是用Rules/.../声明的。开放的“超级用户”代码中的正则表达式是这些正则表达式之一。它们的区别在于它们在必要时回溯,就像经典正则表达式一样。

没有必要回溯以匹配regex {...}格式。除非您需要回溯,否则最好考虑使用其他规则类型之一。我已切换到使用关键字.bib声明的规则。

rule声明的规则与用rule(或regex)声明的规则相同,除了A)它不回溯和B)它将其模式中的空格解释为对应于输入中的可能空格。你是否发现我在/.../之前立刻放弃了\s+?那是因为'title={'会自动处理这个问题。

另一个区别是我写道:

rule

代替:

'title={' ~ '}' ( ... )

即在支架之后移动与支架之间的钻头匹配的图案,并在支架之间放置一个'title={' ( ... ) '}' 。它们匹配相同的整体模式。我本可以在高级用户~模式中编写任何一种方式,并且在本节的/.../模式中使用任何一种方式。但我希望这一部分更加面向“最佳实践”。我将推迟对这种差异的完整解释以及这种模式的所有其他细节,直到下面的“围兜”语法部分的解释。

rule

此行使用早期“超级用户”版本中使用的my \articles = input.match: pattern, :global ; 例程的方法形式。

m:global相同。我可以用两种版本的方式编写它。

如果要搜索匹配的整个字符串,找到尽可能多的匹配项,而不仅仅是第一个匹配项,则在调用:g方法(或:global例程)时将:g(或.match)添加到参数列表中。然后该方法(或m例程)返回m列表而不是一个。在这种情况下,我们将得到三个,对应于输入文件中的三篇文章。

Match objects

根据for articles -> $/ { print "$0: $1\n\n" } ,“P6 doc on $/是匹配变量......所以通常包含Match类型的对象。”它还提供了一些其他便利,我们在这里利用这些便利之一,如下所述。

$/循环连续地将每个匹配对象(对应于您的示例文件中由语法成功解析的每篇文章)绑定到for块内的符号$/

该模式包含两对括号。这些生成“位置捕获”。整个Match对象通过for(postfix Positional subscripting)提供对其两个Positional捕获的访问。因此,在[]区块内,for$/[0]可以访问给定文章的两个位置捕获。但$/[1]$0也是如此 - 因为标准P6将后面这些符号别名化为$1$/[0]以方便您使用。


还在我这儿?

这个答案的后半部分构建并彻底解释了基于语法的方法。阅读它可以提供对上述解决方案的进一步了解。

但首先...

A "boring" practical answer

我想用Perl 6解析这个。任何人都可以帮忙吗?

P6可能使编写解析器比使用其他工具更乏味。但不那么乏味仍然乏味。 P6解析目前很慢。

在大多数情况下,当你想解析除了最简单的文件格式之外的任何东西时,实际的答案 - 特别是几十​​年前的众所周知的格式 - 是找到并使用现有的解析器。

您可以从$/[1]开始,希望找到一个公开共享的“围兜”解析模块。一个纯粹的Perl 6或一个P6包装在非P6库周围。但在撰写本文时,“围兜”并没有匹配。

几乎可以肯定的是,“bib”解析C库已经可用了。它可能是最快的解决方案。你也可以在自己的P6代码中轻松优雅地使用打包为C lib的外部解析库,即使你不知道C.如果search for 'bib' on modules.perl6.org解释太多或太少,请考虑NativeCall并询问无论您需要或想要什么NativeCall帮助。

如果C lib不适合特定用例,那么您仍然可以通过visiting the freenode IRC channel #perl6使用Perl 5,Python,Ruby,Lua等编写的包。只需安装Perl 5,Python或任何你想要的包;确保它使用其他语言运行;安装适当的语言适配器;然后their Inline::* language adapters包含导出的P6函数,类,对象,值等。

Perl 5适配器是最成熟的,所以我将以此为例。假设您使用Perl 5的Text :: BibTex软件包,现在希望将Perl 6与Perl 5中现有的Text :: BibTeX :: BibFormat模块一起使用。首先,设置Perl 5软件包,因为它们应该是自己的README软件包等。然后,在Perl 6中,写下类似的东西:

use the package and its features as if it were a P6 package

第一行是如何告诉P6您希望加载P5模块。 (除非use Text::BibTeX::BibFormat:from<Perl5>; ... @blocks = $entry.format; 已经安装并正常工作,否则它将无效。但是如果你使用的是流行的Rakudo Perl 6软件包,那应该是。如果没有,你应该至少拥有模块安装程序Inline::Perl5,这样你就可以运行zef了。)

最后一行只是来自zef install Inline::Perl5@blocks = $entry->format;线的机械P6平移。

Creating a P6 grammar / parser

好。足够“无聊”的实用建议。现在让我们尝试为您的问题中的示例创建一个足够好的P6解析器。

the SYNOPSIS of the Perl 5 Text::BibTeX::BibFormat

有了这个语法,我们现在可以编写如下内容:

# use Grammar::Tracer;

grammar bib {

    rule TOP           { <article>* }

    rule article       { '@article{' $<id>=<-[,]>+ ','
                            <kv-pairs>
                         '}'
    }

    rule kv-pairs      { <kv-pair>* % ',' }

    rule kv-pair       { $<key>=\w* '={' ~ '}' $<value>=<-[}]>* }

}

生成与早期“超级用户”和“基本Perl 6”解决方案完全相同的输出 - 但使用语法/解析器方法。

Explanation of 'bib' grammar

die "Maybe use Grammar::Tracer?" unless bib.parsefile: 'derm.bib';

for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" }

如果解析失败,则返回值为# use Grammar::Tracer; 。 P6不会告诉你它到底有多远。你将无法理解你的解析失败的原因。

如果你没有Nil,那么,当你的语法失败时,a better option (?)来帮助调试(如果你还没有安装它,首先安装它)。

use Grammar::Tracer

grammar bib { 关键字就像grammar,但语法不仅可以包含像往常一样命名的classs,还可以包含methods,regexs和tokens。

rule

除非另有说明,否则解析例程首先调用名为 rule TOP { rule(或tokenregexmethod)。

作为一个经验法则,如果你不知道你是否应该使用TOPruleregextoken进行一些解析,请使用method。 (与token模式不同,regexs不会回溯,因此它们可以消除由于回溯导致不必要地缓慢运行的风险。)

但在这种情况下,我使用了token。像rule模式一样,tokens也避免回溯。但另外,它们在模式中的任何原子之后采用空白以自然的方式显着。这通常适用于解析树的顶部。 (令牌和偶尔的正则表达式通常适用于叶子。)

rule

规则末尾的空格意味着语法将匹配输入末尾的任何数量的空格。

rule TOP { <article>* } 在此语法中调用另一个命名规则(或令牌/正则表达式/方法)。

因为看起来每个bib文件应该允许任意数量的文章,我在<article>的末尾添加了*zero or more quantifier)。

<article>*

我有时会排除类似 rule article { '@article{' $<id>=<-[,]>+ ',' <kv-pairs> '}' } 看起来的方式。我试着在这里这样做。

typical input是字符类的P6语法,如传统正则表达式语法中的<[...]>。它更强大,但现在你需要知道的是[...]中的-表示否定,即与<-[,]>语法中的^相同。所以[^,]尝试一个或多个角色的匹配,其中没有一个是<-[,]>+

,告诉P6尝试匹配$<id>=<-[,]>+右边的量化原子(即=位)并将结果存储在当前Match对象中的关键<-[,]>+。后者将挂在解析树的一个分支上;我们将准确到达以后的位置。

'id'

此正则表达式代码说明了几个方便的P6正则表达式功能之一。它表示你想要匹配由逗号分隔的零个或多个 rule kv-pairs { <kv-pair>* % ',' } s。

(更详细地说,kv-pair要求左侧的量化原子的匹配由右侧的原子分开。)

% regex infix operator

这里的新位是 rule kv-pair { $<key>=\w* '={' ~ '}' $<value>=<-[}]>* } 。这是另一个方便的正则表达式功能。 '={' ~ '}'解析分隔结构(在这种情况下,一个用regex Tilde operator开启器和={更接近)与分隔符之间的位与匹配器右侧的量化正则表达式原子匹配。这带来了几个好处,但主要的是错误消息可以更清楚。

An explanation of the parse tree's construction/deconstruction

最后一行(})中的$<article>.<id>等位是指存储在解析树中的Match对象,该对象是从成功的解析生成并返回的。

回到语法的顶部:

for $<article> { say .<id> ~ ':' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" }

如果解析成功,则返回单个“TOP”级别Match对象,即对应于解析树顶部的对象。 (它也可以通过变量 rule TOP { 在解析方法调用之后立即用于代码。)

但是在解析的最终返回发生之前,将生成许多其他Match对象,代表整个解析的子部分,并将其添加到解析树中。将Match对象添加到解析树是通过将单个生成的Match对象或它们的列表分配给“父”Match对象的$/Positional元素来完成的,如下所述。

Associative

rule TOP { <article>* } 这样的规则调用有两个效果。首先,P6尝试匹配规则。其次,如果匹配,P6会生成相应的Match对象并将其添加到解析树中。

如果成功匹配的模式只是<article>而不是<article>,那么只会尝试一次匹配,并且只生成一个值,即单个Match对象,并将其添加到解析树中。

但模式是<article>*,而不仅仅是<article>*。所以P6试图多次匹配<article>规则。如果它至少匹配一次,则它生成并存储一个或多个Match对象的相应列表。 (有关更详细的说明,请参阅article。)

因此,匹配对象列表被分配给TOP级匹配对象的my answer to "How do I access the captures within a match?"键。 (如果匹配的正则表达式只是'article'而不是<article>,那么匹配将导致只有一个Match对象被分配给<article>*键而不是它们的列表。)

所以现在我将尝试解释最后一行代码的'article'部分,它是:

$<article>

for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" } $<article>的缩写。

根据qazxsw poi,“qazxsw poi是匹配变量。它存储最后一个正则表达式匹配的结果,因此通常包含Match类型的对象。”。

在我们的案例中,最后一个正则表达式匹配是来自$/.<article>语法的P6 doc on $/规则。

所以$/是解析返回的TOP级Match对象的TOP键下的值。此值是3个“文章”级别匹配对象的列表。

bib

$<article>正则表达式依次包含任务左侧的'article'。这对应于将Match对象分配给添加到文章级别Match对象的新 rule article { '@article{' $<id>=<-[,]>+ ',' 键。

希望这已经足够了(也许太多了!)我现在可以解释最后一行代码了,它再一次是:

article

$<id>迭代在解析期间生成的3个Match对象(对应于输入中的3篇文章)的列表,并存储在TOP级Match对象的'id'键下。

(此迭代自动将这三个子Match对象中的每一个分配给for $<article> { say .<id> ~ ': ' ~ .<kv-pairs><kv-pair>[0]<value> ~ "\n" } ,又称为“it”或“the topic”,然后,在每次赋值后,执行块中的代码(for)。块中的代码通常会引用,无论是明示还是暗示,都与'article'合作。)

块中的$_位等同于{ ... },即它隐含地指$_。正如刚刚解释的那样,.<id>$_.<id>级别匹配对象,这次正在围绕$_循环进行处理。 $_位表示article返回存储在for级别Match对象的<id>键下的Match对象。

最后,.<id>位引用存储在Match对象的'id'键下的Match对象,该对象存储为Match对象列表的第一个(第0个)元素,该对象存储在对应于article规则的.<kv-pairs><kv-pair>[0]<value>规则的'value'键下。存储在kv-pair级别Match对象的kv-pairs键下。

唷!

When the automatically generated parse tree isn't what you want

好像以上所有都不够,我还需要提一件事。

解析树强烈反映了语法的隐式树结构。但是,由于解析而获得此结构有时不方便 - 可能需要不同的树结构,可能是更简单的树,也许是一些非树数据结构。

当自动结果不合适时,用于从解析中准确生成所需内容的主要机制是使用'kv-pairs'。 (这可以在规则内部的代码块中使用,也可以在与语法分开的article中使用。)

反过来,make的主要用例是生成挂在解析树上的Action classes节点。

最后,这些稀疏树的主要用例是存储AST。

© www.soinside.com 2019 - 2024. All rights reserved.