dump of my local changes origin/local-changes
authorChris Butler <chrisb@crustynet.org.uk>
Tue, 24 Aug 2010 23:19:12 +0000 (00:19 +0100)
committerChris Butler <chrisb@crustynet.org.uk>
Tue, 24 Aug 2010 23:21:36 +0000 (00:21 +0100)
template improvements
framework for running in schroot
added test for debian package
added auto downloading of new upstream with uscan

15 files changed:
templates/header [new file with mode: 0644]
templates/result
templates/summaryreport
tools/cb-run-in-chroot [new file with mode: 0755]
tools/cb-run-schroot-tests [new file with mode: 0755]
tools/cb-setup-env [new file with mode: 0644]
tools/cb-test-installed-grabbers [new file with mode: 0755]
tools/mh-xmltv-do-cvs
tools/mh-xmltv-do-package [new file with mode: 0755]
tools/mh-xmltv-do-release
tools/mh-xmltv-generate-summary
tools/mh-xmltv-test
tools/mh-xmltv-testlog2html
tools/watch [new file with mode: 0644]
xmltv.dtd [new file with mode: 0644]

diff --git a/templates/header b/templates/header
new file mode 100644 (file)
index 0000000..c35b1af
--- /dev/null
@@ -0,0 +1,16 @@
+<strong>Generated:</strong> [% now %]<br>
+<strong>Contact me:</strong> <a href="mailto:chrisb@debian.org">Chris Butler</a><br>
+
+<strong>Debian release used for testing:</strong> |
+[% FOREACH dist = alldists.keys %]
+       [% IF dist == curdist %]<strong>[% END %]
+       <a href="[% base %]../../[% dist %]">[% dist %] ([% alldists.$dist %])</a> |
+       [% IF dist == curdist %]</strong>[% END %]
+[% END %]
+<br>
+<strong>Tested version:</strong> |
+[% FOREACH vers = allvers.keys %]
+       [% IF vers == curvers %]<strong>[% END %]
+       <a href="[% base %]../[% vers %]">[% vers %] ([% allvers.$vers %])</a> |
+       [% IF vers == curvers %]</strong>[% END %]
+[% END %]
index bd8637e..c2245b2 100644 (file)
@@ -5,20 +5,19 @@
 <body>
 
 <h1>XMLTV Test Report</h1>
-<b>Source:</b> [% title %]<br>
-<b>ChangeLog:</b> <a href="ChangeLog">Last 7 days</a><br>
-<b>Build log:</b> <a href="build.log">build.log</a><br>
-<b>Generated:</b> [% now %]<br>
-<b>Contact me:</b> <a href="http://www.holmlund.se/mattias/">Mattias Holmlund</a>
+<strong>Source:</strong> [% title %]<br>
+<strong>ChangeLog:</strong> <a href="ChangeLog">Last 7 days</a><br>
+<strong>Build log:</strong> <a href="build.log">build.log</a><br>
+[% INCLUDE header  base="../" %]
 
 <h1>Tested</h1>
 <table>
 [% FOREACH grabber = summary.keys %] 
   <tr>
-    <td><a href='#[% grabber %]'>[% grabber %]</a></td>
+    <td><a href="#[% grabber %]">[% grabber %]</a></td>
     <td>
     [% FOREACH error = summary.$grabber %]
-      <a href='http://xmltv.org/wiki/xmltvtestcodes.html#[% error %]'>[% error %]</a> 
+      <a href="http://wiki.xmltv.org/index.php/XmltvTestCodes#[% error %]">[% error %]</a> 
     [% END %]
     </td>
   </tr>
@@ -32,7 +31,7 @@
     <td>[% grabber %]</td>
     <td>
     [% FOREACH error = nottested.$grabber %]
-      <a href='http://xmltv.org/wiki/xmltvtestcodes.html#[% error %]'>[% error %]</a> 
+      <a href="http://wiki.xmltv.org/index.php/XmltvTestCodes#[% error %]">[% error %]</a> 
     [% END %]
     </td>
   </tr>
 <h1>Detailed result</h1>
 
 [% FOREACH grabber = detailed.keys %] 
-<b><a name='[% grabber %]'>Testing [% grabber %]</a></b><br>
+<strong><a name="[% grabber %]">Testing [% grabber %]</a></strong><br>
   [% FOREACH line = detailed.$grabber %]
     [% PERL %]
       my $a = "[% line %]";
       $a =~ s/\b(t_[-a-z0-9_\.]+)([a-z]{3})/ 
-             $2 eq 'xml' ? "$1$2" : "<a href='$1$2'>$1$2<\/a>"/ge;
+             $2 eq 'xml' ? "$1$2" : "<a href=\"$1$2\">$1$2<\/a>"/ge;
       print $a;
     [% END %]
     <br>
index 3ff6990..4e4d06c 100644 (file)
            text-align: center }
 </style>
 <h1>XMLTV Test Report</h1>
-<p><b>Source:</b> [% srctype %]<br>
+<p><strong>Source:</strong> [% srctype %]<br>
 
-<b>Generated:</b> [% date %]<br>
-<b>Contact me:</b> <a href="http://www.holmlund.se/mattias/">Mattias Holmlund</a>
+[% INCLUDE header  base="" %]
 </p>
 <table>
 <thead>
@@ -50,4 +49,4 @@
 </tbody>
 </table>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/tools/cb-run-in-chroot b/tools/cb-run-in-chroot
