Как я могу отсортировать XML-записи с помощью LibXML и Perl?

Я разбираю XML-файл с помощью LibXML, и мне нужно отсортировать записи по дате. Каждая запись имеет два поля даты: одно для того, когда запись была опубликована, а другое - когда она была обновлена.

<?xml version="1.0" encoding="utf-8"?>
...
<entry>
  <published>2009-04-10T18:51:04.696+02:00</published>
  <updated>2009-05-30T14:48:27.853+03:00</updated>
  <title>The title</title>
  <content>The content goes here</content>
</entry>
...

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

my $parser = XML::LibXML->new();
my $doc = $parser->parse_file($file);
my $xc = XML::LibXML::XPathContext->new($doc->documentElement());

foreach my $entry (reverse($xc->findnodes('//entry'))) {
  ...
}

Однако мне нужно отсортировать файл по дате публикации, а не по дате обновления. Как я могу это сделать? Отметка времени тоже выглядит немного шаткой. Нужно ли мне сначала нормализовать это?

Спасибо!

Обновление: после того, как я поигрался с пространствами имен XPath и потерпел неудачу, я создал функцию, которая анализировала XML и сохраняла нужные мне значения в хэше. Затем я использовал чистый sort для сортировки хэша, который теперь отлично работает.


person Andrew    schedule 13.06.2009    source источник
comment
Рад видеть, что вы пошли по пути XML :: LibXML. Немного сложно привыкнуть к строгости XML, но в конечном итоге это сэкономит вам много времени. (Мне приходилось работать с поставщиками, которые использовали сокращенные способы реализации XML, то есть я не мог фактически дать им XML, мне пришлось дать им своего рода суп из тегов. Очень раздражает.)   -  person jrockway    schedule 14.06.2009


Ответы (2)


Один из способов - изменить ваш reverse на sort оператор (непроверенный):

sub parse_date {
    # Transforms date from 2009-04-10T18:51:04.696+02:00 to 20090410
    my $date= shift;
    $date= join "", $date =~ m!\A(\d{4})-(\d{2})-(\d{2}).*!;
    return $date;
}

sub by_published_date {
    my $a_published= parse_date( $a->getChildrenByTagName('published') );
    my $b_published= parse_date( $b->getChildrenByTagName('published') );

    # putting $b_published in front will ensure the descending order.
    return $b_published <=> $a_published;
}

foreach my $entry ( sort by_published_date $xc->findnodes('//entry') ) {
    ...
}

Надеюсь, что это помогает немного!

person Igor    schedule 13.06.2009
comment
А, теперь я понимаю, я думаю ... $ a и $ b - это две отдельные записи, верно? Но как я мог программно просмотреть все записи? В некоторых файлах есть сотни записей ... - person Andrew; 13.06.2009
comment
$ a и $ b заполняются функцией сортировки. Все, что ваша функция должна сделать для любых двух элементов в вашем списке, - это вернуть -1, если $ a должен отсортировать до $ b, 1, если $ b должен отсортировать до $ a, и 0 в противном случае. sort позаботится обо всем остальном. - person Chris Jester-Young; 13.06.2009
comment
Просто чтобы уточнить (потому что вы спросили, как вы программно просматриваете все записи): sort будет вызывать вашу функцию много раз, каждый раз с двумя значениями из вашего списка (но без определенного порядка). - person Chris Jester-Young; 13.06.2009
comment
Хорошо ... это почти работает, за исключением того, что xPath немного сложнее, чем просто «опубликовано» - это «./post:published». У меня есть пространство имен, объявленное ранее как $ xc- ›registerNs (post =› 'w3.org/2005/ Атом '); но как только объект преобразован в $ a и $ b, он теряет пространство имен. Любой способ сохранить пространство имен внутри подпрограммы? - person Andrew; 13.06.2009
comment
Чтобы быть более конкретным: $a и $b - глобальные переменные, используемые функцией sort. Для получения дополнительной информации о них вы можете прочитать perldoc -f sort в командной строке или perldoc.perl.org/ functions / sort.html. - person Igor; 13.06.2009
comment
Я не совсем понимаю, о чем вы говорите. В написанном мною примере $ a и $ b - это просто ссылки на некоторый элемент, возвращаемый функцией $ xc- ›findnodes (). - person Igor; 13.06.2009
comment
Проблема с xPath. В вашем примере $ a и $ b получают значение getChildrenByTagName ('опубликовано'). Однако в моем XML-файле узел имеет пространство имен: ‹post: published› ... ‹/ pu ...›. Если я оставлю простое «опубликованное» имя в качестве атрибута для $ a и $ b, сценарий завершится ошибкой, потому что оба $ a и $ b имеют значение null. Если я укажу полный xPath в качестве атрибута, сценарий завершится неудачно, потому что пространство имен неизвестно. Поэтому мне нужно как-то сослаться на пространство имен где-нибудь в функции сортировки ... Я просто не могу понять, где ... - person Andrew; 14.06.2009
comment
Возможно, вам следует использовать getChildrenByTagNameNS () вместо этого. Вы проверяли документацию XML :: LibXML :: Element об этих методах? - person Igor; 14.06.2009
comment
Да, я возился с различными функциями NS в LibXML :: Element, но ничего не работает. Я собираюсь попробовать другой подход ... - person Andrew; 14.06.2009
comment
У меня нет большого опыта работы с XML :: LibXML, я предпочитаю использовать XML :: Twig. Возможно, вы могли бы попробовать. - person Igor; 14.06.2009
comment
Я думаю, проблема в том, что вы опускаете http: // из пространства имен. Убедитесь, что вы используете тот же URI пространства имен, которым документ объявляет публикацию. (И помните, вы не можете использовать короткие имена в качестве пространств имен - это ярлыки для разметки, а не ярлыки для синтаксического анализа. Всегда используйте URI.) - person jrockway; 14.06.2009

Голая сортировка может выводить время из разных часовых поясов не по порядку:

 print for sort "2009-06-15T08:00:00+07:00", "2009-06-15T04:00:00+00:00";

Здесь второй раз - через 3 часа после первого, но сначала сортирует.

Я не совсем понимаю, что вы имеете в виду под «шатким». В вашем примере просто показаны временные метки в формате rfc3339.

person ysth    schedule 14.06.2009
comment
Ах. Я думал, что эти временные метки были проприетарными вещами, а не реальным форматом. Спасибо! - person Andrew; 15.06.2009