TVDB: better handling of first run
[nonametv.git] / lib / NonameTV.pm
blob65e350b5cf440d03448c0757bdd13d8cbe429fb7
1 package NonameTV;
3 use strict;
4 use warnings;
6 # Mark this source-file as encoded in utf-8.
7 use utf8;
8 use Env;
10 use LWP::UserAgent;
11 use File::Temp qw/tempfile tempdir/;
12 use File::Slurp;
14 use NonameTV::StringMatcher;
15 use NonameTV::Log qw/w/;
16 use XML::LibXML;
18 BEGIN {
19 use Exporter ();
20 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
22 # set the version for version checking
23 $VERSION = 0.3;
25 @ISA = qw(Exporter);
26 @EXPORT = qw( );
27 %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ],
28 @EXPORT_OK = qw/MyGet expand_entities
29 Html2Xml Htmlfile2Xml
30 Word2Xml Wordfile2Xml
31 File2Xml Content2Xml
32 FindParagraphs
33 norm AddCategory
34 ParseDescCatSwe FixProgrammeData
35 ParseXml ParseXmltv
36 MonthNumber
37 CompareArrays
40 our @EXPORT_OK;
42 my $wvhtml = 'wvHtml --charset=utf-8';
43 # my $wvhtml = '/usr/bin/wvHtml';
45 my $ua = LWP::UserAgent->new( agent => "Grabber from http://tv.swedb.se",
46 cookie_jar => {},
47 env_proxy => 1 );
49 # Fetch a url. Returns ($content, true) if data was fetched from server and
50 # different from the last time the same url was fetched, ($content, false) if
51 # it was fetched from the server and was the same as the last time it was
52 # fetched and (undef,$error_message) if there was an error fetching the data.
54 sub MyGet
56 my( $url ) = @_;
57 my $res = $ua->get( $url );
59 if( $res->is_success )
61 return ($res->content, not defined( $res->header( 'X-Content-Unchanged' ) ) );
63 else
65 return (undef, $res->status_line );
69 # åäö ÅÄÖ
70 my %ent = (
71 257 => 'ä',
72 231 => 'c', # This should really be a c with a special mark on it.
73 # Unicode 000E7, UTF-8 195 167.
74 337 => 'ö',
75 8211 => '-',
76 8212 => '--',
77 8216 => "'",
78 8217 => "'",
79 8220 => '"',
80 8221 => '"',
81 8230 => '...',
82 8364 => "(euro)",
85 sub _expand
87 my( $num, $str ) = @_;
89 if( not defined( $ent{$num} ) )
91 $ent{$num} = "";
92 print STDERR "Unknown entity $num in $str\n";
95 return $ent{$num};
98 sub expand_entities
100 my( $str ) = @_;
102 $str =~ s/\&#(\d+);/_expand($1,$str)/eg;
104 return $str;
107 =item Html2Xml( $content )
109 Convert the HTML in $content into an XML::LibXML::Document.
111 Prints an error-message to STDERR and returns undef if the conversion
112 fails.
114 =cut
116 sub Html2Xml {
117 my( $html ) = @_;
119 my $xml = XML::LibXML->new;
120 $xml->recover(1);
122 # Remove character that makes the parser stop.
123 $html =~ s/\x00//g;
125 my $doc;
126 eval { $doc = $xml->parse_html_string($html, {
127 recover => 1,
128 suppress_errors => 1,
129 suppress_warnings => 1,
130 }); };
132 if( $@ ne "" ) {
133 my ($package, $filename, $line) = caller;
134 print "parse_html_string failed: $@ when called from $filename:$line\n";
135 return undef;
138 return $doc;
141 =item Htmlfile2Xml( $filename )
143 Convert the HTML in a file into an XML::LibXML::Document.
145 Prints an error-message to STDERR and returns undef if the conversion
146 fails.
148 =cut
150 sub Htmlfile2Xml
152 my( $filename ) = @_;
154 my $html = read_file( $filename );
156 return Html2Xml( $html );
160 =item Word2Xml( $content )
162 Convert the Microsoft Word document in $content into html and return
163 the html as an XML::LibXML::Document.
165 Prints an error-message to STDERR and returns undef if the conversion
166 fails.
168 =cut
170 sub Word2Xml
172 my( $content ) = @_;
174 my( $fh, $filename ) = tempfile();
175 print $fh $content;
176 close( $fh );
178 my $doc = Wordfile2Xml( $filename );
179 unlink( $filename );
180 return $doc;
183 sub Wordfile2Xml
185 my( $filename ) = @_;
187 my $html = qx/$wvhtml "$filename" -/;
188 if( $? )
190 w "$wvhtml $filename - failed: $?";
191 return undef;
194 # Remove character that makes LibXML choke.
195 $html =~ s/\…/.../g;
197 return Html2Xml( $html );
200 sub File2Xml {
201 my( $filename ) = @_;
203 my $data = read_file( $filename );
204 my $doc;
205 if( $data =~ /^\<\!DOCTYPE HTML/ )
207 # This is an override that has already been run through wvHtml
208 $doc = Html2Xml( $data );
210 else
212 $doc = Word2Xml( $data );
215 return $doc;
218 sub Content2Xml {
219 my( $cref ) = @_;
221 my $doc;
222 if( $$cref =~ /^\<\!DOCTYPE HTML/ )
224 # This is an override that has already been run through wvHtml
225 $doc = Html2Xml( $$cref );
227 else
229 $doc = Word2Xml( $$cref );
232 return $doc;
235 =pod
237 FindParagraphs( $doc, $expr )
239 Finds all paragraphs in the part of an xml-tree that matches an
240 xpath-expression. Returns a reference to an array of strings.
241 All paragraphs are normalized and empty strings are removed from the
242 array.
244 Both <div> and <br> are taken into account when splitting the document
245 into paragraphs.
247 Use the expression '//body//.' for html-documents when you want to see
248 all paragraphs in the page.
250 =cut
252 my %paraelem = (
253 p => 1,
254 br => 1,
255 div => 1,
256 td => 1,
259 sub FindParagraphs {
260 my( $doc, $elements ) = @_;
262 my $ns = $doc->find( $elements );
264 my @paragraphs;
265 my $p = "";
267 foreach my $node ($ns->get_nodelist()) {
268 if( $node->nodeName eq "#text" ) {
269 $p .= $node->textContent();
271 elsif( defined $paraelem{ $node->nodeName } ) {
272 $p = norm( $p );
273 if( $p ne "" ) {
274 push @paragraphs, $p;
275 $p = "";
280 return \@paragraphs;
284 # Remove any strange quotation marks and other syntactic marks
285 # that we don't want to have. Remove leading and trailing space as well
286 # multiple whitespace characters.
287 # Returns the empty string if called with an undef string.
288 sub norm
290 my( $str ) = @_;
292 return "" if not defined( $str );
294 # Uncomment the code below and change the regexp to learn which
295 # character code perl thinks a certain character has.
296 # These codes can be used in \x{YY} expressions as shown below.
297 # if( $str =~ /unique string/ ) {
298 # for( my $i=0; $i < length( $str ); $i++ ) {
299 # printf( "%2x: %s\n", ord( substr( $str, $i, 1 ) ),
300 # substr( $str, $i, 1 ) );
304 $str = expand_entities( $str );
306 $str =~ tr/\x{96}\x{93}\x{94}/-""/; #
307 $str =~ tr/\x{201d}\x{201c}/""/;
308 $str =~ tr/\x{2022}/*/; # Bullet
309 $str =~ tr/\x{2013}\x{2018}\x{2019}/-''/;
310 $str =~ tr/\x{017c}\x{0144}\x{0105}/zna/;
311 $str =~ s/\x{85}/... /g;
312 $str =~ s/\x{2026}/.../sg;
313 $str =~ s/\x{2007}/ /sg;
315 $str =~ s/^\s+//;
316 $str =~ s/\s+$//;
317 $str =~ tr/\n\r\t / /s;
319 return $str;
322 =item AddCategory
324 Add program_type and category to an entry if the entry does not already
325 have a program_type and category.
327 AddCategory( $ce, $program_type, $category );
329 =cut
331 sub AddCategory
333 my( $ce, $program_type, $category ) = @_;
335 if( not defined( $ce->{program_type} ) and defined( $program_type )
336 and ( $program_type =~ /\S/ ) )
338 $ce->{program_type} = $program_type;
341 if( not defined( $ce->{category} ) and defined( $category )
342 and ( $category =~ /\S/ ) )
344 $ce->{category} = $category;
348 =item ParseDescCatSwe
350 Parse a program description in Swedish and return program_type
351 and category that can be deduced from the description.
353 my( $pty, $cat ) = ParseDescCatSwe( "Amerikansk äventyrsserie" );
355 =cut
357 my $sm = NonameTV::StringMatcher->new();
358 $sm->AddRegexp( qr/kriminalserie/i, [ 'series', 'Crime/Mystery' ] );
359 $sm->AddRegexp( qr/deckarserie/i, [ 'series', 'Crime/Mystery' ] );
360 $sm->AddRegexp( qr/polisserie/i, [ 'series', 'Crime/Mystery' ] );
361 $sm->AddRegexp( qr/familjeserie/i, [ 'series', undef ] );
362 $sm->AddRegexp( qr/tecknad serie/i, [ 'series', undef ] );
363 $sm->AddRegexp( qr/animerad serie/i, [ 'series', undef ] );
364 $sm->AddRegexp( qr/dramakomediserie/i, [ 'series', 'Comedy' ] );
365 $sm->AddRegexp( qr/dramaserie/i, [ 'series', 'Drama' ] );
366 $sm->AddRegexp( qr/resedokumentärserie/i,[ 'series', 'Food/Travel' ] );
367 $sm->AddRegexp( qr/komediserie/i, [ 'series', 'Comedy' ] );
368 $sm->AddRegexp( qr/realityserie/i, [ 'series', 'Reality' ] );
369 $sm->AddRegexp( qr/realityshow/i, [ 'series', 'Reality' ] );
370 $sm->AddRegexp( qr/dokusåpa/i, [ 'series', 'Reality' ] );
371 $sm->AddRegexp( qr/actiondramaserie/i, [ 'series', 'Action' ] );
372 $sm->AddRegexp( qr/actionserie/i, [ 'series', 'Action' ] );
373 $sm->AddRegexp( qr/underhållningsserie/i,[ 'series', undef ] );
374 $sm->AddRegexp( qr/äventyrsserie/i, [ 'series', 'Action/Adv' ] );
375 $sm->AddRegexp( qr/äventyrskomediserie/i,[ 'series', 'Comedy' ] );
376 $sm->AddRegexp( qr/dokumentärserie/i, [ 'series', 'Documentary' ] );
377 $sm->AddRegexp( qr/dramadokumentär/i, [ undef, 'Documentary' ] );
379 $sm->AddRegexp( qr/barnserie/i, [ 'series', "Children's" ] );
380 $sm->AddRegexp( qr/matlagningsserie/i, [ 'series', 'Cooking' ] );
381 $sm->AddRegexp( qr/motorserie/i, [ 'series', undef ] );
382 $sm->AddRegexp( qr/fixarserie/i, [ 'series', "Home/How-to" ] );
383 $sm->AddRegexp( qr/science[-\s]*fiction[-\s]*serie/i,
384 [ 'series', 'SciFi' ] );
385 $sm->AddRegexp( qr/barnprogram/i, [ undef, "Children's" ] );
387 # Movies
388 $sm->AddRegexp( qr/\b(familje|drama|action)*komedi\b/i, [ 'movie', "Comedy" ] );
390 $sm->AddRegexp( qr/\b(krigs|kriminal)*drama\b/i, [ 'movie', "Drama" ] );
392 $sm->AddRegexp( qr/\baction(drama|film)*\b/i, [ 'movie', "Action/Adv" ] );
394 $sm->AddRegexp( qr/\b.ventyrsdrama\b/i, [ 'movie', "Action/Adv" ] );
396 $sm->AddRegexp( qr/\bv.stern(film)*\b/i, [ 'movie', undef ] );
398 $sm->AddRegexp( qr/\b(drama)*thriller\b/i, [ 'movie', "Crime" ] );
400 $sm->AddRegexp( qr/\bscience\s*fiction(rysare)*\b/i, [ 'movie', "SciFi" ] );
402 $sm->AddRegexp( qr/\b(l.ng)*film\b/i, [ 'movie', undef ] );
405 sub ParseDescCatSwe
407 my( $desc ) = @_;
409 return (undef, undef) if not defined $desc;
411 my $res = $sm->Match( $desc );
412 if( defined( $res ) )
414 return @{$res};
416 else
418 return (undef,undef);
422 sub FixProgrammeData
424 my( $d ) = @_;
426 $d->{title} =~ s/^s.songs+tart\s*:*\s*//gi;
427 $d->{title} =~ s/^seriestart\s*:*\s*//gi;
428 $d->{title} =~ s/^reprisstart\s*:*\s*//gi;
429 $d->{title} =~ s/^programstart\s*:*\s*//gi;
431 $d->{title} =~ s/^s.songs*avslutning\s*:*\s*//gi;
432 $d->{title} =~ s/^sista\s+delen\s*:*\s*//gi;
433 $d->{title} =~ s/^sista\s+avsnittet\s*:*\s*//gi;
435 if( $d->{title} =~ s/^((matin.)|(fredagsbio))\s*:\s*//gi )
437 $d->{program_type} = 'movie';
438 $d->{category} = 'Movies';
441 # Set program_type to series if the entry has an episode-number
442 # with a defined episode (i.e. second part),
443 # but doesn't have a program_type.
444 if( exists( $d->{episode} ) and defined( $d->{episode} ) and
445 ($d->{episode} !~ /^\s*\.\s*\./) and
446 ( (not defined($d->{program_type})) or ($d->{program_type} =~ /^\s*$/) ) )
448 $d->{program_type} = "series";
452 =pod
454 my $doc = ParseXml( $strref );
456 Parse an xml-string into an XML::LibXML document. Takes a reference to a
457 string as the only reference.
459 =cut
461 my $xml;
463 sub ParseXml {
464 my( $cref ) = @_;
466 if( not defined $xml ) {
467 $xml = XML::LibXML->new;
468 $xml->load_ext_dtd(0);
471 my $doc;
472 eval {
473 $doc = $xml->parse_string($$cref);
475 if( $@ ) {
476 w "Failed to parse xml: $@";
477 return undef;
480 return $doc;
483 =pod
485 Parse a reference to an xml-string in xmltv-format into a reference to an
486 array of hashes with programme-info.
488 =cut
490 sub ParseXmltv {
491 my( $cref, $channel ) = @_;
493 my $doc = ParseXml( $cref );
494 return undef if not defined $doc;
496 my @d;
498 # Find all "programme"-entries for $channel or all channels.
499 my $filter = "//programme";
500 if ($channel) {
501 $filter .= '[@channel="' . $channel . '"]';
503 my $ns = $doc->find( $filter );
504 if( $ns->size() == 0 ) {
505 return;
508 foreach my $pgm ($ns->get_nodelist) {
509 my $start = $pgm->findvalue( '@start' );
510 my $start_dt = create_dt( $start );
512 my $stop = $pgm->findvalue( '@stop' );
513 my $stop_dt = create_dt( $stop ) if $stop;
515 my $title = $pgm->findvalue( 'title' );
516 my $subtitle = $pgm->findvalue( 'sub-title' );
518 my $desc = $pgm->findvalue( 'desc' );
519 my $cat1 = $pgm->findvalue( 'category[1]' );
520 my $cat2 = $pgm->findvalue( 'category[2]' );
521 my $episode = $pgm->findvalue( 'episode-num[@system="xmltv_ns"]' );
522 my $production_date = $pgm->findvalue( 'date' );
523 my $url = $pgm->findvalue( 'url' );
525 my $aspect = $pgm->findvalue( 'video/aspect' );
526 my $quality = $pgm->findvalue( 'video/quality' );
528 my $stereo = $pgm->findvalue( 'audio/stereo' );
530 my @directors;
531 $ns = $pgm->find( ".//director" );
532 foreach my $dir ($ns->get_nodelist) {
533 push @directors, $dir->findvalue(".");
536 my @actors;
537 my $ns = $pgm->find( ".//actor" );
538 foreach my $act ($ns->get_nodelist) {
539 push @actors, $act->findvalue(".");
542 my @writers;
543 $ns = $pgm->find( ".//writer" );
544 foreach my $dir ($ns->get_nodelist) {
545 push @writers, $dir->findvalue(".");
548 my @adapters;
549 $ns = $pgm->find( ".//adapter" );
550 foreach my $dir ($ns->get_nodelist) {
551 push @adapters, $dir->findvalue(".");
554 my @producers;
555 $ns = $pgm->find( ".//producer" );
556 foreach my $dir ($ns->get_nodelist) {
557 push @producers, $dir->findvalue(".");
560 my @composers;
561 $ns = $pgm->find( ".//composer" );
562 foreach my $dir ($ns->get_nodelist) {
563 push @composers, $dir->findvalue(".");
566 my @editors;
567 $ns = $pgm->find( ".//editor" );
568 foreach my $dir ($ns->get_nodelist) {
569 push @editors, $dir->findvalue(".");
572 my @presenters;
573 $ns = $pgm->find( ".//presenter" );
574 foreach my $dir ($ns->get_nodelist) {
575 push @presenters, $dir->findvalue(".");
578 my @commentators;
579 $ns = $pgm->find( ".//commentator" );
580 foreach my $dir ($ns->get_nodelist) {
581 push @commentators, $dir->findvalue(".");
584 my @guests;
585 $ns = $pgm->find( ".//guest" );
586 foreach my $dir ($ns->get_nodelist) {
587 push @guests, $dir->findvalue(".");
590 my %e = (
591 start_dt => $start_dt,
592 title => $title,
593 description => $desc,
596 if( $stop =~ /\S/ ) {
597 $e{stop_dt} = $stop_dt;
600 if( $subtitle =~ /\S/ ) {
601 $e{subtitle} = $subtitle;
604 if( $episode =~ /\S/ ) {
605 $e{episode} = $episode;
608 if( $url =~ /\S/ ) {
609 $e{url} = $url;
612 if( $cat1 =~ /^[a-z]/ ) {
613 $e{program_type} = $cat1;
615 elsif( $cat1 =~ /^[A-Z]/ ) {
616 $e{category} = $cat1;
619 if( $cat2 =~ /^[a-z]/ ) {
620 $e{program_type} = $cat2;
622 elsif( $cat2 =~ /^[A-Z]/ ) {
623 $e{category} = $cat2;
626 if( $production_date =~ /\S/ ) {
627 $e{production_date} = "$production_date-01-01";
630 if( $aspect =~ /\S/ ) {
631 $e{aspect} = $aspect;
634 if( $quality =~ /\S/ ) {
635 $e{quality} = $quality;
638 if( $stereo =~ /\S/ ) {
639 $e{stereo} = $stereo;
642 if( scalar( @directors ) > 0 ) {
643 $e{directors} = join ", ", @directors;
646 if( scalar( @actors ) > 0 ) {
647 $e{actors} = join ", ", @actors;
650 if( scalar( @writers ) > 0 ) {
651 $e{writers} = join ", ", @writers;
654 if( scalar( @adapters ) > 0 ) {
655 $e{adapters} = join ", ", @adapters;
658 if( scalar( @producers ) > 0 ) {
659 $e{producers} = join ", ", @producers;
662 if( scalar( @composers ) > 0 ) {
663 $e{composers} = join ", ", @composers;
666 if( scalar( @editors ) > 0 ) {
667 $e{editors} = join ", ", @editors;
670 if( scalar( @presenters ) > 0 ) {
671 $e{presenters} = join ", ", @presenters;
674 if( scalar( @commentators ) > 0 ) {
675 $e{commentators} = join ", ", @commentators;
678 if( scalar( @guests ) > 0 ) {
679 $e{guests} = join ", ", @guests;
682 push @d, \%e;
685 return \@d;
688 sub create_dt
690 my( $datetime ) = @_;
692 my( $year, $month, $day, $hour, $minute, $second, $tz ) =
693 ($datetime =~ /(\d{4})(\d{2})(\d{2})
694 (\d{2})(\d{2})(\d{2})\s+
695 (\S+)$/x);
697 my $dt = DateTime->new(
698 year => $year,
699 month => $month,
700 day => $day,
701 hour => $hour,
702 minute => $minute,
703 second => $second,
704 time_zone => $tz
707 return $dt;
710 =pod
712 Convert month name to month number
714 =cut
716 sub MonthNumber {
717 my( $monthname , $lang ) = @_;
719 my( @months_1, @months_2, @months_3, @months_4, @months_5, @months_6, @months_7, @months_8 );
721 if( $lang =~ /^en$/ ){
722 @months_1 = qw/jan feb mar apr may jun jul aug sep oct nov dec/;
723 @months_2 = qw/janu febr marc apr may june july augu sept octo nov dec/;
724 @months_3 = qw/january february march april may june july august september october november december/;
725 @months_4 = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
726 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
727 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
728 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
729 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
730 } elsif( $lang =~ /^de$/ ){
731 @months_1 = qw/jan feb mar apr may jun jul aug sep oct nov dec/;
732 @months_2 = qw/Januar Februar März April Mai Juni Juli August September Oktober November Dezember/;
733 @months_3 = qw/Januar Februar Mä April Mai Juni Juli August September Oktober November Dezember/;
734 @months_4 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
735 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
736 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
737 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
738 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
739 } elsif( $lang =~ /^hr$/ ){
740 @months_1 = qw/sij vel ozu tra svi lip srp kol ruj lis stu pro/;
741 @months_2 = qw/sijecanj veljaca ozujak travanj svibanj lipanj srpanj kolovoz rujan listopad studeni prosinac/;
742 @months_3 = qw/sijecnja veljače ozujka travnja svibnja lipnja srpnja kolovoza rujna listopada studenoga prosinca/;
743 @months_4 = qw/sijeèa veljače ožujka travnja svibnja lipnja srpnja kolovoza rujna listopada studenog prosinca/;
744 @months_5 = qw/januar februar mart april maj juni juli august septembar oktobar novembar decembar/;
745 @months_6 = qw/siječanj veljace ozujka travnja svibnja lipnja srpnja kolovoza rujna listopada studenog prosinca/;
746 @months_7 = qw/jan feb mar apr maj jun jul aug sep okt nov dec/;
747 @months_8 = qw/siječnja feb mar apr maj jun jul aug sep okt nov dec/;
748 } elsif( $lang =~ /^sr$/ ){
749 @months_1 = qw/jan feb mar apr maj jun jul aug sep okt nov dec/;
750 @months_2 = qw/januar februar mart april maj jun juli avgust septembar oktobar novembar decembar/;
751 @months_3 = qw/januara februara marta aprila maja juna jula avgusta septembra oktobra novembra decembra/;
752 @months_4 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
753 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
754 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
755 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
756 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
757 } elsif( $lang =~ /^it$/ ){
758 @months_1 = qw/gen feb mar apr mag giu lug ago set ott nov dic/;
759 @months_2 = qw/gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre/;
760 @months_3 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
761 @months_4 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
762 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
763 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
764 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
765 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
766 } elsif( $lang =~ /^fr$/ ){
767 @months_1 = qw/jan fav mar avr mai jui jul aou sep oct nov dec/;
768 @months_2 = qw/JANVIER FÉVRIER mars avril mai juin juillet Août septembre octobre novembre DÉCEMBRE/;
769 @months_3 = qw/janvier favrier mMARS AVRIL MAI JUIN juillet AOÛT septembre octobre novembre DÉCEMBRE/;
770 @months_4 = qw/1 Février 3 4 5 6 7 8 9 10 11 12/;
771 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
772 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
773 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
774 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
775 } elsif( $lang =~ /^ru$/ ){
776 @months_1 = qw/jan fav mar aprelja maja jui jul aou sep oct nov dec/;
777 @months_2 = qw/JANVIER FÉVRIER mars avril mai juin juillet aout septembre octobre novembre DÉCEMBRE/;
778 @months_3 = qw/janvier favrier mars avril mai juin juillet aout septembre octobre novembre DÉCEMBRE/;
779 @months_4 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
780 @months_5 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
781 @months_6 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
782 @months_7 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
783 @months_8 = qw/1 2 3 4 5 6 7 8 9 10 11 12/;
786 my %monthnames = ();
788 for( my $i = 0; $i < scalar(@months_1); $i++ ){
789 $monthnames{$months_1[$i]} = $i+1;
792 for( my $i = 0; $i < scalar(@months_2); $i++ ){
793 $monthnames{$months_2[$i]} = $i+1;
796 for( my $i = 0; $i < scalar(@months_3); $i++ ){
797 $monthnames{$months_3[$i]} = $i+1;
800 for( my $i = 0; $i < scalar(@months_4); $i++ ){
801 $monthnames{$months_4[$i]} = $i+1;
804 for( my $i = 0; $i < scalar(@months_5); $i++ ){
805 $monthnames{$months_5[$i]} = $i+1;
808 for( my $i = 0; $i < scalar(@months_6); $i++ ){
809 $monthnames{$months_6[$i]} = $i+1;
812 for( my $i = 0; $i < scalar(@months_7); $i++ ){
813 $monthnames{$months_7[$i]} = $i+1;
816 for( my $i = 0; $i < scalar(@months_8); $i++ ){
817 $monthnames{$months_8[$i]} = $i+1;
820 my $month = $monthnames{$monthname};
821 my $lcmonth = $monthnames{lc $monthname};
823 return $month||$lcmonth;
826 =begin nd
828 Function: CompareArrays
830 Compare two arrays (new and old) and call functions to reflect added,
831 deleted and unchanged entries.
833 Parameters:
835 $new - A reference to the new array
836 $old - A reference to the old array
837 $cb - A hashref with callback functions
839 CompareArrays calls the following callback functions:
841 $cb->{cmp}( $enew, $eold ) - Compare an entry from $new with an
842 entry from $old. Shall return -1 if $ea is
843 less than $eb, 0 if they are equal and 1
844 otherwise.
846 $cb->{added}($enew) - Called for all entries that are present in
847 $new but not in $old.
849 $cb->{deleted}($eold) - Called for all entries that are present in
850 $old but not in $new.
852 $cb->{equal}($enew, $eold) - Called for all entries that are present in
853 both $new and $old.
855 Additionally, $cb->{max} shall contain an entry that is always
856 regarded as greater than any possible entry in $new and $old.
858 Returns:
860 nothing
862 =cut
864 sub CompareArrays #( $new, $old, $cb )
866 my( $new, $old, $cb ) = @_;
868 my @a = sort { $cb->{cmp}( $a, $b ) } @{$new};
869 my @b = sort { $cb->{cmp}( $a, $b ) } @{$old};
871 push @a, $cb->{max};
872 push @b, $cb->{max};
874 my $ia = 0;
875 my $ib = 0;
877 while( 1 ) {
878 my $da = $a[$ia];
879 my $db = $b[$ib];
881 # If both arrays have reached the end, we are done.
882 if( ($cb->{cmp}($da, $cb->{max}) == 0) and
883 ($cb->{cmp}($db, $cb->{max}) == 0 ) ) {
884 last;
887 my $cmp = $cb->{cmp}($da, $db);
889 if( $cmp == 0 ) {
890 $cb->{equal}($da, $db);
891 $ia++;
892 $ib++;
894 elsif( $cmp < 0 ) {
895 $cb->{added}( $da );
896 $ia++;
898 else {
899 $cb->{deleted}($db);
900 $ib++;
907 ### Setup coding system
908 ## Local Variables:
909 ## coding: utf-8
910 ## End: