Perl XPath с условным условием - это возможно? - PullRequest
4 голосов
/ 31 января 2012

Этот вопрос был перефразирован.Я использую модули CPAN Perl WWW :: Mechanize для навигации по сайту, HTML :: TreeBuilder-XPath для захвата содержимого и xacobeo для проверки моего кода XPath.на HTML / XML.Цель состоит в том, чтобы вызвать этот сценарий Perl с веб-сайта на основе PHP и загрузить извлеченное содержимое в базу данных.Поэтому, если контент «отсутствует», его все равно необходимо учитывать.

Ниже приведен проверенный сокращенный пример кода, изображающий мою задачу.Примечание:

  1. Эта страница заполнена динамически и содержит различные ITEMS, выведенные для разных магазинов;различное количество Products* будет существовать для каждого магазина.И эти списки продуктов могут иметь или не иметь под ними детализированную таблицу.
  2. Захваченные данные должны быть в массивах, и необходимо поддерживать связь любого детализированного списка (если он существует) с перечнем продуктов.,

Ниже пример xml изменяется для магазина (как описано выше), но для краткости я показываю только один «тип» выходных данных.Я понимаю, что все данные могут быть собраны в один массив, а затем регулярное выражение используется для расшифровки контента с целью загрузки его в базу данных.Я ищу более глубокие знания XPath, чтобы помочь упростить это (и будущее) решение (я).

<!DOCTYPE XHTML>
<table id="8jd9c_ITEMS">
<tr><th style="color:red">The Products we have in stock!</th></tr>

<tr><td><span id="Product_NUTS">We have nuts!</span></td></tr>
<tr><td>
    <!--Table may or may not exist  -->
           <table>                                  
      <tr><td style="color:blue;text-indent:10px">Almonds</td></tr>
      <tr><td style="color:blue;text-indent:10px">Cashews</td></tr>
      <tr></tr>
    </table>
</td></tr>

<tr><td><span id="Product_VEGGIES">We have veggies!</span></td></tr>
<tr><td>
    <!--Table may or may not exist -->
    <table>
      <tr><td style="color:blue;text-indent:10px">Carrots</td></tr>
      <tr><td style="color:blue;text-indent:10px">Celery</td></tr>
      <tr></tr>
    </table>
</td></tr>

<tr><td><span id="Product_ALCOHOL">We have booze!</span></td></tr>
    <!--In this case, the table does not exist -->
</table>

Оператор XPath:

'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/span/text()'

найдет:

We have nuts!
we have veggies!
We have booze!

И оператор XPath:

'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/table/tr/td/text()'

найдет:

Almonds
Cashews
Carrots
Celery

Можно объединить два оператора XPath:

'//table[contains(@id, "ITEMS")]/tr[position() >1]/td/span/text() | //table[contains(@id, "ITEMS")]/tr[position() >1]/table/tr/td/text()'

Чтобы найти:

We have nuts!
Almonds
Cashews
We have veggies!
Carrots
Celery
We have booze!

Опять же, вышеупомянутый массив может быть расшифрован (в реальном коде) для его связи продукта с списком с помощью регулярных выражений. Но может ли массив быть построен с использованием XPath таким образом, чтобы сохранить эту связь?

Например (псевдо-речь, это не работает):

'//table[contains(@id, "ITEMS")]/tr[position()>1]/td/span/text() | 
if exists('//table[contains(@id, "ITEMS")]/tr[position() >1]/table)) 
then ("NoTable") else ("TableRef") | 
Save this result into @TableRef ('//table[contains(@id, "ITEMS")]/tr[position() >1]/table/tr/td/text()')'

Невозможно построить многомерные массивы (в традиционном смысле) в Perl, см. perldoc perlref Но, надеюсь, решение, подобное приведенному выше, может создать что-то вроде:

@ITEMS[0] => We have nuts!
@ITEMS[1] => nutsREF     <-- say, the last word of the span value + REF
@ITEMS[2] => We have veggies!
@ITEMS[3] => veggiesREF  <-- say, the last word of the span value + REF
@ITEMS[4] => We have booze!
@ITEMS[5] => NoTable     <-- value accounts for the missing info

@nutsREF[0] => Almonds
@nutsREF[1] => Cashews

@veggiesREF[0] => Carrots
@veggiesREF[1] => Celery 

В реальном коде продукты известны, поэтому my @veggiesREF и my @nutsREF могут быть определены в ожидании вывода XPath.

Я понимаю, что XPath if / else / then работает в версии XPath 2.0,Я нахожусь в системе Ubuntu и работаю локально, но я все еще не ясно, использует ли мой сервер apache2 его или версию 1.0.Как мне это проверить?

Наконец, если вы можете показать, как вызывать скрипт Perl из отправки формы PHP И как передавать массив Perl в вызывающую функцию PHP, то это будет способствовать получению вознаграждения.:)

