在Perl或者Python中模拟类似lex的功能

时间:2020-03-06 15:00:34  来源:igfitidea点击:

这是交易。有没有办法在基于多个正则表达式的行中标记字符串?

一个例子:

我必须获取所有href标记,其对应的文本以及基于不同正则表达式的一些其他文本。
所以我有3个表达式,想对行进行标记并提取与每个表达式匹配的文本的标记。

实际上,我是使用flex(不要与Adobe混淆)完成此操作的,flex是对旧的lex的一种实现。 lex提供
通过基于表达式执行"操作"来完成此操作的一种优雅方法。一个可以控制的方式
lex也读取文件(基于块/行的读取)。

问题在于flex实际上会产生实际上执行标记化工作的C / C ++代码。我有一个
制作包含所有这些内容的文件。
我想知道perl / python是否可以以某种方式做同样的事情。只是我想做的一切
我喜欢使用单一的编程语言。

标记化只是我作为应用程序的一部分要做的事情之一。

除了perl或者python外,任何语言(也可以起作用)都可以吗?

我确实在这里阅读了有关PLY和ANTLR的信息(解析,在哪里可以了解到它)。

但是有没有办法在python本身中自然地做到这一点?请原谅我的无知,但是这些工具是否在任何流行的产品/服务中使用?

谢谢你。

解决方案

我们看过PyParsing吗?

从他们的主页:

这是一个解析" Hello,World!"的程序。 (或者任何形式为",!"的问候语):

from pyparsing import Word, alphas
greet = Word( alphas ) + "," + Word( alphas ) + "!" # <-- grammar defined here
hello = "Hello, World!"
print hello, "->", greet.parseString( hello )

程序输出以下内容:

Hello, World! -> ['Hello', ',', 'World', '!']

听起来我们真的只是想解析HTML,我建议我们查看以下任何出色的软件包:

  • 美丽汤
  • lxml.html
  • html5lib

或者!我们可以使用以下之一的解析器:

  • Py解析
  • DParser-具有良好python绑定的GLR解析器。
  • ANTLR-可以生成python代码的递归体面的解析器生成器。

此示例来自BeautifulSoup文档:

from BeautifulSoup import BeautifulSoup, SoupStrainer
import re

links = SoupStrainer('a')
[tag for tag in BeautifulSoup(doc, parseOnlyThese=links)]
# [<a href="http://www.bob.com/">success</a>, 
#  <a href="http://www.bob.com/plasma">experiments</a>, 
#  <a href="http://www.boogabooga.net/">BoogaBooga</a>]

linksToBob = SoupStrainer('a', href=re.compile('bob.com/'))
[tag for tag in BeautifulSoup(doc, parseOnlyThese=linksToBob)]
# [<a href="http://www.bob.com/">success</a>, 
#  <a href="http://www.bob.com/plasma">experiments</a>]

查看有关CPAN上以下模块的文档

HTML :: TreeBuilder

HTML :: TableExtract

解析:: RecDescent

我已经使用这些模块来处理非常大和复杂的网页。

如果我们是专门从网页中解析链接之后的,那么Perl的WWW :: Mechanize模块将以非常优雅的方式为我们解决问题。这是一个示例程序,该程序抓取Stack Overflow的第一页并解析出所有链接,并打印其文本和相应的URL:

#!/usr/bin/perl
use strict;
use warnings;
use WWW::Mechanize;

my $mech = WWW::Mechanize->new;

$mech->get("http://stackoverflow.com/");

$mech->success or die "Oh no! Couldn't fetch stackoverflow.com";

foreach my $link ($mech->links) {
    print "* [",$link->text, "] points to ", $link->url, "\n";
}

在主循环中,每个$ link都是WWW :: Mechanize :: Link对象,因此我们不仅限于获取文本和URL。

一切顺利,

保罗

如果问题与网页抓取完全有关,我建议我们查看Web :: Scraper,它可以通过XPath或者CSS选择器轻松选择元素。我在Web :: Scraper上有一个(德语)演讲,但是如果我们通过babelfish运行它,或者只是看代码示例,那可以快速了解语法。

手工解析HTML十分繁琐,并且不会给我们太多使用预制的HTML解析器之一的麻烦。如果HTML的变化非常有限,则可以使用巧妙的正则表达式,但如果我们已经使用了硬核解析器工具,那么听起来HTML的正则性远远超出了理智的解析范围。常用表达。

还要检查一下pQuery,它是做这种事情的一种非常不错的Perlish方法。

use pQuery;

pQuery( 'http://www.perl.com' )->find( 'a' )->each( 
    sub {
        my $pQ = pQuery( $_ ); 
        say $pQ->text, ' -> ', $pQ->toHtml;
    }
);

# prints all HTML anchors on www.perl.com
# =>  link text -> anchor HTML

但是,如果要求超出了HTML / Web的范围,那么这里就是早期的" Hello World!"。 Parse :: RecDescent中的示例...

use strict;
use warnings;
use Parse::RecDescent;

my $grammar = q{
    alpha : /\w+/
    sep   : /,|\s/
    end   : '!'
    greet : alpha sep alpha end { shift @item; return \@item }
};

my $parse = Parse::RecDescent->new( $grammar );
my $hello = "Hello, World!";
print "$hello -> @{ $parse->greet( $hello ) }";

# => Hello, World! -> Hello , World !

可能是太大的锤子太大而无法使螺母破裂;-)

从佩洛普(Perlop):

A useful idiom for lex -like scanners
  is /\G.../gc . You can combine
  several regexps like this to process a
  string part-by-part, doing different
  actions depending on which regexp
  matched. Each regexp tries to match
  where the previous one leaves off.

 LOOP:
    {
      print(" digits"),       redo LOOP if /\G\d+\b[,.;]?\s*/gc;
      print(" lowercase"),    redo LOOP if /\G[a-z]+\b[,.;]?\s*/gc;
      print(" UPPERCASE"),    redo LOOP if /\G[A-Z]+\b[,.;]?\s*/gc;
      print(" Capitalized"),  redo LOOP if /\G[A-Z][a-z]+\b[,.;]?\s*/gc;
      print(" MiXeD"),        redo LOOP if /\G[A-Za-z]+\b[,.;]?\s*/gc;
      print(" alphanumeric"), redo LOOP if /\G[A-Za-z0-9]+\b[,.;]?\s*/gc;
      print(" line-noise"),   redo LOOP if /\G[^A-Za-z0-9]+/gc;
      print ". That's all!\n";
    }

修改Bruno的示例以包括错误检查:

my $input = "...";
while (1) {
    if ($input =~ /\G(\w+)/gc) { print "word: ''\n"; next }
    if ($input =~ /\G(\s+)/gc) { print "whitespace: ''\n"; next }

    if ($input !~ /\G\z/gc)  { print "tokenizing error at character " . pos($input) . "\n" }
    print "done!\n"; last;
}

(注意,不幸的是,使用标量// g是我们确实无法避免使用$ 1等变量的地方。)