package Ernad::Sort; use strict; use warnings; use Carp qw(cluck longmess shortmess croak confess); use Data::Dumper; use Ernad::Common; use Ernad::Constant; use Ernad::Lenu; my $ernad_ns = $Ernad::Constant::c->{'ernad_ns'}; my $amf_ns = $Ernad::Constant::c->{'amf_ns'}; # my $xpc = &Ernad::Common::create_xpc; ## injects an array of weights ## can also deal with a hash sub inject_sort { my $doc=shift; my $weights=shift; my $criterion=shift // ''; if(not $criterion) { die "no criterion name"; } my $ref_doc; $ref_doc=ref $doc or $ref_doc=$doc; if($ref_doc ne 'XML::LibXML::Document') { confess "I need an XML document, but you gave me '$ref_doc'"; } my @papers_elements=$doc->getElementsByTagNameNS($amf_ns,'text')->get_nodelist(); my $papers_count=$doc->getElementsByTagNameNS($amf_ns,'text')->size(); my $count_text=0; my $sort_element=$doc->createElementNS($ernad_ns,'sort'); $sort_element->setAttribute('criterion',$criterion); my $printed=0; while($count_text < $papers_count) { my $item_element=$doc->createElementNS($ernad_ns,'item'); my $polet=&Ernad::Lenu::code($count_text+1); $item_element->setAttribute('polet',$polet); my $paper=$papers_elements[$count_text]; my $ref=$paper->getAttribute('ref') // ''; if(not $ref) { die "no ref= attribute on paper number $count_text (from 0)"; } $item_element->setAttribute('ref',$ref); my $weight; if(ref $weights eq 'HASH') { ## has to be here for coherene with array case $count_text++; $weight=$weights->{$ref}; if(not defined($weight)) { confess "I must have a weight for $ref defined."; } } elsif(ref $weights eq 'ARRAY') { $weight=$weights->[$count_text++] // 0; } else { confess "I have no clue what to do with $weights."; } $item_element->setAttribute('weight',$weight); $sort_element->appendChild($item_element); ## print out the first item to check if(not $printed) { # print "weight is $weight, ref is $ref\n"; $printed=1; } } my $e=$main::e // confess 'I need an erimp here.'; my $xpc=$e->{'xpc'} // confess 'I need an xpc here.'; my $collection_element=$xpc->findnodes('/amf:amf/amf:collection[1]',$doc)->get_node(1); if(not defined($collection_element)) { print $doc; confess "I need a collection_element defined here."; } ## delete old sort, if it exists my $sort_xp='/amf:amf/amf:collection[1]/ernad:sort[@criterion=\''.$criterion.'\']'; my @old_sort_elements=$xpc->findnodes($sort_xp,$doc)->get_nodelist; #open(F,"> /tmp/sort.log"); foreach my $old_sort_element (@old_sort_elements) { my $result=$old_sort_element->parentNode->removeChild($old_sort_element); #print F "removed ", $old_sort_element->toString, "\n"; } #close F; if(not defined($sort_element)) { confess "I need a sort_element defined here."; } $collection_element->appendChild($sort_element); $doc=$e->{'x'}->polet($doc); return $doc; } ## are all weights zero sub are_all_weights_zero { my $weights=shift; foreach my $weight (@{$weights}) { if($weight != 0) { return 0; } } return 1; } ## set all weights to zero to allow for repeat sorting sub set_all_weights_zero { my $weights=shift; my $count_weights=0; foreach my $weight (@{$weights}) { $weights->[$count_weights++]=0; } return $weights; } ## sort by a criterion in the sort element sub sort_by_criterion { my $doc=shift; my $criterion=shift // ''; if(not $criterion) { confess "I need a criterion here."; } my $direction=shift // ''; if($direction ne 'a' and $direction ne 'd') { confess "My direction must 'a' or 'd'."; } my $type=shift // 'numeric'; ## 2018-11-14 moved truncated to Rif::Truncate # my $truncate_param=shift // ''; # ## this may indicate a default truncation. Fixme: # my $max_papers=0; # my $max_percent=0; # if($truncate_param) { # if($truncate_param=~m|^\d+$|) { # $max_papers=$truncate_param; # if($max_percent<100) { # warn "You want to truncate to $max_papers less than 100, did you mean $max_papers%?"; # } # } # elsif($truncate_param=~m|^(\d+)%$|) { # $max_percent=$1; # if($max_percent>100) { # confess "My percentage can not be larger than 100."; # } # } # else { # confess "You truncate parameter '$truncate_param' is neither a number nor percentage."; # } # } my $sort_xp='/amf:amf/amf:collection[1]/ernad:sort[@criterion=\''.$criterion.'\']'; my $e=$main::e // confess 'I need an erimp here.'; my $xpc=$e->{'xpc'} // confess 'I need an xpc here.'; my $sort_element=$xpc->findnodes($sort_xp,$doc)->get_node(1) // ''; if(not $sort_element) { print $doc->toString; die "I can not find a sort element for criterion $criterion"; } my $out=&get_rump_doc($doc,$xpc); my $item_xp="ernad:item"; my @items=$xpc->findnodes($item_xp,$sort_element)->get_nodelist() or confess "I don't see any item element in '$sort_element'.\n"; my $weights; my $item_elements; foreach my $item (@items) { #print $item->toString; my $ref=$item->getAttribute('ref') or die; my $weight=$item->getAttribute('weight'); $weights->{$ref}=$weight; $item_elements->{$ref}=$item->cloneNode(1); } my $haspart_xp='/amf:amf/amf:collection[1]/amf:haspart'; my @hasparts=$xpc->findnodes($haspart_xp,$doc)->get_nodelist(); # my $total_hasparts=scalar @hasparts; # if(not $max_papers and $max_percent) { # $max_papers=int($total_hasparts * $max_percent / 100); # } my $papers; my $refs; my $count_papers=0; foreach my $haspart_element (@hasparts) { my $ref=$xpc->findvalue('amf:text/@ref',$haspart_element) or die "no ref"; my $paper=$haspart_element->cloneNode(1); my $item_xp='ernad:item[@criterion=\''.$criterion.'\']'; my @old_item_elements=$xpc->findnodes($item_xp,$paper); ## remove the old item element foreach my $old_item_element (@old_item_elements) { #print "old item element removed\n"; $old_item_element->parentNode->removeChild($old_item_element); } ## append item my $my_item=$item_elements->{$ref} or die; $my_item->setAttribute('criterion',$criterion); $my_item->removeAttribute('ref'); # $count_papers++; # if($max_papers and $count_papers > $max_papers) { # last; # } $paper->appendChild($my_item); $papers->{$ref}=$paper; } my $coll_xp='/amf:amf/amf:collection[1]'; my $collection=$xpc->findnodes($coll_xp,$out)->get_node(1); my @sorted; if($type eq 'numeric') { if($direction eq 'd') { @sorted=sort {$weights->{$b} <=> $weights->{$a}} keys %$papers; } elsif($direction eq 'a') { @sorted=sort {$weights->{$a} <=> $weights->{$b}} keys %$papers; } } elsif($type eq 'string') { if($direction eq 'd') { @sorted=sort {$weights->{$b} cmp $weights->{$a}} keys %$papers; } elsif($direction eq 'a') { @sorted=sort {$weights->{$a} cmp $weights->{$b}} keys %$papers; } } else { confess "I need a sorting type 'numeric' or 'string', you gave me '$type'."; } $count_papers=0; foreach my $ref (@sorted) { $count_papers++; # if($max_papers and $count_papers > $max_papers) { # last; # } $collection->appendChild($papers->{$ref}); } ## delete the existing sort element $sort_element=$xpc->findnodes($sort_xp,$out)->get_node(1) // ''; if($sort_element) { $sort_element->parentNode->removeChild($sort_element); } return $out; } ## removes papers from a rix. This should be in Ernad::Rix, but then it needs an erimp. sub get_rump_doc { my $doc=shift; my $xpc=shift; if (not ref $doc eq 'XML::LibXML::Document') { die 'no document for get_rump'; } my $out=$doc->cloneNode(1); my $texts_xp='/amf:amf/amf:collection[1]/amf:haspart'; my @hasparts=$xpc->findnodes($texts_xp,$out)->get_nodelist(); foreach my $haspart (@hasparts) { $haspart->parentNode->removeChild($haspart); } return $out; } # ## injects an hash of weights # sub inject_sort_by_hash { # my $doc=shift; # my $weights=shift; # die ref $weights; # my $criterion=shift // ''; # if(not $criterion) { # die "no criterion name"; # } # my @papers_elements=$doc->getElementsByTagNameNS($amf_ns,'text')->get_nodelist(); # my $papers_count=$doc->getElementsByTagNameNS($amf_ns,'text')->size(); # my $count_text=0; # my $sort_element=$doc->createElementNS($ernad_ns,'sort'); # $sort_element->setAttribute('criterion',$criterion); # while($count_text < $papers_count) { # my $item_element=$doc->createElementNS($ernad_ns,'item'); # my $paper=$papers_elements[$count_text]; # my $ref=$paper->getAttribute('ref') // ''; # if(not $ref) { # die "no ref= attribute on paper number $count_text (from 0)"; # } # $item_element->setAttribute('ref',$ref); # my $weight=$weights->{$ref}; # if(not defined($weight)) { # confess "I must have a weight fro $ref defined."; # } # $item_element->setAttribute('weight',$weight); # $sort_element->appendChild($item_element); # } # my $collection_element=$xpc->findnodes('/amf:amf/amf:collection[1]',$doc)->get_node(1); # ## delete old sort, if it exists # my $sort_xp='/amf:amf/amf:collection[1]/ernad:sort[@criterion=\''.$criterion.'\']'; # my @old_sort_elements=$xpc->findnodes($sort_xp,$doc)->get_nodelist; # foreach my $old_sort_element (@old_sort_elements) { # open(F,"> /tmp/sort.log"); # my $result=$old_sort_element->parentNode->removeChild($old_sort_element); # print F "removed ", $old_sort_element->toString, "\n"; # close F; # } # $collection_element->appendChild($sort_element); # return $doc; # } 1;