Спасибо!

ЗАКЛЮЧИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ:

Комментарии непосредственно под этим постом были направлены на первоначальный пост, который был слишком расплывчатым.На последующую повторную публикацию (и награду) ikegami отреагировал очень креативно, что решило проблему псевдо, но оказалось трудным для понимания и повторного использования в моем реальном приложении, что влечет за собой многократное использование на различных html-страницах.Примерно в 18-м комментарии в нашем диалоге я наконец обнаружил его значение и использование ($ cat) - недокументированного синтаксиса Perl, который он использовал.Для новых читателей понимание этого синтаксиса позволяет понять (и переформатировать) его разумное решение проблемы.Его пост, безусловно, отвечает основным требованиям, предъявляемым в ОП, но для этого не используется HTML :: TreeBuilder :: XPath.

jpalecek использует HTML :: TreeBuilder :: XPath, но не помещает захваченные данные в массивы для передачи обратно в функцию PHP и загрузки в базу данных.

Я узнал от обоих респондентови надеюсь, что этот пост поможет другим новичкам в Perl, таким как я.Будем весьма признательны за любые окончательные материалы.

Ответы [ 2 ]

5 голосов
/ 04 февраля 2012

Если бы я угадал, ваш вопрос: «Как я могу получить следующее из предоставленного ввода?»

my $categorized_items = {
   'We have nuts!'    => [ 'Almonds', 'Cashwes' ],
   'We have veggies!' => [ 'Carrots', 'Celery' ],
   'We have booze!'   => [ ],
};

Если так, вот как я это сделаю:

use Data::Dumper qw( Dumper );
use XML::LibXML  qw( );

my $root = XML::LibXML->load_xml(IO=>\*DATA)->documentElement;

my %cat_items;
for my $cat_tr ($root->findnodes('//table[contains(@id, "ITEMS")]/tr[td/span]')) {
   my ($cat) = map $_->textContent(),
      $cat_tr->findnodes('td/span');

   my @items = map $_->textContent(),
      $cat_tr->findnodes('following-sibling::tr[position()=1]/td/table/tr/td');

   $cat_items{$cat} = \@items;
}

print(Dumper(\%cat_items));

__DATA__
...xml...

PS. То, что у вас есть, не соответствует действительности.

  1. Элемент TABLE не может быть размещен непосредственно внутри элемента TR. Там отсутствует элемент TD.
  2. Элемент TR не может быть пустым. Он должен иметь хотя бы один элемент TH или TD.
2 голосов
/ 03 февраля 2012
  1. Как убедиться, что something существует перед запуском query. Например. если существует //p[@class='red'], вернуть //table:

    /.[//p[@class='red']]//table
    
  2. x[3 and 4 and 5]: 3 and 4 and 5 - логическое выражение, которое выдает true. Следовательно, вы получите все 1015 * с. Для 3-го, 4-го и 5-го вы хотите

    x[position() >= 3 and position() <= 5]
    

Ответ на отредактированный вопрос:

Почему бы вам не использовать XML::XPathEngine с несколькими запросами?

my $xp = XML::XPathEngine->new;
my $tree = HTML::TreeBuilder::XPath->new;
$tree->parse (something);

Затем вы можете запросить:

my $shops = $xp->findnodes('//table[contains(@id, "ITEMS")]/tr[position() >1]/td[@span]', $tree);
for($shops->get_nodelist) {
  print "Name of shop is ".$xp->findvalue('span/text()', $_)."\n"; # <- query relative to $_
  print "The shop sells:\n". join("\n", $xp->findvalues('parent::*/following-sibling::tr[1][not(span)]/td/table/tr/td', $_));
}

Это то же самое, что и ответ @ ikegami (XML::XPathEngine используется HTML::TreeBuilder::XPath). Кстати, если в магазинах может быть больше строк с продуктами после них, это следует обновить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...