#!/usr/bin/perl -w =pod =head1 NAME tv_to_latex - Convert XMLTV listings to LaTeX source. =head1 SYNOPSIS tv_to_latex [--help] [--with-desc] [--output FILE] [FILE...] =head1 DESCRIPTION Read XMLTV data and output LaTeX source for a summary of listings. The programme titles, subtitles, times and channels are shown. B<--with-desc> include programme description in output B<--output FILE> write to FILE rather than standard output =head1 SEE ALSO L. =head1 AUTHOR Ed Avis, ed@membled.com =head1 BUGS The LaTeX source generated is not perfect, it sometimes produces spurious blank lines in the output. =cut use strict; use XMLTV; use XMLTV::Version "$XMLTV::VERSION"; use IO::File; use Getopt::Long; # Use Log::TraceMessages if installed. BEGIN { eval { require Log::TraceMessages }; if ($@) { *t = sub {}; *d = sub { '' }; } else { *t = \&Log::TraceMessages::t; *d = \&Log::TraceMessages::d; Log::TraceMessages::check_argv(); } } # Use Unicode::String if installed, else kludge. my $warned_strip = 0; sub my_u8_to_latin1( $ ) { local $_ = shift; tr/\000-\177//cd && (not $warned_strip++) && warn "stripping non-ASCII characters, install Unicode::String\n"; return $_; } BEGIN { eval { require Unicode::String }; if ($@) { *u8_to_latin1 = \&my_u8_to_latin1; } else { *u8_to_latin1 = sub { Unicode::String::utf8($_[0])->latin1() }; } } use XMLTV::Summarize qw(summarize); use XMLTV::Usage < \$opt_help, 'output=s' => \$opt_output, 'with-desc' => \$opt_withdesc) or usage(0); usage(1) if $opt_help; @ARGV = ('-') if not @ARGV; if (defined $opt_output) { open(STDOUT, ">$opt_output") or die "cannot write to $opt_output: $!"; } $opt_withdesc = 0 if !defined $opt_withdesc; ######## # Configuration # # Width of programme title my $WIDTH = '0.7\textwidth'; $WIDTH = '0.35\textwidth' if $opt_withdesc; # adjust column width if outputting description # Number of programmes in each table (should fit onto a page) my $CHUNK_SIZE = 30; # at least 1, please ######## # End of configuration # # FIXME maybe memoize some stuff here # Print the start of the LaTeX document. print <<'END'; \documentclass[a4paper]{article} \usepackage[latin1]{inputenc} \begin{document} \sf \begin{flushleft} END # Read input and work out how to handle its encoding. my ($encoding, $credits, $ch, $progs) = @{XMLTV::parsefiles(@ARGV)}; my $to_latin1; for ($encoding) { START: if (not defined) { warn "encoding of input unknown, assuming ISO-8859-1\n"; $_ = 'ISO-8859-1'; goto START; } elsif (/^UTF-?8$/) { $to_latin1 = \&u8_to_latin1; } elsif (/^(?:US-?)ASCII$/ or /^[Ll]atin-?1/ or /^ISO-?8859-?1$/) { $to_latin1 = sub { $_[0] }; # identity } else { warn "unknown encoding $_, assuming ISO-8859-1\n"; $_ = 'ISO-8859-1'; goto START; } } my $table_size = 0; # 0 means no table currently open sub maybe_end_table() { return if not $table_size; print '\end{tabular} \\\\ ', "\n"; $table_size = 0; } # Pass in LaTeX source. sub print_row( $ ) { my $tex = shift; if ($table_size == $CHUNK_SIZE) { maybe_end_table(); # back to 0 } print '\begin{tabular}{r@{--}lp{', $WIDTH, '}r', ($opt_withdesc ? "p{$WIDTH}" : ''), '} ', "\n" if not $table_size++; print $tex; } my @summ = summarize($ch, $progs); foreach (@summ) { die if not defined; if (not ref) { # Heading for a new day. maybe_end_table(); print '\section*{\sf ', quote($to_latin1->($_)), "}\n"; } else { my ($start, $stop, $title, $sub_title, $channel, $desc) = map { defined() ? quote($to_latin1->($_)) : undef } @$_; die if not defined $start; die if not defined $title; die if not defined $channel; # Apparently, you have to put \smallskip _before_ each line # (even the first) in order to get consistent spacing. The # blank line after $title is to explicitly end the paragraph, # so that \raggedright takes effect. # $stop = '' if not defined $stop; $title .= " // $sub_title" if defined $sub_title; $desc = '' if not defined $desc; my $row; if ($opt_withdesc) { $row = <{'generator-info-name'}; $g =~ s!/(\d)! $1! if defined $g; my $s = $credits->{'source-info-name'}; if (not defined $g and not defined $s) { # No acknowledgement since unknown source. } elsif (not defined $g and defined $s) { print 'Generated from \textbf{', quote($to_latin1->($s)), "}.\n"; } elsif (defined $g and not defined $s) { print 'Generated by \textbf{', quote($to_latin1->($g)), "}.\n"; } elsif (defined $g and defined $s) { print 'Generated from \textbf{', quote($to_latin1->($s)), '} by \textbf{', quote($to_latin1->($g)), "}.\n"; } else { die } print "\\end{document}\n"; # quote() # # Quote at least some characters which do funny things in LaTeX. # # Parameters: string to quote # Returns: quoted version # # Copied from ; # should put something like this into a 'LaTeX' module some day. # sub quote( $ ) { die 'usage: quote(string)' if @_ != 1; local $_ = shift; # Quote characters s/\\/\\(\\backslash\\)/g; foreach my $ch ('_', '#', '%', '{', '}', '&', '|') { s/\Q$ch\E/\\$ch/g; } s/\$/\\\$/g; foreach my $ch ('<', '>') { s/$ch/\\($ch\\)/g; } s/~/\\(\\sim\\)/g; s/\^/\\(\\hat{}\\)/g; s/¦/\|/g; # Lines of dots s/\.{3,}\s*$/\\dotfill/mg; return $_; }