#!perl # SPDX-License-Identifier: GPL-2.0-only use strict; use warnings; use Getopt::Long; use JSON::MaybeXS; use Encode; my %Options; my $Translations = {}; my $rem2html_version = '@VERSION@'; my($days, $shades, $moons, $classes, $Month, $Year, $Numdays, $Firstwkday, $Mondayfirst, $weeks, @Daynames, $Nextmon, $Nextlen, $Prevmon, $Prevlen); my $TIDY_PROGNAME = $0; $TIDY_PROGNAME =~ s|^.*/||; # rem2html -- convert the output of "remind -pp" or "remind -ppp" to HTML =head1 NAME rem2html - Convert the output of "remind -pp" or "remind -ppp" to HTML =head1 SYNOPSIS remind -ppp [remind_options] file | rem2html [options] remind -pp [remind_options] file | rem2html [options] You can also use the old interchange format as below, but the -pp or -ppp versions are preferred. remind -p [remind_options] file | rem2html [options] =head1 OPTIONS =over 4 =item --help, -h Print usage information =item --version Print version =item --utf8 Assume standard input is encoded in UTF-8; write UTF-8 data to standard output. =item --backurl I When producing the small calendar for the previous month, make the month name a link to I. =item --forwurl I When producing the small calendar for the next month, make the month name a link to I. =item --imgbase I When creating URLs for the stylesheet or external images, use I as the base URL. =item --pngs Normally, rem2html uses inline "data:" URLs for the moon phase images, yielding a standalone HTML file. The C<--pngs> option makes it use external images named firstquarter.png, fullmoon.png, lastquarter.png and newmoon.png, which are expected to live in C<--imgbase>. =item --stylesheet I Use I as the stylesheet. If this option is used, I is interpreted relative to B I it starts with a "/". =item --nostyle Produce basic HTML that does not use a CSS stylesheet. =item --tableonly Output results as a EtableE ... E/tableE sequence only without any EhtmlE or EbodyE tags. =item --title I Use I<title> as the content between E<lt>titleE<gt> and E<lt>/titleE<gt> tags. =item --yaag Instead of making 7-column tables for each month, show each month as a I<single> table row of 38 columns. This is the so-called "year-at-a-glance" format. You must use the "-ppp" option to C<remind> or the --yaag option will be ignored. And this format works best if you create a calendar for an entire year (so, starting in January and using "-ppp12" to get 12 months' worth of reminders.) =item --prologue I<html_text> Insert I<html_text> right after the E<lt>bodyE<gt> tag. =item --epilogue I<html_text> Insert I<html_text> right before the E<lt>/bodyE<gt> tag. =back =head1 SPECIALS SUPPORTED The rem2html back-end supports the following SPECIAL reminders: =over =item HTML Add an HTML reminder to the calendar. All HTML tags are available. =item HTMLCLASS Add a CSS class to the box representing the trigger date. See "HIGHLIGHTING TODAY" for an example =item WEEK, MOON, SHADE, COLOR The standard SPECIALs supported by all back-ends =back =head1 INFO STRINGS SUPPORTED Rem2html supports one INFO string, namely C<Url>. Its value should be a URL. If the C<Url> INFO string is supplied for a normal reminder, a COLOR special, a MOON special or a WEEK special, the corresponding output is turned into a hyperlink. =head1 HIGHLIGHTING TODAY Older versions of rem2html used to highlight today's date with a red outline. The current version does not do that by default. If you wish to highlight today's date, add the following reminder to your reminders file: REM [realtoday()] SPECIAL HTMLCLASS rem-today =head1 AUTHOR rem2html was written by Dianne Skoll with much inspiration from an earlier version by Don Schwarz. =head1 HOME PAGE L<https://dianne.skoll.ca/projects/remind/> =head1 SEE ALSO B<remind>, B<rem2ps>, B<rem2pdf>, B<tkremind> =cut sub usage { my ($exit_status) = @_; if (!defined($exit_status)) { $exit_status = 1; } print STDERR <<"EOM"; $TIDY_PROGNAME: Produce an HTML calendar from the output of "remind -pp[p]" Usage: remind -pp [remind_options] file | rem2html [options] or: remind -ppp [remind_options] file | rem2html [options] Options: --help, -h Print usage information --utf8 Assume UTF-8 input and write UTF-8 output --man Show man page (requires "perldoc") --version Print version --backurl url Make the title on the previous month's small calendar entry a link to <url> --forwurl url Same as --backurl, but for the next month's small calendar --imgbase url Base URL of images and default stylesheet file --pngs Use external .PNG images for moon phases rater than inline data: URLs --stylesheet url.css URL of CSS stylesheet. If specified, imgbase is NOT prepended to url.css --nostyle Produce basic HTML that does not use a CSS stylesheet --tableonly Output results as a <table> only, no <html>, <body>, etc. --title string What to put in <title>... tags --prologue html_text Text to insert at the top of the body --epilogue html_text Text to insert at the end of the body --yaag Output one month per row for year-at-a-glance format EOM exit($exit_status); } sub smoosh { my ($first, $second) = @_; return $second unless defined ($first); return $second if $first eq ''; return $second if ($second =~ m|^/|); # Absolute path given for second # Squash multiple slashes $first =~ s|/+|/|g; # Special case return "/$second" if ($first eq '/'); # Delete trailing slash $first =~ s|/$||; return "$first/$second"; } sub parse_options { local $SIG{__WARN__} = sub { print STDERR "$TIDY_PROGNAME: $_[0]\n"; }; if (!GetOptions(\%Options, "help|h", "man", "utf8", "pngs", "yaag", "version", "stylesheet=s", "nostyle", "backurl=s", "forwurl=s", "title=s", "prologue=s", "epilogue=s", "imgbase=s", "tableonly")) { usage(1); } $Options{title} ||= 'HTML Calendar'; my $stylesheet = $Options{stylesheet}; if ($stylesheet) { $Options{stylesheet} = smoosh($Options{imgbase}, $stylesheet); } if ($Options{utf8}) { binmode(STDIN, ':encoding(UTF-8)'); binmode(STDOUT, ':encoding(UTF-8)'); } } sub start_output { return if ($Options{tableonly}); print("\n\n"); if ($Options{utf8}) { print '' . "\n"; } print("" . $Options{title} . "\n"); if (!$Options{nostyle}) { if ($Options{stylesheet}) { print('' . "\n"); } else { print("\n"); } } print("\n\n"); if ($Options{prologue}) { print $Options{prologue} . "\n"; } } sub end_output { return if ($Options{tableonly}); if ($Options{epilogue}) { print $Options{epilogue} . "\n"; } print("\n\n"); } sub slurp_translations { my $line; $line = ; chomp $line; eval { if ($Options{utf8}) { $Translations = decode_json(encode('UTF-8', $line, Encode::FB_DEFAULT)); } else { $Translations = decode_json($line); } }; if ($@) { $Translations = {}; } } sub t { my ($str) = @_; return $Translations->{$str} if exists($Translations->{$str}); return $str; } sub parse_input { undef $days; undef $shades; undef $moons; undef $classes; undef $weeks; my $found_data = 0; while() { chomp; if ($_ eq '[') { return parse_input_ppp(); } if ($Options{yaag}) { $Options{yaag} = 0; print STDERR "Ignoring option --yaag: You must use remind -ppp for this option\n"; } if (/# translations/) { slurp_translations(); next; } last if /^\# rem2ps2? begin$/; } my $line; # Month Year numdays firstday monday_first_flag $line = ; return 0 unless $line; chomp($line); ($Month, $Year, $Numdays, $Firstwkday, $Mondayfirst) = split(' ', $line); $Month =~ s/_/ /g; # Day names $line = ; return 0 unless $line; chomp($line); @Daynames = split(' ', $line); for (my $i=0; $i<7; $i++) { $Daynames[$i] =~ s/_/ /g; } # Prevmon prevlen $line = ; return 0 unless $line; chomp($line); ($Prevmon, $Prevlen) = split(' ', $line); $Prevmon =~ s/_/ /g; # Nextmon nextlen $line = ; return 0 unless $line; chomp($line); ($Nextmon, $Nextlen) = split(' ', $line); $Nextmon =~ s/_/ /g; $found_data = 1; my $class; if ($Options{nostyle}) { $class = ''; } else { $class = ' class="rem-entry"'; } while() { chomp; last if /^\# rem2ps2? end$/; next if /^\#/; my ($y, $m, $d, $special, $tag, $duration, $time, $body, $title); my $url_pre = ''; my $url_post = ''; my $url; if (m/^(\d*).(\d*).(\d*)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/) { ($y, $m, $d, $special, $tag, $duration, $time, $body) = ($1, $2, $3, $4, $5, $6, $7, $8); $title = ''; } elsif (/\{/) { my $obj; if ($Options{utf8}) { $obj = decode_json(encode('UTF-8', $_, Encode::FB_DEFAULT)); } else { $obj = decode_json($_); } next unless ($obj->{date} =~ /^(\d+)-(\d+)-(\d+)$/); if ($obj->{info} && $obj->{info}->{url}) { $url = $obj->{info}->{url}; $url_pre = ""; $url_post = ""; } $y = $1; $m = $2; $d = $3; $special = $obj->{passthru} || '*'; $tag = $obj->{tags} || '*'; $duration = $obj->{duration} || '*'; $time = $obj->{time} || '*'; $body = $obj->{body}; $title = info_to_title($obj->{info}); } else { next; } my $d1 = $d; $d1 =~ s/^0+//; $special = uc($special); if ($special eq 'HTML') { push(@{$days->[$d]}, $body); } elsif ($special eq 'HTMLCLASS') { $classes->[$d] = $body; } elsif ($special eq 'WEEK') { $body =~ s/^\s+//; $body =~ s/\s+$//; $weeks->{$d1}->{body} = $body; $weeks->{$d1}->{url} = $url; } elsif ($special eq 'MOON') { if ($body =~ /(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) { my ($phase, $moonsize, $fontsize, $msg) = ($1, $2, $3, $4); $moons->[$d]->{'phase'} = $phase; $moons->[$d]->{'msg'} = $msg; } elsif ($body =~ /(\S+)/) { $moons->[$d]->{'phase'} = $1; $moons->[$d]->{'msg'} = ''; } $moons->[$d]->{url} = $url; } elsif ($special eq 'SHADE') { if ($body =~ /(\d+)\s+(\d+)\s+(\d+)/) { $shades->[$d] = sprintf("#%02X%02X%02X", ($1 % 256), ($2 % 256), ($3 % 256)); } elsif ($body =~ /(\d+)/) { $shades->[$d] = sprintf("#%02X%02X%02X", ($1 % 256), ($1 % 256), ($1 % 256)); } } elsif ($special eq 'COLOR' || $special eq 'COLOUR') { if ($body =~ /(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/s) { my($r, $g, $b, $text) = ($1, $2, $3, $4); my $color = sprintf("style=\"color: #%02X%02X%02X;\"", $r % 256, $g % 256, $b % 256); push(@{$days->[$d]}, "" . $url_pre . fix_whitespace(escape_html($text)) . $url_post . '