new file mode 100755 (executable)
index 0000000..2fd7420
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+SCRIPTDIR="`dirname $0`/"
+
+. ${SCRIPTDIR}/cb-setup-env
+
+${XT}/mh-xmltv-do-cvs
+${XT}/mh-xmltv-do-release
+${XT}/mh-xmltv-do-package
diff --git a/tools/cb-run-schroot-tests b/tools/cb-run-schroot-tests
new file mode 100755 (executable)
index 0000000..523e592
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# run sid first, as this will run uscan to check for the latest upstream version (lenny uscan doesn't support the options I'm using)
+CHROOTS="sid lenny squeeze" # dapper hardy intrepid jaunty karmic"
+#CHROOTS="lenny"
+
+for CHROOT in $CHROOTS; do
+       schroot -c ${CHROOT}-xmltvtester /home/xmltv-tester/xmltv-tester/tools/cb-run-in-chroot
+done
diff --git a/tools/cb-setup-env b/tools/cb-setup-env
new file mode 100644 (file)
index 0000000..f9b6644
--- /dev/null
@@ -0,0 +1,11 @@
+if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
+       debian_chroot=$(cat /etc/debian_chroot)
+fi
+
+export DEBIAN_VERS=${debian_chroot%%-*}
+export BASE=/home/xmltv-tester/base-${DEBIAN_VERS}
+export XT=/home/xmltv-tester/xmltv-tester/tools/
+
+export http_proxy=http://10.8.0.1:3128/
+
+mkdir -p ${BASE}
diff --git a/tools/cb-test-installed-grabbers b/tools/cb-test-installed-grabbers
new file mode 100755 (executable)
index 0000000..2b65b2c
--- /dev/null
@@ -0,0 +1,158 @@
+#!/usr/bin/perl
+#
+# Run each of the grabbers in turn and do some checks on the output.
+# This is a tool for xmltv developers to run only occasionally -
+# because it does network fetches it can't be part of 'make test'!
+# Run it giving the root of the xmltv source tree, after 'make'.
+# It needs a test.conf file in each grabber directory.
+#
+# -- Ed Avis, ed@membled.com, 2005-08-20
+#
+# Modified to test grabbers installed in /usr/bin
+#
+# -- Chris Butler, chrisb@debian.org, 2009-08-02
+#
+use warnings;
+use strict;
+use Getopt::Long;
+use File::chdir;
+use FindBin qw($Bin);
+    
+use XMLTV::ValidateFile qw/LoadDtd/;
+use XMLTV::ValidateGrabber qw/ValidateGrabber ConfigureGrabber/;
+
+sub w;
+
+our $opt_configure;     # try to --configure grabbers if necessary
+our $opt_only;          # run just one grabber
+our $opt_list_channels; # run with --list-channels
+our $opt_help=0;
+#our $root = "$Bin/..";
+our $root = glob $ENV{'BASE'} . '/work/release/xmltv-*';
+
+#$ENV{XMLTV_SUPPLEMENT} = "$root/blib/share";
+
+# Store the status of each grabber to print a summary at the end.
+my %status;
+my %untested;
+my @grabbers; 
+
+my $result = GetOptions('configure' => \$opt_configure,
+                       'only=s' => \$opt_only,
+                       'list-channels' => \$opt_list_channels,
+                       'xmltv-root=s' => \$root,
+                       'help|h' => \$opt_help,
+                       );
+
+if (scalar( @ARGV ) != 0 or not $result or $opt_help) {
+    print << "EOH";
+usage: $0 [options]
+    
+Valid options:
+--only <grabber>  Only test the specified grabber.
+--configure       Configure all/the selected grabber(s).
+--list-channels   Test that the --list-channels option is supported.
+--xmltv-root dir  Root directory for the xmltv distribution. Only necessary
+                  if text_grabbers is moved outside of the xmltv distribution
+                  directory.
+--help            Print this text.
+
+EOH
+
+   exit 1;
+}
+
+die "--list-channels not implemented" if $opt_list_channels;
+
+my $dtd_in_root = "$root/xmltv.dtd";
+if (not -e $dtd_in_root) {
+    print "$dtd_in_root does not exist.\n";
+    print "Failed to find the xmltv distribution directory. Please use the\n";
+    print "--xmltv-root parameter to specify it.\n";
+    exit 1;
+}
+
+LoadDtd( $dtd_in_root );
+
+{
+    local $CWD = "$root/grab";
+    if (defined $opt_only) {
+       die "no such grabber $opt_only\n" if not -d $opt_only;
+       @grabbers = ($opt_only);
+    }
+    else {
+       @grabbers = map { /tv_grab_(.*)/; $1 } </usr/bin/tv_grab_*>;
+    }
+}
+
+process_grabbers();
+
+my $summary_file = 't_summary.log';
+open(SUMMARY, ">$summary_file")
+  or die "cannot open $summary_file for writing: $!";
+
+sub print_summary {
+    my $msg = shift;
+    print SUMMARY $msg or die "cannot write to $summary_file: $!";
+    print $msg or die "cannot write to stdout: $!";
+}
+
+print_summary "\nTested:\n-------\n";
+foreach my $grabber (sort keys %status) {
+    print_summary "$grabber\t$status{$grabber}\n";
+}
+
+print "\nNot tested:\n-----------\n";
+foreach my $grabber (sort keys %untested) {
+    print_summary "$grabber\t$untested{$grabber}\n";
+}
+
+print_summary "\n";
+close SUMMARY or die "cannot close t_summary.log: $!";
+
+sub w {
+    print "$_[0]\n";
+}
+
+sub process_grabbers {
+  while( my $grabber = shift @grabbers ) {
+    $grabber =~ /^[a-z_]+$/ or die "bad grabber name $_";
+    my $exe = "tv_grab_$grabber";
+    my $cmd = "perl /usr/bin/$exe";
+    my $conf = "$root/grab/$grabber/test.conf";
+    my $output_prefix = "t_${grabber}_";
+
+    if ( not -f "/usr/bin/$exe" ) {
+#      w "No such grabber $exe.";
+        $untested{$exe} = "no exe";
+       goto done;
+    }
+
+    if( $opt_configure ) {
+      w "Configuring $exe";
+      ConfigureGrabber( $cmd, $conf );
+    }
+
+    if( not -f $conf ) {
+#      w "$exe not configured. Skipping.";
+      $untested{$exe} = "no test.conf";
+      goto done;
+    }
+
+    w "Testing $exe";
+
+    my @errors = ValidateGrabber( $exe,  $cmd, $conf, $output_prefix, undef, 1);
+                                 
+
+    if (scalar( @errors )) {
+        $status{$exe} = join ", ", @errors;
+       w "$exe has errors: @errors";
+    }
+    else {
+        $status{$exe} = "ok";
+    }
+
+  done:
+  }
+}
+
index 0bc0420..0301223 100755 (executable)
@@ -2,7 +2,7 @@
 
 BASE=${BASE:-/home/mattias/nobackup/xmltv-tester}
 XT=${XT:-/home/mattias/development/xmltv-tester/tools}
