Perl: получение нескольких дочерних элементов элемента XPath

Я использую XML::Twig для анализа XML-файла и вывода некоторой информации. Вот мой XML-файл:

<report>
    <reportheader>
        <month>February 2015</month>
        <dateofgeneration>20/02/2015 - 12:29:02</dateofgeneration>
    </reportheader>
</report>

и у меня есть следующий код:

XML::Twig->new(
    twig_handlers => {
        '/report/reportheader' => sub {
            printf qq|%s\n|, $_->text;
        },
    },  
)->parsefile($ARGV[0]);

К сожалению, это печатает February 201520/02/2015 - 12:29:02. Есть ли способ разделить эти два, а не объединять их? Я надеялся сделать что-то вроде:

printf qq|Month: %s\nDate: %s\n|, $_->text[0], $_->text[1];

разделить его на две переменные, но это не сработало.


person Bijan    schedule 05.03.2015    source источник
comment
Разве это не было бы просто '/report/reportheader/*' для селектора? «Текст» или «значение» узла элемента обычно представляет собой конкатенацию текстового содержимого самого себя и всех дочерних элементов.   -  person user2864740    schedule 05.03.2015
comment
Ваша модификация немного сработала. Теперь он печатает их на отдельных строках, но я хочу, чтобы им предшествовал другой текст, то есть Month: February 2015\nDate: 20/02/2015....   -  person Bijan    schedule 05.03.2015
comment
Вы должны иметь возможность проверить имя текущего узла. Или вы можете создать обработчик для каждого узла.   -  person ikegami    schedule 05.03.2015
comment
Как мне создать обработчик для каждого узла.   -  person Bijan    schedule 05.03.2015


Ответы (1)


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

Тот же подход подходит и для решения этой проблемы. Это просто вопрос поиска всех /report/reportheader элементов и вывода текстового содержимого их (первых) month и dateofgeneration дочерних элементов.

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

use strict;
use warnings;
use 5.010;     # For `say`

use XML::Twig;

my $twig = XML::Twig->new;
$twig->parsefile(shift @ARGV);

for my $report_header ( $twig->findnodes('/report/reportheader') ) {
  say $report_header->first_child_trimmed_text('month');
  say $report_header->first_child_trimmed_text('dateofgeneration');
}

вывод

February 2015
20/02/2015 - 12:29:02
person Borodin    schedule 05.03.2015
comment
Спасибо за ответ. Мне это нравится больше, чем то, что у меня было сейчас. Быстрый вопрос, чтобы не задавать еще один вопрос. Можно ли использовать findnodes для поиска всех узлов месяца и сохранения их в массиве? - person Bijan; 05.03.2015
comment
@Бижан: Конечно. Вы должны написать my @months = $twig->findnodes('//month'), чтобы найти все month элементы в любом месте XML-документа. Похоже, вы могли бы использовать Руководство по XPath, но, пожалуйста, избегайте W3Schools, которые в первую очередь предназначены для зарабатывания денег, а не для честного источника информации. Лучше всего сам RFC - person Borodin; 05.03.2015
comment
Есть ли однострочник, чтобы просто загрузить текст из результатов? my @months = $twig->findnodes('//month')->text() дает мне текст метода объекта "Не могу найти". Я бы предпочел не использовать foreach для загрузки $_->text() - person Bijan; 05.03.2015
comment
@Bijan: Вы не можете этого сделать, потому что findnodes в скалярном контексте просто возвращает количество найденных элементов. (Вы также показали только половину сообщения об ошибке. Пожалуйста, не делайте этого.) Вам нужно будет использовать map, если вы действительно хотите, чтобы это было в одной строке, например my @months = map $_->trimmed_text, $twig->findnodes('//month'), но это странно требуют решения с одним утверждением, когда они часто менее ясны, чем эквивалентная итерация. - person Borodin; 05.03.2015
comment
И последнее (обещаю). Как лучше всего вытащить один элемент, например «/report/reportheader/month», вместо использования цикла for - person Bijan; 05.03.2015
comment
@Bijan: /report/reportheader/month может соответствовать многим элементам; но чтобы поймать только первый, вы можете написать my ($first_report_month) = $twig->findnodes('/report/reportheader/month') - person Borodin; 05.03.2015
comment
Давайте продолжим обсуждение в чате. - person Bijan; 05.03.2015
comment
@Bijan: Нет, мне жаль, что я попался на Быстрый вопрос, чтобы не задавать еще один вопрос. Stack Overflow — это не форум, где вы лично решаете свои проблемы: он предназначен для того, чтобы стать хранилищем общих проблем программирования и их решений. Основная цель вопросов здесь — помочь другим, у которых может быть похожая проблема. Все слишком конкретное не по теме, а задавание вопросов в комментариях скрывает эту информацию от других людей, которые могут ее искать. Пожалуйста, будьте немного более открытыми и задайте новый вопрос, если вы не можете найти ответ на него уже на этом сайте. - person Borodin; 05.03.2015