Как мога да сортирам 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
Само за пояснение (защото попитахте как програмно преминавате през всички записи): сортирането ще извика вашата функция много пъти, всеки път с две стойности от вашия списък (но без определен ред). - person Chris Jester-Young; 13.06.2009
comment
Добре... това почти работи, с изключение на това, че xPath е малко по-сложен от просто 'published'-това е './post:published'. Имам пространството от имена, декларирано по-рано като $xc-›registerNs(post =› 'w3.org/2005/ Atom'); но след като обектът бъде преработен като $a и $b, той губи пространството от имена. Някакъв начин за поддържане на пространството от имена вътре в sub? - 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('published'). В моя XML файл обаче възелът има пространство за имена: ‹post:published›...‹/pu...›. Ако оставя простото „публикувано“ име като атрибут за $a и $b, скриптът се проваля, защото и $a, и $b завършват с нула. Ако поставя пълния 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 пространство от имена, което документът декларира, че е post. (И не забравяйте, че не можете да използвате кратките имена като пространства от имена - това са преки пътища за маркиране, а не преки пътища за анализиране. Винаги използвайте 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