'); } } elsif ($special eq '*') { push(@{$days->[$d]}, "" . $url_pre . fix_whitespace(escape_html($body)) . $url_post . '

'); } } return $found_data; } sub parse_input_ppp { my $json = "[\n"; my $curlies = 0; my $did_a_calendar = 0; while() { $json .= $_; $curlies++ if ($_ eq "{\n"); $curlies-- if ($_ eq "}\n"); $curlies-- if ($_ eq "},\n"); if ($_ eq "]\n" && !$curlies) { my $array; eval { if ($Options{utf8}) { $array = decode_json(encode('UTF-8', $json, Encode::FB_DEFAULT)); } else { $array = decode_json($json); } }; if (!$array) { print STDERR "Could not decode JSON.\n"; exit(1); } if (!$did_a_calendar) { start_output(); $did_a_calendar = 1; } if (exists($array->[0]{caltype}) && $array->[0]{caltype} eq 'weekly') { if ($Options{yaag}) { $Options{yaag} = 0; print STDERR "Ignoring option --yaag for a weekly calendar\n"; } emit_ppp_calendars($array, 'weekly'); } else { emit_ppp_calendars($array, 'monthly'); } $json = ''; } } if (!$did_a_calendar) { print STDERR "$TIDY_PROGNAME: Could not find any calendar data on STDIN.\n"; exit(1); } else { end_output(); exit(0); } } sub print_yaag_weekday_row { my ($cal) = @_; print ""; print " "; my $ex; for (my $i=0; $i<37; $i++) { my $day; if ($cal->{mondayfirst}) { $day = $cal->{daynames}->[($i+1) % 7]; } else { $day = $cal->{daynames}->[$i % 7]; } if (($i % 7) == 6) { $ex = " rem-yaag-end-of-week"; } else { $ex = ""; } print "$day"; } print "\n"; } sub emit_ppp_calendars { my ($array, $type) = @_; my $prev_year = 0; my $done_one = 0; foreach my $cal (@$array) { if ($Options{yaag}) { if ($cal->{year} != $prev_year) { $prev_year = $cal->{year}; if ($done_one) { print "\n"; } print "$prev_year\n"; print "\n"; print_yaag_weekday_row($cal); } } emit_one_ppp_calendar($cal, $type); $done_one = 1; } if ($done_one) { print "
\n"; } } sub info_to_title { my ($info) = @_; if (!$info) { return ''; } my $done_one = 0; my $title = ''; foreach my $key ('location', 'description') { next unless $info->{$key}; if ($done_one) { $title .= " "; } $done_one = 1; my $val = escape_html($info->{$key}); $val =~ s/\n/ /g; $val =~ s/"/"/g; $val =~ s/{translations})) { $Translations = $c->{translations}; } if ($type eq 'monthly') { $Month = $c->{monthname}; $Year = $c->{year}; $Numdays = $c->{daysinmonth}; $Firstwkday = $c->{firstwkday}; $Mondayfirst = $c->{mondayfirst}; @Daynames = @{$c->{daynames}}; $Prevmon = $c->{prevmonthname}; $Prevlen = $c->{daysinprevmonth}; $Nextmon = $c->{nextmonthname}; $Nextlen = $c->{daysinnextmonth}; } else { my $idx = 0; $Numdays = 7; foreach my $date (@{$c->{dates}}) { $Daynames[$idx] = $date->{dayname}; $dates_to_day_index->{$date->{date}} = $idx; $cols_to_date_info->[$idx] = $date; $idx++; } } my $class; if ($Options{nostyle}) { $class = ''; } else { $class = ' class="rem-entry"'; } foreach my $obj (@{$c->{entries}}) { next unless ($obj->{date} =~ /^(\d+)-(\d+)-(\d+)$/); my $y = $1; my $m = $2; my $d = $3; my $col; if ($type eq 'weekly') { $col = $dates_to_day_index->{$obj->{date}}; } else { $col = $d; $col =~ s/^0+//; } my $special = $obj->{passthru} || '*'; my $tag = $obj->{tags} || '*'; my $duration = $obj->{duration} || '*'; my $time = $obj->{time} || '*'; my $body = $obj->{body}; my $title = info_to_title($obj->{info}); my $url_pre = ''; my $url_post = ''; my $url = ''; if ($obj->{info} && $obj->{info}->{url}) { $url = $obj->{info}->{url}; $url_pre = ""; $url_post = ""; } $special = uc($special); if ($special eq 'HTML') { push(@{$days->[$col]}, $body); } elsif ($special eq 'HTMLCLASS') { $classes->[$col] = $body; } elsif ($special eq 'WEEK') { $body =~ s/^\s+//; $body =~ s/\s+$//; $weeks->{$col}->{body} = $body; $weeks->{$col}->{url} = $url; } elsif ($special eq 'MOON') { if ($body =~ /(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) { my ($phase, $moonsize, $fontsize, $msg) = ($1, $2, $3, $4); $moons->[$col]->{'phase'} = $phase; $moons->[$col]->{'msg'} = $msg; } elsif ($body =~ /(\S+)/) { $moons->[$col]->{'phase'} = $1; $moons->[$col]->{'msg'} = ''; } $moons->[$col]->{url} = $url; } elsif ($special eq 'SHADE') { if ($body =~ /(\d+)\s+(\d+)\s+(\d+)/) { $shades->[$col] = sprintf("#%02X%02X%02X", ($1 % 256), ($2 % 256), ($3 % 256)); } elsif ($body =~ /(\d+)/) { $shades->[$col] = sprintf("#%02X%02X%02X", ($1 % 256), ($1 % 256), ($1 % 256)); } } elsif ($special eq 'COLOR' || $special eq 'COLOUR') { if ($body =~ /(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/s) { my($r, $g, $b, $text) = ($1, $2, $3, $4); my $color = sprintf("style=\"color: #%02X%02X%02X;\"", $r % 256, $g % 256, $b % 256); push(@{$days->[$col]}, "" . $url_pre . fix_whitespace(escape_html($text)) . $url_post . '

