Исправление предиката XPath для использования в XML :: Twig - PullRequest
4 голосов
/ 08 июля 2011

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

Учитывая XML как:

<Path>
  <To>
    <My>
      <Node>
        <ChildA>ValA</ChildA>
        <ChildB>ValB</ChildB>
        <ChildC>ValC</ChildC>
      </Node>
    </My>
  </To>
</Path>
<!-- A lot of siblings follow... -->

Я использую выражение XPath:

/Path/To/My/Node[ChildA="ValA" and ChildB="ValB" and ChildC="ValC"]

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

Error in XPath expression
/Path/To/My/Node[ChildA="ValA" and ChildB="ValB" and ChildC="ValC"] at 
ChildA="ValA" and ChildB="ValB" and ChildC="ValC" at Twig.pm line 3353

Я в растерянности из-за этого и ищу предложения. Я пытался погуглить, но не могу найти рабочих примеров использования предикатов, подобных этому, в XML::Twig. Я не знаю, в чем проблема в моем синтаксисе XPath или как я использую XML::Twig.

Для хорошей меры я также попробовал:

/Path/To/My/Node[ChildA/text()="ValA" and ChildB/text()="ValB" and ChildC/text()="ValC"]

Не повезло и с этим. Какое решение?

Ответы [ 2 ]

3 голосов
/ 08 июля 2011

Есть два способа сделать это: загрузить весь XML и удалить ненужные узлы, затем вывести веточку или отфильтровать по мере продвижения, что немного сложнее, но использует меньше памяти.

Первый способ (вам может потребоваться последняя версия XML :: XPathEngine, я не тестировал ее с более старыми версиями или с XML :: XPath, который также может действовать как механизм XPath)

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig::XPath;

my $t= XML::Twig::XPath->new( pretty_print => 'indented')
                       ->parse( \*DATA);
$_->delete for ($t->findnodes( '/Path/To/My/Node[./ChildA="ValA" and ./ChildB="ValB" and ./ChildC="ValC"]'));

$t->print;

__DATA__
<Path>
  <To>
    <My>
      <Node>
        <ChildA>ValA</ChildA>
        <ChildB>ValB</ChildB>
        <ChildC>ValC</ChildC>
      </Node>
      <Node>
        <ChildA>ValD</ChildA>
        <ChildB>ValB</ChildB>
        <ChildC>ValC</ChildC>
      </Node>
    </My>
  </To>
</Path>

И «фильтрующий» способ:

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

XML::Twig->new( twig_roots => { '/Path/To/My/Node' => \&filter },
                twig_print_outside_roots => 1,
                keep_spaces => 1,
              )
         ->parse( \*DATA);
exit;

# the handler expressions cannot lookahead, so we need to look at each node
# once it's completely parsed
sub filter
  { my( $t, $node)= @_;
    if(    ($node->field( 'ChildA') eq 'ValA')
        && ($node->field( 'ChildB') eq 'ValB')
        && ($node->field( 'ChildC') eq 'ValC')
      )
      { $node->delete; }
    else
      { $t->flush; }
  }

__DATA__
<Path>
  <To>
    <My>
      <Node>
        <ChildA>ValA</ChildA>
        <ChildB>ValB</ChildB>
        <ChildC>ValC</ChildC>
      </Node>
      <Node>
        <ChildA>ValD</ChildA>
        <ChildB>ValB</ChildB>
        <ChildC>ValC</ChildC>
      </Node>
    </My>
  </To>
</Path>
3 голосов
/ 08 июля 2011

В тесте Node является узлом контекста, поэтому вы должны сказать:

/Path/To/My/Node[./ChildA="ValA" and ./ChildB="ValB" and ./ChildC="ValC"]

Это работает для меня в короткой тестовой программе, которая использует XML::XPath.

* 1007.* РЕДАКТИРОВАТЬ: Извините, я не очень знаком с XML :: Twig, и я сделал неверное предположение о его возможностях XPath.Согласно документации, он поддерживает только «XPath-подобный» синтаксис, который не поднимается до уровня сложности вашего примера.Однако, если вы используете XML::Twig::XPath вместо XML::Twig, вы получите полный механизм XPath:
my $twig = XML::Twig::XPath->new;
$twig->parse('your string');
my $nodes = $twig->findnodes('/Path/To/My/Node[ChildA="ValA" and ChildB="ValB" and ChildC="ValC"]');
print $nodes;

Это выдает «ValAValBValC».

...