-CVS=${CVS:-:pserver:anonymous@xmltv.cvs.sourceforge.net:/cvsroot/xmltv}
+CVS=:pserver:anonymous@xmltv.cvs.sourceforge.net:/cvsroot/xmltv
 LASTWEEK=`date -d "last week" +%F`
 
 rm -rf $BASE/work/nightly
@@ -18,7 +18,7 @@ cd xmltv
 touch ChangeLog
 
 cvs2cl -g -Q -l "-d>$LASTWEEK"
-cp -a $BASE/config/nightly/* $BASE/work/nightly/xmltv
+cp -a $BASE/config/nightly/* $BASE/work/nightly/xmltv/grab
 
 rm -rf $BASE/result/nightly/latest
 mkdir -p $BASE/result/nightly/latest
@@ -28,8 +28,8 @@ $XT/mh-xmltv-test "$BASE/work/nightly/xmltv" \
               "$BASE/result/nightly/latest" 
 
 $XT/mh-xmltv-testlog2html $BASE/result/nightly/latest/test_grabbers.log \
-                      "Nightly" $BASE/result/nightly/latest
+                      "Nightly" $BASE/result/nightly/latest nightly ${DEBIAN_VERS}
 
 $XT/mh-xmltv-add-result "$BASE/result/nightly/latest" "$BASE/result/nightly"
-$XT/mh-xmltv-generate-summary "Nightly" "$BASE/result/nightly"
-rsync -az --delete $BASE/result/nightly/* upload.www2.holmlund.se:public_html/xmltv.se/validator/nightly
+$XT/mh-xmltv-generate-summary "Nightly" "$BASE/result/nightly" nightly ${DEBIAN_VERS}
+#rsync -az --delete $BASE/result/nightly/* upload.www2.holmlund.se:public_html/xmltv.se/validator/nightly
diff --git a/tools/mh-xmltv-do-package b/tools/mh-xmltv-do-package
new file mode 100755 (executable)
index 0000000..5f553fb
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash 
+
+RELEASE=0.5.55
+
+BASE=${BASE:-/home/mattias/nobackup/xmltv-tester}
+XT=${XT:-/home/mattias/development/xmltv-tester/tools}
+
+rm -rf $BASE/work/package
+mkdir -p $BASE/work/package
+
+#tar xjf $BASE/tar/xmltv-$RELEASE.tar.bz2 --directory $BASE/work/package 
+#cp -a $BASE/config/package/* $BASE/work/package/xmltv-$RELEASE/grab
+
+rm -rf $BASE/result/package/latest
+mkdir -p $BASE/result/package/latest
+cd $BASE/result/package/latest
+
+$XT/mh-xmltv-test "" \
+              "$BASE/result/package/latest" $XT/cb-test-installed-grabbers
+
+$XT/mh-xmltv-testlog2html $BASE/result/package/latest/test_grabbers.log \
+                      "Release $RELEASE" $BASE/result/package/latest package ${DEBIAN_VERS}
+
+$XT/mh-xmltv-add-result "$BASE/result/package/latest" "$BASE/result/package"
+
+VER=`dpkg -l xmltv-util | tail -n 1 | awk '{print $3}'`
+$XT/mh-xmltv-generate-summary "Debian package $VER" "$BASE/result/package" package ${DEBIAN_VERS}
+#rsync -az --delete $BASE/result/package/* upload.www2.holmlund.se:public_html/xmltv.se/validator/package
index e3ade70..5807055 100755 (executable)
@@ -1,15 +1,36 @@
 #!/bin/bash 
 
-RELEASE=0.5.56
+#RELEASE=0.5.57
 
 BASE=${BASE:-/home/mattias/nobackup/xmltv-tester}
 XT=${XT:-/home/mattias/development/xmltv-tester/tools}
 
+UPSTREAM=`ls ~/tar/xmltv-*.tar.* | sed 's/^.*xmltv-\([0-9.]*\)\.tar.*$/\1/'`
+
+if [ "$DEBIAN_VERS" = "sid" ] || [ -z "$UPSTREAM" -a "${DEBIAN_VERS}" = "squeeze" ]; then
+       # none found? use an extremely low version to force uscan to download the package
+       if [ -z "$UPSTREAM" ]; then
+               UPSTREAM=0.0.1
+       fi
+
+       uscan --download --package xmltv --watchfile ${XT}/watch --destdir ~/tar --upstream-version ${UPSTREAM} --no-symlink
+       RELEASE="`uscan --package xmltv --watchfile ${XT}/watch --upstream-version ${UPSTREAM} --report-status | sed -n 's,^.*Newest version on remote site is \([0-9.]*\).*$,\1,p'`"
+elif [ -z "$UPSTREAM" ]; then
+       echo "$0: no upstream tar.gz found, and lenny uscan can't do the auto download." >&2
+       echo "run the sid/squeeze version first!"
+       exit 1
+else
+       RELEASE="$UPSTREAM"
+fi
+
 rm -rf $BASE/work/release
 mkdir -p $BASE/work/release
 
-tar xjf $BASE/tar/xmltv-$RELEASE.tar.bz2 --directory $BASE/work/release 
-cp -a $BASE/config/release/* $BASE/work/release/xmltv-$RELEASE
+tar xjf ~/tar/xmltv-$RELEASE.tar.bz2 --directory $BASE/work/release 
+cp -a $BASE/config/release/* $BASE/work/release/xmltv-$RELEASE/grab
+
+# _ar gets into an infinite loop, disable by removing the test.conf
+rm $BASE/work/release/xmltv-$RELEASE/grab/ar/test.conf
 
 rm -rf $BASE/result/release/latest
 mkdir -p $BASE/result/release/latest
@@ -17,8 +38,8 @@ $XT/mh-xmltv-test "$BASE/work/release/xmltv-$RELEASE" \
               "$BASE/result/release/latest" 
 
 $XT/mh-xmltv-testlog2html $BASE/result/release/latest/test_grabbers.log \
-                      "Release $RELEASE" $BASE/result/release/latest
+                      "Release $RELEASE" $BASE/result/release/latest release ${DEBIAN_VERS}
 
 $XT/mh-xmltv-add-result "$BASE/result/release/latest" "$BASE/result/release"
-$XT/mh-xmltv-generate-summary "Release $RELEASE" "$BASE/result/release"
-rsync -az --delete $BASE/result/release/* upload.www2.holmlund.se:public_html/xmltv.se/validator/release
+$XT/mh-xmltv-generate-summary "Release $RELEASE" "$BASE/result/release" release ${DEBIAN_VERS}
+#rsync -az --delete $BASE/result/release/* upload.www2.holmlund.se:public_html/xmltv.se/validator/release
index 7cf76a5..12b789a 100755 (executable)
@@ -4,8 +4,9 @@ use strict;
 
 use File::Slurp;
 use Template;
+use FindBin;
 
-my( $srctype, $dir ) = @ARGV;
+my( $srctype, $dir, $vers, $dist ) = @ARGV;
 
 my $state = loadvar( "$dir/state.dump" );
 foreach my $key (keys %{$state}) {
@@ -38,7 +39,7 @@ delete $grabbers{t};
 
 # some useful options (see below for full list)
 my $config = {
-    INCLUDE_PATH => '/home/mattias/development/xmltv-tester/templates/',
+    INCLUDE_PATH => "$FindBin::Bin/../templates/",
     INTERPOLATE  => 1,               # expand "$var" in plain text
     POST_CHOMP   => 1,               # cleanup whitespace
 #  PRE_PROCESS  => 'header',        # prefix each template
@@ -51,10 +52,14 @@ my $template = Template->new($config);
 # define template variables for replacement
 my $vars = {
   srctype => $srctype,
-  date => scalar localtime(time),
+  now => scalar localtime(time),
   state  => $state,
   results => \@results,
   grabbers => [sort keys %grabbers],
+  curdist => $dist,
+  curvers => $vers,
+  alldists => {'lenny' => 'stable', 'squeeze' => 'testing', 'sid' => 'unstable'},
+  allvers => {'nightly' => 'latest CVS', 'release' => 'latest released', 'package' => 'latest package in Debian'}
   };
 
 # process input template, substituting variables
index 452a0d0..175c56c 100755 (executable)
@@ -9,29 +9,33 @@ use IO::Wrap qw/wraphandle/;
 use File::Slurp qw/read_file/;
 use Data::Dumper;
 
-if( scalar( @ARGV ) != 2 ) {
+if( scalar( @ARGV ) != 2 && scalar( @ARGV ) != 3) {
   print STDERR << "EOH";
 
-mh-xmltv-test <xmltvdir> <resultdir>
+mh-xmltv-test <xmltvdir> <resultdir> [<test_grabbers>]
 
 xmltvdir shall contain the xmltv-source code.
 resultdir shall be a non-existent directory where the result will be placed.
+test_grabbers is an (optional) override for the path to test_grabbers
 
 EOH
 
   exit 1;
 }
 
-my( $xmltvdir, $resultdir ) = @ARGV;
+my ( $xmltvdir, $resultdir, $test_grabbers_path ) = @ARGV;
 
 my $stdout = wraphandle( \*STDOUT );
 my $output_fh = $stdout;
 
-set_output( "$resultdir/build.log" );
-make( $xmltvdir );
+unless ($xmltvdir eq '')
+{
+       set_output( "$resultdir/build.log" );
+       make( $xmltvdir );
+}
 
 set_output( "$resultdir/test_grabbers.log" );
-test_grabbers( $xmltvdir, "$resultdir" );
+test_grabbers( $xmltvdir, "$resultdir", $test_grabbers_path );
 set_output();
 
 remove_listings( "$resultdir" );
@@ -44,9 +48,16 @@ sub make {
 }
 
 sub test_grabbers {
-  my( $srcdir, $resultdir ) = @_;
+  my( $srcdir, $resultdir, $test_grabbers_path ) = @_;
 
-  run_cmd( $resultdir, "$srcdir/grab/test_grabbers" );
+  if (defined $test_grabbers_path)
+  {
+         run_cmd( $resultdir, $test_grabbers_path );
+  }
+  else
+  {
+         run_cmd( $resultdir, "$srcdir/grab/test_grabbers" );
+  }
 }
 sub remove_listings {
   my( $dir ) = @_;
index a340fa1..8a4a321 100755 (executable)
@@ -6,12 +6,12 @@ use Template;
 use File::Slurp;
 use FindBin;
 
-if( scalar @ARGV != 3 ) {
-    print "mh-xmltv-testlog2html <logfile> <title> <outdir>\n";
+if( scalar @ARGV != 5 ) {
+    print "mh-xmltv-testlog2html <logfile> <title> <outdir> <xmltv vers> <dist>\n";
     exit 1;
 }
 
-my( $infile, $title, $outdir ) = @ARGV;
+my( $infile, $title, $outdir, $vers, $dist ) = @ARGV;
 
 # some useful options (see below for full list)
 my $config = {
@@ -28,9 +28,14 @@ my $template = Template->new($config);
 # Memory is cheap...
 my @lines = read_file( $infile );
 
-my $data;
-$data->{now} = localtime(time);
-$data->{title} = $title;
+my $data = {
+  title => $title,
+  now => scalar localtime(time),
+  curdist => $dist,
+  curvers => $vers,
+  alldists => {'lenny' => 'stable', 'squeeze' => 'testing', 'sid' => 'unstable'},
+  allvers => {'nightly' => 'latest CVS', 'release' => 'latest released', 'package' => 'latest package in Debian'}
+  };
 
 my $i = 0;
 do  {
diff --git a/tools/watch b/tools/watch
new file mode 100644 (file)
index 0000000..9e61af8
--- /dev/null
@@ -0,0 +1,2 @@
+version=3
+http://sf.net/xmltv/xmltv-(.*)\.tar\.bz2
diff --git a/xmltv.dtd b/xmltv.dtd
new file mode 100644 (file)
index 0000000..702b650
--- /dev/null
+++ b/xmltv.dtd
@@ -0,0 +1,536 @@
+<!-- DTD for TV listings
+
+This is a DTD to represent a TV listing.  It doesn't explicitly group
+programmes by day or by channel, instead broadcast time and channel
+are attributes of the 'programme' element.  Optionally, data about the
+TV channels used can be stored in 'channel' elements.
+
+Data about a TV programme are stored in the subelements of element
+'programme', but metadata such as when it will be broadcast are stored
+as attributes.
+
+Many of the details have a 'lang' attribute so that you can
+store them in multiple languages or have mixed languages in a single
+listing.  This 'lang' should be the two-letter code such as 'en' or
+'fr_FR'.  Or you can just leave it out and let your reader take a
+guess.
+
+Unless otherwise specified, an element containing CDATA must have some
+text if it is written.
+
+An example XML file for this DTD might look like this:
+
+<tv generator-info-name="my listings generator">
+  <channel id="3sat.de">
+    <display-name lang="de">3SAT</display-name>
+  </channel>
+  <channel id="das-erste.de">
+    <display-name lang="de">ARD</display-name>
+    <display-name lang="de">Das Erste</display-name>
+  </channel>
+
+  <programme start="200006031633" channel="3sat.de">
+    <title lang="de">blah</title>
+    <title lang="en">blah</title>
+    <desc lang="de">
+       Blah Blah Blah.
+    </desc>
+    <credits>
+      <director>blah</director>
+      <actor>a</actor>
+      <actor>b</actor>
+    </credits>
+    <date>19901011</date>
+    <country>ES</country>
+    <episode-num system="xmltv_ns">2 . 9 . 0/1</episode-num>
+    <video>
+      <aspect>16:9</aspect>
+    </video>
+    <rating system="MPAA">
+      <value>PG</value>
+      <icon src="pg_symbol.png" />
+    </rating>
+    <star-rating>
+      <value>3/3</value>
+    </star-rating>
+  </programme>
+  <programme> ... </programme>
+  ...
+</tv>
+
+This describes two channels and then a programme broadcast on one of
+the channels, then some more programmes.  Almost everything in the DTD
+is optional, so you can write files which are much simpler than this
+example.
+
+All dates and times in this DTD follow the same format, loosely based
+on ISO 8601.  They can be 'YYYYMMDDhhmmss' or some initial
+substring, for example if you only know the year and month you can
+have 'YYYYMM'.  You can also append a timezone to the end; if no
+explicit timezone is given, UTC is assumed.  Examples:
+'200007281733 BST', '200209', '19880523083000 +0300'.  (BST == +0100.)
+
+Unless specified otherwise, textual element content may not contain
+newlines - this is to make it easy to convert into line-oriented
+formats, and to avoid the question of what exactly a newline would
+mean in the middle of someone's name or whatever.  Leading and
+trailing whitespace in element content is not significant.
+
+At present versions of this DTD correspond to releases of the 'xmltv'
+package, which is a set of programs to generate and manipulate files
+conforming to this DTD.  Written by Ed Avis (ed@membled.com) and
+Gottfried Szing, thanks to others for suggestions.
+
+$Id: xmltv.dtd,v 1.40 2009/02/16 17:32:39 rmeden Exp $
+
+-->
+
+<!-- The root element, tv.
+
+Date should be the date when the listings were originally produced in
+whatever format; if you're converting data from another source, then
+use the date given by that source.  The date when the conversion
+itself was done is not important.
+
+To indicate the source of the listings, there are three attributes you
+can define:
+
+'source-info-url' is a URL describing the data source in
+some human-readable form.  So if you are getting your listings from
+SAT.1, you might set this to the URL of a page explaining how to
+subscribe to their feed.  If you are getting them from a website, the
+URL might be the index of the site or at least of the TV listings
+section.
+
+'source-info-name' is the link text for that URL; it should
+generally be the human-readable name of your listings supplier.
+Sometimes the link text might be printed without the link itself, in
+hardcopy listings for example.
+
+'source-data-url' is where the actual data is grabbed from.  This
+should link directly to the machine-readable data files if possible,
+but it's not rigorously defined what 'actual data' means.  If you are
+parsing the data from human-readable pages, then it's more appropriate
+to link to them with the source-info stuff and omit this attribute.
+
+To publicize your wonderful program which generated this file, you can
+use 'generator-info-name' (preferably in the form 'progname/version')
+and 'generator-info-url' (a link to more info about the program).
+-->
+<!ELEMENT tv (channel*, programme*)>
+<!ATTLIST tv date   CDATA #IMPLIED
+             source-info-url     CDATA #IMPLIED
+             source-info-name    CDATA #IMPLIED
+             source-data-url     CDATA #IMPLIED
+             generator-info-name CDATA #IMPLIED
+             generator-info-url  CDATA #IMPLIED >
+
+<!-- channel - details of a channel
+
+Each 'programme' element (see below) should have an attribute
+'channel' giving the channel on which it is broadcast.  If you want to
+provide more detail about channels, you can give some 'channel'
+elements before listing the programmes.  The 'id' attribute of the
+channel should match what is given in the 'channel' attribute of the
+programme.
+
+Typically, all the channels used in a particular TV listing will be
+included and then the programmes using those channels.  But it's
+entirely optional to include channel details - you can just leave out
+channel elements or provide only some of them.  It is also okay to
+give just channels and no programmes, if you just want to describe
+what TV channels are available in a certain area.
+
+Each channel has one id attribute, which must be unique and should
+preferably be in the form suggested by RFC2838 (the 'broadcast'
+element of the grammar in that RFC, in other words, a DNS-like name
+but without any URI scheme).  Then one or more display names which are
+shown to the user.  You might want a different display name for
+different languages, but also you can have more than one name for the
+same language.  Names listed earlier are considered 'more canonical'.
+
+Since the display name is just there as a way for humans to refer to
+the channel, it's acceptable to just put the channel number if it's
+fairly universal among viewers of the channel.  But remember that this
+isn't an official statement of what channel number has been
+allocated, and the same number might be used for a different channel
+somewhere else.
+
+The ordering of channel elements makes no difference to the meaning of
+the file, since they are looked up by id and not by their position.
+However it makes things like diffing easier if you write the channel
+elements sorted by ASCII order of their ids.
+-->
+<!ELEMENT channel (display-name+, icon*, url*) >
+<!ATTLIST channel id CDATA #REQUIRED >
+
+<!-- A user-friendly name for the channel - maybe even a channel
+number.  List the most canonical / common ones first and the most
+obscure names last.  The lang attribute follows RFC 1766.
+-->
+<!ELEMENT display-name (#PCDATA)>
+<!ATTLIST display-name lang CDATA #IMPLIED>
+
+<!-- A URL where you can find out more about the element that contains
+it (programme or channel).  This might be the official site, or a fan
+page, whatever you like really.
+
+If multiple url elements are given, the most authoritative or official
+(which might conflict...) sites should be listed first.
+-->
+<!ELEMENT url (#PCDATA)>
+
+<!-- programme - details of a single programme transmission
+
+A show will be exactly the same whether it is broadcast at 18:00 or
+19:00, and on whichever channel.  Technical details like broadcast
+time don't affect the content of the programme itself, so they are
+included as attributes of this element.  Start time and channel are
+the two that you must include.
+
+Sometimes VCR programming systems like PDC or VPS have their own
+notion of 'start time' which is different from the actual start time,
+so there are attributes for that.  In practice, stop time will usually
+be the start time of the next programme, but if you can get it more
+accurate, good for you.  Similarly, you can specify a code for
+Gemstar's Showview or VideoPlus programming systems.
+
+TV listings sometimes have the problem of listing two or more
+programmes in the same timeslot, such as 'News; Weather'.  We call
+this a 'clump' of programmes, and the 'clumpidx' attribute
+differentiates between two programmes sharing the same timeslot and
+channel.  In this case News would have clumpidx="0/2" and Weather
+would have clumpidx="1/2".  If you don't have this problem, be
+thankful!
+
+It's intended that start time and stop time, when both are present,
+make a half-closed interval: a programme is considered to be
+broadcasting _at_ its start time but to stop just before its stop
+time.  In this way a programme from 11:00 to 12:00 does not overlap
+with another programme from 12:00 to 13:00, not even for a moment.
+Nor is there any gap between the two.
+
+To do: Some means of indicating breaks between programmes on the same
+channel.  The 'channel' attribute references the 'id' of a channel
+element, but the DTD doesn't give a way to specify this constraint.
+Perhaps there is some better XML syntax we could use for that.
+-->
+<!ELEMENT programme (title+, sub-title*, desc*, credits?, date?,
+                     category*, language?, orig-language?, length?,
+                     icon*, url*, country*, episode-num*, video?, audio?,
+                     previously-shown?, premiere?, last-chance?, new?,
+                     subtitles*, rating*, star-rating* )>
+<!ATTLIST programme start     CDATA #REQUIRED
+                    stop      CDATA #IMPLIED
+                    pdc-start CDATA #IMPLIED
+                    vps-start CDATA #IMPLIED
+                    showview  CDATA #IMPLIED
+                    videoplus CDATA #IMPLIED
+                    channel   CDATA #REQUIRED
+                    clumpidx  CDATA "0/1" >
+
+<!-- Programme title, eg 'The Simpsons'. -->
+<!ELEMENT title (#PCDATA)>
+<!ATTLIST title lang CDATA #IMPLIED>
+
+<!-- Sub-title or episode title, eg 'Datalore'.   Should probably be
+called 'secondary title' to avoid confusion with captioning!
+-->
+<!ELEMENT sub-title (#PCDATA)>
+<!ATTLIST sub-title lang CDATA #IMPLIED>
+
+<!-- Description of the programme or episode.
+
+Unlike other elements, long bits of whitespace here are treated as
+equivalent to a single space and newlines are permitted, so you can
+break lines and write a pretty-looking paragraph if you wish.
+-->
+<!ELEMENT desc (#PCDATA)>
+<!ATTLIST desc lang CDATA #IMPLIED>
+
+<!-- Credits for the programme.
+
+People are listed in decreasing order of importance; so for example
+the starring actors appear first followed by the smaller parts.  As
+with other parts of this file format, not mentioning a particular
+actor (for example) does not imply that he _didn't_ star in the film -
+so normally you'd list only the few most important people.
+
+Adapter can be either somebody who adapted a work for television, or
+somebody who did the translation from another language.  Maybe these
+should be separate, but if so how would 'translator' fit in with the
+'language' element?
+-->
+<!ELEMENT credits (director*, actor*, writer*, adapter*, producer*,
+                   presenter*, commentator*, guest* )>
+<!ELEMENT director    (#PCDATA)>
+<!ELEMENT actor       (#PCDATA)>
+<!ATTLIST actor role  CDATA #IMPLIED>
+<!ELEMENT writer      (#PCDATA)>
+<!ELEMENT adapter     (#PCDATA)>
+<!ELEMENT producer    (#PCDATA)>
+<!ELEMENT presenter   (#PCDATA)>
+<!ELEMENT commentator (#PCDATA)>
+<!ELEMENT guest       (#PCDATA)>
+
+
+<!-- The date the programme or film was finished.  This will probably
+be the same as the copyright date.
+-->
+<!ELEMENT date (#PCDATA)>
+
+<!-- Type of programme, eg 'soap', 'comedy' or whatever the
+equivalents are in your language.  There's no predefined set of
+categories and it's okay for a programme to belong to several.
+-->
+<!ELEMENT category (#PCDATA)>
+<!ATTLIST category lang CDATA #IMPLIED>
+
+<!-- The language the programme will be broadcast in.  This does not
+include the language of any subtitles, but it is affected by dubbing
+into a different language.  For example, if a French film is dubbed
+into English, language=en and orig-language=fr.
+
+There are two ways to specify the language.  You can use the
+two-letter codes such as en or fr, or you can give a name such as
+'English' or 'Deutsch'.  In the latter case you might want to use the
+'lang' attribute, for example
+
+<language lang="fr">Allemand</language>
+-->
+<!ELEMENT language (#PCDATA)>
+<!ATTLIST language lang CDATA #IMPLIED>
+
+<!-- The original language, before dubbing.  The same remarks as for
+'language' apply.
+-->
+<!ELEMENT orig-language (#PCDATA)>
+<!ATTLIST orig-language lang CDATA #IMPLIED>
+
+<!-- The true length of the programme, not counting advertisements or
+trailers.  But this does take account of any bits which were cut out
+of the broadcast version - eg if a two hour film is cut to 110 minutes
+and then padded with 20 minutes of advertising, length will be 110
+minutes even though end time minus start time is 130 minutes.
+-->
+<!ELEMENT length (#PCDATA)>
+<!ATTLIST length units (seconds | minutes | hours) #REQUIRED>
+
+<!-- An icon associated with the element that contains it.
+src: uri of image
+width, height: (optional) dimensions of image
+
+These dimensions are pixel dimensions for the time being, eventually
+this will change to be more like HTML's 'img'.
+-->
+<!ELEMENT icon EMPTY>
+<!ATTLIST icon src         CDATA #REQUIRED
+               width       CDATA #IMPLIED
+               height      CDATA #IMPLIED> 
+
+<!-- The value of the element that contains it.  This is for elements
+that can have both a textual 'value' and an icon.  At present there is
+no 'lang' attribute here because things like 'PG' are not translatable
+(although a document explaining what 'PG' actually means would be).
+It happens that 'value' is used only for this sort of thing.
+-->
+<!ELEMENT value (#PCDATA)>
+
+<!-- A country where the programme was made or one of the countries in
+a joint production.  You can give the name of a country, in which case
+you might want to specify the language in which this name is written,
+or you can give a two-letter uppercase country code, in which case the
+lang attribute should not be given.  For example,
+
+<country lang="en">Italy</country>
+<country>GB</country>
+-->
+<!ELEMENT country (#PCDATA)>
+<!ATTLIST country lang CDATA #IMPLIED>
+
+<!-- Episode number
+
+Not the title of the episode, its number or ID.  There are several
+ways of numbering episodes, so the 'system' attribute lets you specify
+which you mean.
+
+There are two predefined numbering systems, 'xmltv_ns' and
+'onscreen'.
+
+xmltv_ns: This is intended to be a general way to number episodes and
+parts of multi-part episodes.  It is three numbers separated by dots,
+the first is the series or season, the second the episode number
+within that series, and the third the part number, if the programme is
+part of a two-parter.  All these numbers are indexed from zero, and
+they can be given in the form 'X/Y' to show series X out of Y series
+made, or episode X out of Y episodes in this series, or part X of a
+Y-part episode.  If any of these aren't known they can be omitted.
+You can put spaces whereever you like to make things easier to read.
+
+(NB 'part number' is not used when a whole programme is split in two
+for purely scheduling reasons; it's intended for cases where there
+really is a 'Part One' and 'Part Two'.  The format doesn't currently
+have a way to represent a whole programme that happens to be split
+across two or more timeslots.)
+
+Some examples will make things clearer.  The first episode of the
+second series is '1.0.0/1' .  If it were a two-part episode, then the
+first half would be '1.0.0/2' and the second half '1.0.1/2'.  If you
+know that an episode is from the first season, but you don't know
+which episode it is or whether it is part of a multiparter, you could
+give the episode-num as '0..'.  Here the second and third numbers have
+been omitted.  If you know that this is the first part of a three-part
+episode, which is the last episode of the first series of thirteen,
+its number would be '0 . 12/13 . 0/3'.  The series number is just '0'
+because you don't know how many series there are in total - perhaps
+the show is still being made!
+
+The other predefined system, onscreen, is to simply copy what the
+programme makers write in the credits - 'Episode #FFEE' would
+translate to '#FFEE'.
+
+You are encouraged to use one of these two if possible; if xmltv_ns is
+not general enough for your needs, let me know.  But if you want, you
+can use your own system and give the 'system' attribute as a URL
+describing the system you use.
+-->
+<!ELEMENT episode-num (#PCDATA)>
+<!ATTLIST episode-num system CDATA "onscreen">
+
+<!-- Video details: the subelements describe the picture quality as
+follows:
+
+present: whether this programme has a picture (no, in the
+case of radio stations broadcast on TV or 'Blue'), legal values are
+'yes' or 'no'.  Obviously if the value is 'no', the other elements are
+meaningless.
+
+colour: 'yes' for colour, 'no' for black-and-white.
+
+aspect: The horizontal:vertical aspect ratio, eg '4:3' or '16:9'.
+
+quality: information on the quality, eg 'HDTV', '800x600'.
+
+-->
+<!ELEMENT video (present?, colour?, aspect?, quality?)>
+<!ELEMENT present (#PCDATA)>
+<!ELEMENT colour (#PCDATA)>
+<!ELEMENT aspect (#PCDATA)>
+<!ELEMENT quality (#PCDATA)>
+
+<!-- Audio details, similar to video details above.
+
+present: whether this programme has any sound at all, 'yes' or 'no'.
+
+stereo: Description of the stereo-ness of the sound.  Legal values
+are currently 'mono','stereo','dolby','dolby digital' and 'surround'; 
+others like 'quad' might be added later.
+-->
+<!ELEMENT audio (present?, stereo?)>
+<!ELEMENT stereo (#PCDATA)>
+
+<!-- When and where the programme was last shown, if known.  Normally
+in TV listings 'repeat' means 'previously shown on this channel', but
+if you don't know what channel the old screening was on (but do know
+that it happened) then you can omit the 'channel' attribute.
+Similarly you can omit the 'start' attribute if you don't know when
+the previous transmission was (though you can of course give just the
+year, etc.).
+
+The absence of this element does not say for certain that the
+programme is brand new and has never been screened anywhere before.
+-->
+<!ELEMENT previously-shown EMPTY>
+<!ATTLIST previously-shown start   CDATA #IMPLIED
+                           channel CDATA #IMPLIED >
+
+<!-- 'Premiere'.  Different channels have different meanings for this
+word - sometimes it means a film has never before been seen on TV in
+that country, but other channels use it to mean 'the first showing of
+this film on our channel in the current run'.  It might have been
+shown before, but now they have paid for another set of showings,
+which makes the first in that set count as a premiere!
+
+So this element doesn't have a clear meaning, just use it to represent
+where 'premiere' would appear in a printed TV listing.  You can use
+the content of the element to explain exactly what is meant, for
+example:
+
+<premiere lang="en">
+  First showing on national terrestrial TV
+</premiere>
+
+The textual content is a 'paragraph' as for <desc>.  If you don't want
+to give an explanation, just write empty content:
+
+<premiere />
+-->
+<!ELEMENT premiere (#PCDATA)>
+<!ATTLIST premiere lang CDATA #IMPLIED>
+
+<!-- Last-chance.  In a way this is the opposite of premiere.  Some
+channels buy the rights to show a movie a certain number of times, and
+the first may be flagged 'premiere', the last as 'last showing'.
+
+For symmetry with premiere, you may use the element content to give a
+'paragraph' describing exactly what is meant - it's unlikely to be the
+last showing ever!  Otherwise, explicitly put empty content:
+
+<last-chance />
+-->
+<!ELEMENT last-chance (#PCDATA)>
+<!ATTLIST last-chance lang CDATA #IMPLIED>
+
+<!-- New.  This is the first screened programme from a new show that
+has never been shown on television before - if not worldwide then at
+least never before in this country.  After the first episode or
+programme has been shown, subsequent ones are no longer 'new'.
+Similarly the second series of an established programme is not 'new'.
+
+Note that this does not mean 'new season' or 'new episode' of an
+existing show.  You can express part of that using the episode-num
+stuff.
+-->
+<!ELEMENT new EMPTY>
+
+<!-- Subtitles.  These can be either 'teletext' (sent digitally, and
+displayed at the viewer's request), 'onscreen' (superimposed on the
+picture and impossible to get rid of), or 'deaf-signed' (in-vision 
+signing for users of sign language). You can have multiple subtitle
+streams to handle different languages.  Language for subtitles is
+specified in the same way as for programmes.
+-->
+<!ELEMENT subtitles (language?)>
+<!ATTLIST subtitles type (teletext | onscreen | deaf-signed) #IMPLIED>
+
+<!-- Rating.  Various bodies decide on classifications for films -
+usually a minimum age you must be to see it.  In principle the same
+could be done for ordinary TV programmes.  Because there are many
+systems for doing this, you can also specify the rating system used
+(which in practice is the same as the body which made the rating).
+-->
+<!ELEMENT rating (value, icon*)>
+<!ATTLIST rating system CDATA #IMPLIED>
+
+<!-- 'Star rating' - many listings guides award a programme a score as
+a quick guide to how good it is.  The value of this element should be
+'N / M', for example one star out of a possible five stars would be
+'1 / 5'.  Zero stars is also a possible score (and not the same as
+'unrated').  You should try to map whatever wacky system your listings
+source uses to a number of stars: so for example if they have thumbs
+up, thumbs sideways and thumbs down, you could map that to two, one or
+zero stars out of two.  If a programme is marked as recommended in a
+listings guide you could map this to '1 / 1'. Because there could be many 
+ways to provide star-ratings or recommendations for a programme, you can 
+specify multiple star-ratings. You can specify the star-rating system 
+used, or the provider of the recommendation, with the system attribute. 
+Whitespace between the numbers and slash is ignored.
+-->
+
+<!ELEMENT star-rating (value, icon*)>
+<!ATTLIST star-rating system CDATA #IMPLIED>
+<!-- (Why are things like 'stereo', which must be one of a small
+number of values, stored as the contents of elements rather than as
+attributes?  Because they are data rather than metadata.  Attributes
+are used for things like the language or encoding of element contents,
+or for programme transmission details.) -->