带有条件的Perl XPath语句 - 可能吗?

时间:2012-01-30 20:21:08

标签: perl xpath

这个问题已被重新定义。我使用CPAN Perl模块WWW::Mechanize来浏览网站,HTML::TreeBuilder-XPath来捕获内容,使用xacobeo来测试HTML / XML上的XPath代码。目标是从基于PHP的网站调用此Perl脚本,并将已删除的内容上载到数据库中。因此,如果内容“缺失”,则仍需要考虑内容。

以下是经过测试的简化示例代码,描述了我的挑战。注意:

  1. 此页面是动态填充的,包含为不同商店输出的各种ITEMS;每个商店将存在不同数量的Products*。这些产品清单可能有也可能没有其下方的逐项表。
  2. 捕获的数据必须在数组中,并且必须维护任何逐项列表(如果存在)与产品列表的关联。
  3. 下面,示例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 
    

    在实际代码中,产品是已知的,因此可以在预期XPath输出时定义my @veggiesREFmy @nutsREF

    我意识到XPath if / else / then功能是在XPath 2.0版本中。我在ubuntu系统上并在本地工作,但我仍然不清楚我的apache2服务器是使用它还是1.0版本。我该如何检查?

    最后,如果您可以展示如何从PHP表单提交调用Perl脚本以及如何将Perl数组传递回调用PHP函数,那么这将获得赏金。 :)

    谢谢!

    最终编辑:

    这篇文章下方的评论是针对一个过于模糊的初始帖子。随后的重新发布(和赏金)由ikegami以非常有创意的方式回应解决了伪问题,但在我的实际应用程序中难以掌握和重用 - 这需要在各种html页面上进行多次使用。在关于我们对话框中的第18条评论中,我终于发现了他的意思和使用($ cat) - 他使用的未记录的Perl语法。对于新读者来说,理解该语法可以理解(并重新格式化)他对问题的智能解决方案。他的帖子肯定符合OP中寻求的基本要求,但不使用HTML :: TreeBuilder :: XPath来实现。

    jpalecek使用HTML :: TreeBuilder :: XPath,但不会将捕获的数据放入数组中,以便传回PHP函数并上传到数据库中。

    我从两位响应者那里学到了很多,希望这篇文章可以帮助像Perl这样的新人,比如我自己。任何最后的贡献将不胜感激。

2 个答案:

答案 0 :(得分:5)

如果我猜,你的问题是:“如何从提供的输入中获得以下内容?”

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 - 你所拥有的是无效的HTML。

  1. TABLE元素不能直接放在TR元素中。 TD元素缺失了。
  2. TR元素不能为空。它必须至少有一个TH或TD元素。

答案 1 :(得分:2)

  1. 如何在运行something之前确定query存在。例如。如果存在//p[@class='red'],则返回//table

    /.[//p[@class='red']]//table
    
  2. x[3 and 4 and 5]3 and 4 and 5是一个布尔表达式,产生true。因此,它将为您提供所有x个。对于你想要的第3,第4和第5个

    x[position() >= 3 and position() <= 5]
    
  3. 回答编辑过的问题:

    为什么不将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)。顺便说一句,如果商店可以有更多的产品线,那么应该更新。