'); } } elsif ($special eq '*') { push(@{$days->[$col]}, "" . $url_pre . fix_whitespace(escape_html($body)) . $url_post . '

'); } } output_calendar($type, $cols_to_date_info); } sub fix_whitespace { my ($text) = @_; # Collapse multiple spaces/tabs to a single space $text =~ s/[ \t]+/ /gs; # Remove whitespace before/after newlines $text =~ s/\s+\n/\n/gs; $text =~ s/\n\s+/\n/gs; # Collapse multiple newlines to a single newline $text =~ s/\n+/\n/gs; # Convert newlines to
$text =~ s|\n|
|g; return $text; } sub small_calendar { my($month, $monlen, $url, $first_col) = @_; if ($Mondayfirst) { $first_col--; if ($first_col < 0) { $first_col = 6; } } if ($Options{nostyle}) { print "\n"; print "\n"; print "\n"; } sub output_calendar { # Which column is 1st of month in? my ($type, $date_info) = @_; my ($first_col, $last_col, $number_of_rows); if ($type eq 'monthly') { $first_col = $Firstwkday; if ($Mondayfirst) { $first_col--; if ($first_col < 0) { $first_col = 6; } } # Last column $last_col = ($first_col + $Numdays - 1) % 7; # Figure out how many rows $number_of_rows = int(($first_col + $Numdays ) / 7 + 0.999); # Add a row for small calendars if necessary if ($first_col == 0 && $last_col == 6) { $number_of_rows++; } } else { $first_col = 0; $last_col = 6; $number_of_rows = 1; } # Start the table my $class; if (!$Options{yaag}) { if ($Options{nostyle}) { print '
"; } else { print "
\n"; print "\n"; print "\n"; my $class; if ($Options{nostyle}) { print ''; $class = ' align="right"'; } else { print ''; $class = ' class="rem-sc-hdr"'; } if (!$Mondayfirst) { print "" . substr($Daynames[0], 0, 1) . ''; } for (my $i=1; $i<7; $i++) { print "" . substr($Daynames[$i], 0, 1) . ''; } if ($Mondayfirst) { print "" . substr($Daynames[0], 0, 1) . ''; } print("\n"); my $col = 0; for (; $col<$first_col; $col++) { if ($col == 0) { print("\n"); } if ($Options{nostyle}) { print(""); } else { print(""); } } for (my $day=1; $day <= $monlen; $day++) { if ($col == 0) { print("\n"); } $col++; if ($Options{nostyle}) { print(""); } else { print(""); } if ($col == 7) { print("\n"); $col = 0; } } if ($col) { while ($col < 7) { if ($Options{nostyle}) { print(""); } else { print(""); } $col++; } print("\n"); } print("
"; } print "" if ($url); print $month; print "" if ($url); print "
  
$day$day
  
\n"); print "
'; if ($type eq 'monthly') { print ""; } print "\n"; $class = ' width="14%"'; } else { if ($type eq 'monthly') { print '
$Month $Year
' . "\n"; } else { print '
' . $Month . ' ' . $Year . '
' . "\n"; } print ''; $class = ' class="rem-cal-hdr"'; } if ($type eq 'monthly') { if (!$Mondayfirst) { print "" . $Daynames[0] . ''; } for (my $i=1; $i<7; $i++) { print "" . $Daynames[$i] . ''; } if ($Mondayfirst) { print "" . $Daynames[0] . ''; } } else { for (my $i=0; $i<7; $i++) { my $inf = $date_info->[$i]; print "" . $inf->{dayname} . "
" . $inf->{day} . ' ' . $inf->{month} . ' ' . $inf->{year} . ''; } } print "
\n"; } else { # For year-at-a-glance calendar, it's just a row print ''; print '\n"; my $cells_drawn = 0; # Print the blank ones for (my $i=0; $i<$first_col; $i++) { print ""; $cells_drawn++; } # All the entries for (my $day=1; $day <= $Numdays; $day++) { if (($cells_drawn % 7) == 6) { draw_day_cell($day, 1, $type, ' rem-yaag-end-of-week'); } else { draw_day_cell($day, 1, $type); } $cells_drawn++; } # And fill in the remaining ones while ($cells_drawn < 37) { if (($cells_drawn % 7) == 6) { print ""; } else { print ""; } $cells_drawn++; } print "\n"; return; } # Start the calendar rows my $col = 0; if ($Options{nostyle}) { print "\n"; } else { print "\n"; } if ($type eq 'monthly') { if ($first_col > 0) { small_calendar($Prevmon, $Prevlen, $Options{backurl}, ($Firstwkday - $Prevlen + 35) % 7); $col++; } if ($last_col == 6 && $first_col > 0) { small_calendar($Nextmon, $Nextlen, $Options{forwurl}, ($Firstwkday + $Numdays) % 7); $col++; } } if ($Options{nostyle}) { $class = ' width="14%"'; } else { $class = ' class="rem-empty rem-empty-$number_of_rows-rows"'; } while ($col < $first_col) { print(" \n"); $col++; } for (my $day=1; $day<=$Numdays; $day++) { draw_day_cell($day, $number_of_rows, $type); $col++; if ($col == 7) { $col = 0; print "\n"; if ($day < $Numdays) { if ($Options{nostyle}) { print "\n"; } else { print "\n"; } } } } if ($type eq 'monthly') { if ($col) { while ($col < 7) { if ($col == 5) { if ($first_col == 0) { small_calendar($Prevmon, $Prevlen, $Options{backurl}, ($Firstwkday - $Prevlen + 35) % 7); } else { print(" \n"); } } elsif ($col == 6) { small_calendar($Nextmon, $Nextlen, $Options{forwurl}, ($Firstwkday + $Numdays) % 7); } else { print(" \n"); } $col++; } print "\n"; } # Add a row for small calendars if they were not yet done! if ($first_col == 0 && $last_col == 6) { if ($Options{nostyle}) { print "\n"; } else { print "\n"; } small_calendar($Prevmon, $Prevlen, $Options{backurl}, ($Firstwkday - $Prevlen + 35) % 7); for (my $i=0; $i<5; $i++) { print(" \n"); } small_calendar($Nextmon, $Nextlen, $Options{forwurl}, ($Firstwkday + $Numdays) % 7); print("\n"); } } # End the table print "
' . $Month . "   
\n"; if ($type eq 'weekly') { print " 
\n"; } } sub draw_day_cell { my($day, $number_of_rows, $type, $extra_class) = @_; my $shade = $shades->[$day]; my $week = ''; $extra_class = '' unless defined($extra_class); if (exists($weeks->{$day})) { if ($weeks->{$day}->{url}) { $week = ' ' . escape_html($weeks->{$day}->{body}) . ''; } else { $week = ' ' . escape_html($weeks->{$day}->{body}); } } my $class = $classes->[$day] || ''; if (!$Options{nostyle}) { $class .= ' ' if $class ne ''; if ($Options{yaag}) { $class .= "rem-yaag-cell" } else { $class .= "rem-cell rem-cell-$number_of_rows-rows"; } } if ($shade) { $shade = " style=\"background: $shade;\""; } else { $shade = ""; } if ($class ne '' || $extra_class ne '') { print "\n"; } else { print "\n"; } if ($moons->[$day]) { my $phase = $moons->[$day]->{'phase'}; my $msg = $moons->[$day]->{'msg'}; $msg ||= ''; if ($msg ne '') { $msg = ' ' . escape_html($msg); } my $img; my $alt; my $title; if ($phase == 0) { if ($Options{pngs}) { $img = smoosh($Options{imgbase}, 'newmoon.png'); } else { $img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC6SURBVDiNpdNNbsIwFATgL0HKolchHKBX6yFaBOEyoPYUabvOIVKJRaCL2JX5TRNGGvnJ8ozGz89cYoElPvET+BX2yivn/1Bggw5HHMKa1h2qcPZC/JEIhvh+brIZIY6sorhMYo9hh3KGFzzfa84NZNjDt9OG/ZcH1BlaPE1IAG0+URhxzNGESKPFaHJs9Q0Ziww7HnvGeXSrJhis0jiFfjwnj3I0WRv+TKtr4hQl3lDrZ6QN9Wt654hfWfGDmBpUwDkAAAAASUVORK5CYII='; } $title = escape_html(t('New Moon')); $alt = 'new'; } elsif ($phase == 1) { if ($Options{pngs}) { $img = smoosh($Options{imgbase}, 'firstquarter.png'); } else { $img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADfSURBVDiNndM9TsNAFATgzy5yjZSAE85JBygETgENUPF3iBCitHAFQkcIhZ/Ryn9gRlrZmp2Z3ef3TBOHOMULPrDBMrhpi/4HI5xjix2+4nmJRbx/Yh7ahvkpRPVV4QDXwT3UQy46zGkAZDgK/iytefvHgCrkJsqZUH6cLnNbABSxd5Jhhf1IbkMXv8Qux7hH1Ic1xvk/jBWy6gavumvtwx7ectwZXkKh7MA95XgObeOtpI2U4zl0kGbpxgiPvwQUcXLrKFchc82f6Ur0PK49azOnmOI4TBu84zm4SV38DeIVYkrYJyNbAAAAAElFTkSuQmCC'; } $title = escape_html(t('First Quarter')); $alt = '1st'; } elsif ($phase == 2) { if ($Options{pngs}) { $img = smoosh($Options{imgbase}, 'fullmoon.png'); } else { $img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADlSURBVDiNrdNBUsJAEAXQlyw4hq4hwWPqTixET6ELkZ16CcAq7oFLqXExjaYgQVNlV/Viev7/6XT/4TjGuME7PiLXUatb8N8xwB12SFjiIXIZtU/MAntEfgvQE4YtHxhiHpjXQ5H7uLhEcaLLAleBvd0Xx9Ha/BdyU+Q5OBV5OKmj7a4YBWdSyNPe4aKHAHkzqcQZNj3JgnNexqE8heyIAulffuFF3kTfIVbBVeu/xoXGGsn2TLJJ/mqkafNiINszySYZdbS90GHlvcgsWktY4TFy7ecxTdvIzahxHQLbyFXUqkPwF2ASRNYgB/PXAAAAAElFTkSuQmCC'; } $alt = 'full'; $title = escape_html(t('Full Moon')); } else { if ($Options{pngs}) { $img = smoosh($Options{imgbase}, 'lastquarter.png'); } else { $img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADmSURBVDiNndMxTsNAEIXhzy5yCyQ6FAgcE7oQheQWUAAl5BIkREoZrgB0GFNkHBl7bURGsryaee/3jHeXdpxjghU+8InXyI0S+n0MMEeBEi+4jfV3vAvMQtsyL0J0j2GtViaeRRMyj8IlsgY8BSijE2Kur/hy09wHKMJrEolhwtwHKDHOsI4OLnoAXfl1jiNsOkR9keE4P8D4q4scbzg5xIxtjie709f1E7siC+9+Gx/8fxvPKtEsklcJSBdgWhcN8ByFR5z+AWgd5QpyE+OUWOJO+zJNU+Z6jHAdgHe7K73CuD5zFT9nCmRDIssCaAAAAABJRU5ErkJggg=='; } $alt = 'last'; $title = escape_html(t('Last Quarter')); } my $url_pre = ''; my $url_post = ''; if ($moons->[$day]->{url}) { $url_pre = ''; $url_post = ''; } if ($Options{nostyle}) { print("
$url_pre\"$alt\"msg$url_post
"); } else { print("
$url_pre\"$alt\"$msg$url_post
"); } } if ($type eq 'monthly') { if ($Options{nostyle}) { print "
$day$week
\n"; print "

 

\n"; } else { if ($Options{yaag}) { print "
$day$week
\n"; } else { print "
$day$week
\n"; } } } else { if ($Options{nostyle}) { print "
$week
\n"; print "

 

\n"; } else { print "
$week
\n"; } } if ($days->[$day]) { print(join("\n", @{$days->[$day]})); } print "\n"; } sub escape_html { my($in) = @_; $in =~ s/\&/\&/g; $in =~ s/\/\>/g; return $in; } parse_options(); if ($Options{help}) { usage(0); exit(0); } elsif ($Options{man}) { system("perldoc $0"); exit(0); } elsif ($Options{version}) { print "rem2html version $rem2html_version\n"; exit(0); } if (-t STDIN) { ## no critic print STDERR "$TIDY_PROGNAME: Input should not come from a terminal.\n\n"; usage(1); } my $found_something = 0; while(1) { last if (!parse_input()); start_output() unless $found_something; $found_something = 1; output_calendar('monthly', undef); } if ($found_something) { end_output(); exit(0); } else { print STDERR "$TIDY_PROGNAME: Could not find any calendar data on STDIN.\n"; exit(1); } sub default_stylesheet { return <<'EOF'; table.rem-cal { font-family: helvetica, arial, sans-serif; font-size: 12pt; } span.year_heading { font-family: helvetica, arial, sans-serif; font-size: 28pt; } table.rem-sc-table { font-family: helvetica, arial, sans-serif; font-size: 10pt; width: 95%; float: left; } caption.rem-cal-caption { font-size: 14pt; font-weight: bold; } th.rem-cal-hdr { width: 14%; border-style: solid; border-width: 1px; vertical-align: top; } td.rem-empty, td.rem-cell, td.rem-small-calendar { width: 14%; height: 7em; border-style: solid; border-width: 1px; vertical-align: top; } table.rem-cal { width: 100%; border-collapse: collapse; } div.rem-daynumber { float: right; text-align: right; vertical-align: top; font-size: 14pt; } p.rem-entry { clear: both; } div.rem-moon { float: left; text-align: left; vertical-align: top; } th.rem-sc-hdr { text-align: right; } td.rem-sc-empty-cell, td.rem-sc-cell { text-align: right; width: 14%; } caption.rem-sc-caption { font-size: 12pt; } table.rem-yaag { font-family: helvetica, arial, sans-serif; font-size: 8pt; table-layout: fixed; cellspacing: 0; border-collapse: collapse; } td.rem-yaag-cell { height: 7em; border-style: solid; border-color: #adadad; border-width: 1px; vertical-align: top; } th.rem-yaag-weekday-header { font-weight: 100; font-size: 9pt; padding: 6px 0 0 6px; text-align:left; border-style: solid; border-color: #adadad; border-width: 1px; vertical-align: top; } td.rem-yaag-cal-blank { height: 7em; border-style: solid; border-width: 1px; border-color: #adadad; background-color: #d5d6d5; vertical-align: top; } td.rem-yaag-end-of-week { border-right-width: 3; } th.rem-yaag-end-of-week { border-right-width: 3; } th.rem-yaag-cal-hdr { text-align: right; padding-top: 8px; padding-right: 5px; font-weight: 100; font-size: 9pt; border-style: solid; border-color: #adadad; border-width: 1px; vertical-align: top; } div.rem-yaag-daynumber { float: right; text-align: right; vertical-align: top; font-size: 10pt; } tr.rem-yaag-cal-row:nth-child(3n+1) { border-bottom: 3px solid #000; } .rem-yaag-weekday-row .rem-yaag-empty-header { height: 20pt; width: 70px !important; } td.rem-today { border-style: solid; border-width: 2px; border-color: #EE3333; } EOF }