Эмуляция лексоподобной функциональности в Perl или Python
Вот сделка. Есть ли способ, чтобы строки токенов в строке основывались на нескольких регулярных выражениях?
Один пример:
Я должен получить все теги href, соответствующий им текст и некоторый другой текст, основанный на другом регулярном выражении. Итак, у меня есть 3 выражения, и я хотел бы токенизировать строку и извлечь токены текста, соответствующие каждому выражению.
Я на самом деле сделал это, используя flex (не путать с Adobe), который является реализацией старого доброго lex. lex предоставляет элегантный способ сделать это, выполняя "действия" на основе выражений. Можно также контролировать способ чтения файла lex (чтение на основе блоков / строк).
Проблема в том, что flex на самом деле создает код C/ C++, который фактически выполняет работу по токенизации. У меня есть файл make, который оборачивает все эти вещи. Мне было интересно, может ли Perl / Python каким-то образом сделать то же самое. Просто я хотел бы делать все, что мне нравится, на одном языке программирования.
Токенизация - это только одна из вещей, которые я хочу сделать в рамках своего приложения.
Кроме perl или python может ли это сделать любой язык (в том числе и функциональный)?
Я читал о PLY и ANTLR здесь ( разбор, где я могу узнать об этом).
Но есть ли способ сделать это естественно в самом Python? простите за мое невежество, но используются ли эти инструменты в каких-либо популярных продуктах / услугах?
Спасибо.
8 ответов
Если вы специально разбираете ссылки с веб-страниц, то модуль Perl WWW:: Mechanize очень изящно разберется с вами. Вот пример программы, которая захватывает первую страницу переполнения стека и анализирует все ссылки, печатая их текст и соответствующие URL:
#!/usr/bin/perl
use strict;
use warnings;
use WWW::Mechanize;
my $mech = WWW::Mechanize->new;
$mech->get("http://stackru.com/");
$mech->success or die "Oh no! Couldn't fetch stackru.com";
foreach my $link ($mech->links) {
print "* [",$link->text, "] points to ", $link->url, "\n";
}
В основном цикле каждый $link
является объектом WWW:: Mechanize:: Link, так что вы не просто обязаны получать текст и URL.
Всего наилучшего,
Павел
Посмотрите документацию для следующих модулей на CPAN
а также
Я использовал эти модули для обработки довольно больших и сложных веб-страниц.
Похоже, вы действительно просто хотите разобрать HTML, для этого я рекомендую взглянуть на любой из замечательных пакетов:
Или же! Вы можете использовать парсер, например, один из следующих:
- Pyparsing
- DParser - анализатор GLR с хорошими привязками Python.
- 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>]
Вы смотрели на 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', '!']
Также проверьте 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 !
Возможно, слишком большой молоток, чтобы расколоть эту гайку;-)
Если ваша проблема имеет какое-либо отношение к веб-очистке, я рекомендую взглянуть на Web:: Scraper, который обеспечивает легкий выбор элементов с помощью XPath и CSS-селекторов. У меня есть (немецкий) доклад о Web:: Scraper, но если вы проведете его через babelfish или просто посмотрите на примеры кода, это поможет вам получить краткий обзор синтаксиса.
Ручной анализ HTML является обременительным и не даст вам слишком много, используя один из готовых HTML-парсеров. Если ваш HTML имеет очень ограниченные вариации, вы можете обойтись с помощью умных регулярных выражений, но если вы уже пробуете инструменты для синтаксического анализа, это звучит так, как будто ваш HTML гораздо более регулярный, чем тот, который нормален для анализа регулярные выражения.
Из perlop:
Полезная идиома для lex -подобных сканеров
/\G.../gc
, Вы можете комбинировать несколько регулярных выражений, например, для обработки строки по частям, выполняя различные действия в зависимости от того, какое регулярное выражение сопоставлено. Каждое регулярное выражение пытается найти совпадение с предыдущим.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"; }
Изменение примера Бруно для включения проверки ошибок:
my $input = "...";
while (1) {
if ($input =~ /\G(\w+)/gc) { print "word: '$1'\n"; next }
if ($input =~ /\G(\s+)/gc) { print "whitespace: '$1'\n"; next }
if ($input !~ /\G\z/gc) { print "tokenizing error at character " . pos($input) . "\n" }
print "done!\n"; last;
}
(Обратите внимание, что использование scalar //g, к сожалению, единственное место, где вы действительно не можете избежать использования переменных $1 и т. Д.)