2015-07-05 9 views
2

У меня есть очень большой XML-документ, который я выполняю. XML использует в основном атрибуты, а не значения узлов. Мне может понадобиться найти множество файлов в файле, чтобы собрать одну группу информации. Они связаны друг с другом с помощью различных значений тега ref. В настоящее время каждый раз, когда мне нужно найти один из узлов для извлечения данных, я перебираю весь XML и выполняю соответствие атрибуту, чтобы найти правильный узел. Есть ли более эффективный способ просто выбрать узел заданного значения атрибута вместо постоянного цикла и сравнения? Мой текущий код настолько медленный, что он почти бесполезен.Perl libXML найти узел по значению атрибута

В настоящее время я делаю что-то подобное много раз в том же файле для множества различных узлов и комбинаций атрибутов.

my $searchID = "1234"; 
foreach my $nodes ($xc->findnodes('/plm:PLMXML/plm:ExternalFile')) { 
    my $ID  = $nodes->findvalue('@id'); 
    my $File = $nodes->findvalue('@locationRef'); 
    if ($searchID eq $ID) { 
     print "The File Name = $File\n"; 
    } 
} 

В приведенном выше примере я выполняю цикл и использую «if» для сравнения ID. Я надеялся, что смогу сделать что-то вроде этого ниже, чтобы просто совместить узел по атрибуту ... и будет ли он более эффективным, чем цикл?

my $searchID = "1234"; 
$nodes = ($xc->findnodes('/plm:PLMXML/plm:ExternalFile[@id=$searchID]')); 
my $File = $nodes->findvalue('@locationRef'); 
print "The File Name = $File\n"; 

ответ

2

Пройдите один проход, чтобы извлечь нужную информацию в более удобный формат или построить индекс.

my %nodes_by_id; 
for my $node ($xc->findnodes('//*[@id]')) { 
    $nodes_by_id{ $node->getAttribute('id') } = $node; 
} 

Тогда ваши петли становятся

my $node = $nodes_by_id{'1234'}; 

(И прекратить использование findvalue вместо getAttribute.)

+0

Упс, исправлена ​​ошибка. – ikegami

+0

Я удивлен, что '//.[@ id]' работает, поскольку '.' представляет текущий контекст и не имеет особого значения, кроме как первый шаг в пути. '// * [@ id]' гораздо чаще встречается – Borodin

+0

@Borodin, это как раз в файловой системе. '/ foo/bar /././ baz' - это то же самое, что'/foo/bar/baz'. Однако '*' соответствует только узлам элемента, тогда как '.' соответствует любому узлу, поэтому' * '- это то, что я должен был использовать. Исправлена. – ikegami

1

Если у вас есть DTD для документа, который объявляет атрибут id как DTD ID, и вы убедитесь, что DTD считывается при разборе документа, вы можете получить доступ к элементам с определенным идентификатором эффективно с помощью $doc->getElementById($id).

1

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

my $search_id = "1234"; 
my $query = "/plm:PLMXML/plm:ExternalFile/[\@id = '$search_id']"; 
foreach my $node ($xc->findnodes($query)) { 
    # ... 
} 

В выражении XPath вы также можете объединить несколько проверок атрибутов, например:

[@id = '$search_id' and contains(@pathname, '.pdf')] 

Один XPath Tutorial из many

2

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

Я надеялся, что я мог бы сделать что-то вроде этого ниже, чтобы просто соответствовать узлу с помощью атрибута вместо

...

$nodes = ($xc->findnodes('/plm:PLMXML/plm:ExternalFile[@id=$searchID]')); 

Рода.

Для данного ID, да, вы можете сделать

$nodes = $xc->findnodes("/plm:PLMXML/plm:ExternalFile[\@id=$searchID]"); 

... при условии, что $searchID, как известно, числовая.Обратите внимание, что двойные кавычки в perl означают, что переменные интерполируются, поэтому вы должны избегать @id, потому что это часть литеральной строки, а не массив perl, тогда как вы хотите, чтобы значение $searchID стало частью строки xpath, поэтому оно не является убежали.

Обратите внимание, что в этом случае вы запрашиваете его в скалярном контексте, будет иметь объект XML::LibXML::Nodelist, а не фактический узел или массив; для последнего вам нужно будет использовать квадратные скобки вместо круглых, как я сделал в следующем примере.

В качестве альтернативы, если ваш поиск идентификатор не может быть числовыми, но вы точно знаете, что это безопасно положить в строку XPath (например, не имеет каких-либо кавычек), то вы можете сделать следующее:

$nodes = [ $xc->findnodes('/plm:PLMXML/plm:ExternalFile[@id="' . $searchID . '"]') ]; 
print $nodes->[0]->getAttribute('locationRef'); # if you're 100% sure it exists 

Обратите внимание, что итоговая строка будет содержать значение в кавычках.

Наконец, можно сразу перейти к:

print $xc->findvalue('/plm:PLMXML/plm:ExternalFile[@id="' . $searchID . '"]/@locationRef'); 

... условии, что вы знаете, что есть только один узел с этим идентификатором.

+0

Re «Если вы будете делать это для большого количества идентификаторов», OP специально сказал, что у него много петель. В лучшем случае вы создали небольшое улучшение для каждого цикла, но мой подход исключает все, кроме одного цикла. – ikegami

+0

'my @nodes = $ xc-> findnodes (...);' будет иметь больше смысла. 'my ($ node) = $ xc-> findnodes (...);' будет иметь еще больший смысл. – ikegami

+0

Мои идентификаторы не являются числовыми. Все они имеют префикс id. Пример: Brian

Смежные вопросы