【问题标题】:Perl XPath statement with a conditional - is that possible?带有条件的 Perl XPath 语句 - 这可能吗?
【发布时间】:2012-02-22 14:57:27
【问题描述】:

这个问题已经改写了。我正在使用 CPAN Perl 模块 WWW::Mechanize 导航网站,使用 HTML::TreeBuilder-XPath 捕获内容并使用 xacobeo 在 HTML/XML 上测试我的 XPath 代码。目标是从基于 PHP 的网站调用这个 Perl 脚本,并将抓取的内容上传到数据库中。因此,如果内容“缺失”,仍然需要考虑。

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

  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 

在实际代码中,产品是已知的,因此可以在预期 XPath 输出时定义 my @veggiesREF 和 my @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 的人,比如我自己。任何最终贡献将不胜感激。

【问题讨论】:

  • 无响应。 :(我的问题是不是太罗嗦了?有些人更喜欢额外的信息。这真的是一个常见的要求吗?
  • td[3 and 4 and 6] 不返回第三个、第四个和第六个成员。括号中的表达式返回 true,因此返回所有 td。使用td[position()=3 or position()=4 or position()=6]。
  • 您忘记提供源 XML 文档(请尽可能小)。如果没有显示源 XML,那么请求选择某些节点的 XPath 表达式几乎毫无意义——这很好地解释了您得到的响应(缺少)。
  • @DimitreNovaatchev 我并不是真的在寻找一个确切的句法答案并发布多个文档,而且他们的所有代码都太多了 - 帖子已经太长了。这是一个架构问题:Choroba 解决了明显的语法错误。问题 1 更复杂,但从我读过的内容来看,XPath 没有为节点提供逻辑 if/else 方法。尽管 XPath 可以在格式正确的谓词中提供“if”选择,但我的解决方案更可能是在运行表达式的 Perl 代码中 - 可能处理返回的谓词值(?)
  • @choroba 感谢您对问题 1 的简短回答。 RE: 问题 2,您是否曾经需要检查节点 in XPATH 是否存在(if/else)并相应地更改您的代码?我倾向于使用 XPath 表达式检查存在,然后更改运行它的 Perl 代码。

标签: perl xpath


【解决方案1】:

如果我猜的话,您的问题是:“我如何从提供的输入中获得以下信息?”

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 元素。

【讨论】:

  • 你好@ikegami我已经添加了你提到的缺失的td,你能更新你的回复吗?另外,我很难运行您的代码。我尝试用my $xml = &lt;&lt;'__XML__; code here __XML__; 包装我的xml 然后my $parser = XML::LibXML-new() or die "can't ..."; 然后my $root = $parser-&gt;parse_string( $xml ); 不起作用,但我认为我缺少一些简单的东西。
  • @Ricalsin,解析器返回一个文档。您需要调用它的(不幸地命名为)documentElement 方法来获取根节点。我根据您的数据更改更新了我的节点,并添加了测试代码。
  • 谢谢@ikegami 我搞定了。对我来说,这是高级代码。我正在研究它。 :) map $_-&gt;textContent() 和 my ($cat) = map 段让我很困惑。你能告诉我 perldocs 的哪些部分我可以阅读以自学吗? (看来你是唯一一个愿意打 50 分的人,但我知道你的时间和知识更有价值 - 所以再次“感谢”。)
  • 顺便说一句,“我不想这样想……”也可以说是“不可能……”当然,我不认为比无数的努力更清楚许多人在几十年的存在。我试图让您了解我的智力来自哪里以及我掌握 Perl 的能力在哪里卡住了——因此需要一些“光”或链接。我认为您是一位出色的程序员(从您对我的帖子的回复中可以看出)。不幸的是,不那么熟练的人(比如我)需要像你这样的人耐心学习。再次感谢@ikegami 的帮助
  • 然后你接着说:“@Ricalsin,你以前没写过子吗?sub foo { my ($cat) = @_; ... }” @ikegami 你化身了程序员遭受的刻板印象:他们越精通代码,他们在人际关系中的能力就越差。正如我所说,我认为您对计算机、代码和 Perl 非常了解。认为这就是成功的全部是错误的。最后,架构和概念化解决方案和市场主导了这一天——而不是一些暂时提升你自己的模糊语法。感谢您的学习曲线。
【解决方案2】:
  1. 如何在运行query 之前确定something 存在。例如。如果//p[@class='red']存在,则返回//table:

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

    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 使用)。顺便说一句,如果商店后面可以有更多的产品线,应该更新。

【讨论】:

  • 你好@jpalecek 谢谢你的回答。我发布了一个更具描述性的问题,提出了几乎相同的问题 - 并在其上加了 50 分。你能再看看吗?
  • 嗨@jpalecek 我正在尝试运行您的代码。使用 CPAN 模组对我来说仍然很困难。与上面的 ikegami 一样,加载它的最后一个技巧是 my $root = XML::LibXML-&gt;load_xml(IO=&gt;\*DATA)-&gt;documentElement; 您引用的 HTML::TreeBuilder::XPath 甚至没有解析方法描述,但 HTML::TreeBuilder 有。尽管如此,输入$tree-&gt;parse (\*DATA); 似乎并没有给我任何结果。我为自己如此完全的无知而道歉。似乎我在 Perl 中浪费了很多时间在其他人似乎知道的事情上——我没有阅读什么来帮助这些看似简单的步骤?
  • BTW, if the shops can have more lines with products after them, this should be updated. 为什么这么说?
  • 嗨@jpalecek,我发现要传递__DATA__' contents into the HTML::TreeBuilder::XPath` 我需要$tree-&gt;parse_file(\*DATA); 然后将您的XPath 代码稍微更改为tr[position() &gt;1]/td[span](删除@) 使它工作。您的代码使用我的 OP 中的 HTML::TreeBuilder::XPath 模块。它不会将结果放入散列或数组中以上传到数据库中,该数据库也在 OP 中。我无法理解 ikegami 对 ($cat) 的使用。你能展示一种保存这些结果的优雅方法吗?此外,您使用 $_ 是否会在 DOM 上保存多次传递?
猜你喜欢
  • 1970-01-01
  • 2012-04-19
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
  • 2011-02-18
  • 2015-07-24
  • 1970-01-01
  • 2021-11-13
相关资源
最近更新 更多