123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804 |
- #! /usr/bin/perl
- #
- # build.pl - a tool for building FSFE web pages
- #
- # Copyright (C) 2003 Jonas Öberg
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful, but
- # WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- # 02110-1301, USA.
- #
- use File::Find::Rule;
- use Getopt::Std;
- use File::Path;
- use File::Basename;
- use XML::LibXSLT;
- use XML::LibXML;
- use File::Copy;
- use POSIX qw(strftime);
- use IO::Handle;
- use IO::Select;
- use Socket;
- use Fcntl ':flock';
-
- # This defines the focuses and their respective preferred / original
- # language. For example, it says that we should have a focus called
- # "se" (Sweden) which has the preferred language "sv" (Swedish).
- #
- # This also says that documents in the directory /se should be considered
- # as having the Swedish version as the original, and so on.
- #
- our %countries = (global => 'en');
- #our %countries = (
- # global => 'en',
- # de => 'de',
- # es => 'es',
- # it => 'it',
- # fr => 'fr',
- # se => 'sv' );
-
- #
- # This is a hash of all the languages that we have translations into, and their
- # respective names in the local language. Make sure that one entry exists
- # here for every language, or it won't be rendered.
- #
- # NOTE: Make sure that the language also is added to Apache configuration so
- # content negotiation works.
- #
- our %languages = (
- ar => 'العربيّة',
- bg => 'Български',
- ca => 'Català',
- cs => 'Česky',
- da => 'Dansk',
- de => 'Deutsch',
- el => 'Ελληνικά',
- en => 'English',
- es => 'Español',
- et => 'Eesti',
- fi => 'Suomi',
- fr => 'Français',
- hr => 'Hrvatski',
- hu => 'Magyar',
- it => 'Italiano',
- ku => 'Kurdî',
- mk => 'Mакедонски',
- nb => 'Norsk (bokmål)',
- nl => 'Nederlands',
- nn => 'Norsk (nynorsk)',
- pl => 'Polski',
- pt => 'Português',
- ro => 'Română',
- ru => 'Русский',
- sk => 'Slovenčina',
- sl => 'Slovenščina',
- sq => 'Shqip',
- sr => 'Srpski',
- sv => 'Svenska',
- tr => 'Türkçe',
- uk => 'Українська',
- );
-
- our $current_date = strftime "%Y-%m-%d", localtime;
- our $current_time = strftime "%Y-%m-%d %H:%M:%S", localtime;
-
-
- # This static array contains files that can't be out of date
- our %cant_be_outdated = (
- "news/news" => 1,
- "index" => 1
- );
-
-
- #
- # Parse the command line options. We need two; where to put the finished
- # pages and what to use as base for the input.
- #
- getopts('o:i:t:duqn', \%opts);
- unless ($opts{o}) {
- print STDERR "Usage: $0 [-q] [-u] [-d] [-n] [-t #] -o <output directory>\n";
- print STDERR " -q Quiet\n";
- print STDERR " -u Update only\n";
- print STDERR " -d Print some debug information\n";
- print STDERR " -n Don't write any files\n";
- print STDERR " -t Number of worker childs to create (default: 1)\n";
- exit 1;
- }
-
- # It might be nice to be able to specify this, but it will break things as
- # they are now. This is on the TODO list :-)
-
- $opts{i} = ".";
-
- $| = 1;
-
- $SIG{CHLD} = 'IGNORE';
-
- # Create XML and XSLT parser contexts. Also create the root note for the
- # above mentioned XML file (used to feed the XSL transformation).
-
- my $parser = XML::LibXML->new('encoding'=>'utf-8');
- my $xslt_parser = XML::LibXSLT->new('encoding'=>'utf-8');
-
- # Parse the global stylesheet
-
- my $global_style_doc = $parser->parse_file($opts{i}."/fsfe.xsl");
- my $global_stylesheet = $xslt_parser->parse_stylesheet($global_style_doc);
-
- #
- # First topic of today: create all directories we need. Instead of creating
- # these as they are used, we create them in a batch at the beginning of each
- # run, so we won't have to worry about them later.
- # Note though that this also REMOVES the previous paths. You don't want to
- # build directly into the production web tree.
- #
- my @dirs = File::Find::Rule->directory()
- ->in($opts{i});
-
- while (my ($path, undef) = each %countries) {
- print STDERR "Resetting path for $path\n" unless $opts{q};
- rmtree($opts{o}.'/'.$path) unless ($opts{u} || $opts{n});
- my @paths = map { $opts{o}."/$path/".$_ } grep(!/^\.\.?$/, @dirs);
- foreach (@paths) {
- print "Creating $_\n" if $opts{d};
- mkpath($_) unless $opts{n};
- }
- }
-
- #
- # Here starts our real work. First we get ourselves a list of all files
- # that we need to worry about and then single out the XHTML files. We
- # create a hash of hashes, %bases, which contains the basename of each
- # file, together with the translations that it exists in.
- #
- my @files = File::Find::Rule->file()
- ->in($opts{i});
-
- my %bases;
- foreach (grep(/\.xhtml$/, @files)) {
- $_ =~ s/^$opts{i}\/?// unless $opts{i} eq ".";
- my ($lang) = ($_ =~ /\.([a-z][a-z])\.xhtml$/);
- unless ($lang) { $lang = "en"; }
- $_ =~ s/\.[a-z][a-z]\.xhtml$//;
- $_ =~ s/\.xhtml$//;
- $bases{$_}{$lang} = 1;
- }
-
- # Open the file where we will log all outdated and missing translations
- open (TRANSLATIONS, '>', "$opts{o}/translations.log");
-
- #
- # For each file, translation and focus, we create a new XML file. This will
- # contain all information that the XSL needs to produce a finished page.
- # The XML file will look like this:
- #
- # <buildinfo>
- # <trlist> <!-- All translations that this page exists in -->
- # <tr id="sv">Svenska</tr>
- # ...
- # </trlist>
- # <localmenuset> <!-- Local menu items for some directories -->
- # ...
- # </localmenuset>
- # <menuset> <!-- The menu items for the left hand bar -->
- # ...
- # </menuset>
- # <textset> <!-- The static text set for this language -->
- # ...
- # </textset>
- # <textsetbackup> <!-- The English textset as backup for missing translations -->
- # ...
- # </textsetbackup>
- # <document> <!-- The actual document, as read from the XHTML -->
- # <head>
- # <title>...</title>
- # <body>...</body>
- # </head>
- # </document>
- # </buildinfo>
- #
- # In addition to this, the buildinfo and document root will be equipped with
- # the following attributes:
- #
- # buildinfo/@original The language code of the original document
- # buildinfo/@filename The filename without language or trailing .html
- # buildinfo/@dirname The path to the file
- # buildinfo/@language The language that we're building into
- # buildinfo/@outdated Set to "yes" if the original is newer than this page
- # document/@language The language that this documents is in
- #
- #
- # $threads is the number of child processes to fork off to build the tree
- #
- unless ($threads = $opts{t}) {
- $threads = 1;
- }
-
- #
- # Start the required number of children, for each child we create a socket
- # pair to communicate between parent and child. This information is kept in
- # the %procs hash, which contains file handles for both child and parent.
- #
- foreach my $i (1..$threads) {
- $procs[$i]{child} = new IO::Handle;
- $procs[$i]{parent} = new IO::Handle;
-
- socketpair($procs[$i]{child}, $procs[$i]{parent}, AF_UNIX,
- SOCK_STREAM, PF_UNSPEC);
-
- $procs[$i]{child}->autoflush(1);
- $procs[$i]{parent}->autoflush(1);
- #$procs[$i]{child}->blocking(false);
- #$procs[$i]{parent}->blocking(false);
-
- if (fork()) {
- #
- # The parent doesn't do anything at this stage, except close one of
- # the filehandes not used.
- #
- close($procs[$i]{parent});
- } else {
- #
- # This is the main worker for the children, which wait for a command
- # to execute, either DIE or PROCESS. In the case of the first, the child
- # exists gracefully, in the case of the second, it calls on process()
- # to build the required page and languages.
- #
- # When waiting for the next page to be sent to it, the child sends NEXT
- # to the parent to signify that it's ready for the next command.
- #
- close($procs[$i]{child});
- my $io = $procs[$i]{parent};
- print $io "NEXT\n";
- while (!$io->error) {
- my $cmd = <$io>;
- if ($cmd =~ /DIE/) {
- exit;
- } elsif ($cmd =~ /PROCESS/) {
- chomp($cmd);
- my (undef, $file, $langs) = split(/\|/, $cmd);
- process($file, $langs);
- print $io "NEXT\n";
- }
- }
- exit;
- }
- }
-
- #
- # This sets up an IO::Select object with the filehandles of all children.
- # The parent uses this when looking for the next available child and blocks
- # until any child is ready.
- #
- my $s = IO::Select->new();
- foreach my $i (1..$threads) {
- $s->add($procs[$i]{child});
- }
-
- while (my ($file, $langs) = each %bases) {
- $s->can_read();
-
- my $done = 0;
- while (!$done) {
- foreach my $fh ($s->can_read()) {
- $cmd = <$fh>;
- if ($cmd =~ /NEXT/) {
- printf $fh "PROCESS|%s|%s\n", $file, join(':', keys(%{$langs}));
- $done = 1;
- last;
- }
- }
- }
- }
-
- #
- # When done, we send the DIE command to each child.
- #
- foreach my $i (1..$threads) {
- my $io = $procs[$i]{child};
- print $io "DIE\n";
- }
-
- #
- # This ensures a timely wait for every child to finish processing and shutdown.
- #
- while (wait() != -1) {
- sleep 2;
- }
-
- sub process {
- my ($file, $langs) = @_;
-
- print STDERR "Building $file.. \n" unless $opts{q};
- # Create the root note for the above mentioned XML file (used to feed the XSL
- # transformation).
-
- my $dom = XML::LibXML::Document->new("1.0", "utf-8");
- my $root = $dom->createElement("buildinfo");
- $dom->setDocumentElement($root);
-
- #
- # Set the current date, to use for comparision in the XSLT.
- #
- $root->setAttribute("date", $current_date);
-
- #
- # Find original language. It's en, unless we're in the country specific
- # se/, fr/, de/ and so on, directories.
- #
- $root->setAttribute("original", "en");
- my $srcfocus = "global";
- if ($file =~ /^([a-z][a-z])\//) {
- $srcfocus = "$1";
- $root->setAttribute("original", $countries{$1});
- }
-
- $root->setAttribute("filename", "/$file");
-
- #
- # Set the directory name attribute
- #
- my (undef, $current_dir, undef) = fileparse($file);
-
- $root->setAttribute("dirname", "$current_dir");
-
- #
- # Find all translations for this document, and create the trlist set
- # for them.
- #
- my $trlist = $dom->createElement("trlist");
- foreach my $lang (split(/:/, $langs)) {
- my $tr = $dom->createElement("tr");
- $tr->setAttribute("id", $lang);
- $tr->appendText($languages{$lang});
- $trlist->appendChild($tr);
- }
- $root->appendChild($trlist);
-
- #
- # Load the file with local menu's
- #
- my $localmenu = "$opts{i}/localmenuinfo.xml";
- if (-f $localmenu) {
- my $menudoc = $dom->createElement("localmenuset");
- $root->appendChild($menudoc);
- clone_document($menudoc, $localmenu);
- }
-
- #
- # Load English backup texts
- #
-
- my $backup = $dom->createElement("textsetbackup");
- $root->appendChild($backup);
- clone_document($backup, $opts{i}."/tools/texts-en.xml");
-
- #
- # Transform it, once for every focus!
- #
- while (my ($dir, undef) = each %countries) {
- # If we handle a focus specific file, only process it in that focus
- # -> we don't handle focus-specific files anymore, commenting next line out, since it's causing errors
- #next if (("$srcfocus" ne "global") && ("$dir" ne "$srcfocus"));
-
- print STDERR "$dir " unless $opts{q};
-
- #
- # And once for every language!
- #
- while (my ($lang, undef) = each %languages) {
- $root->setAttribute("language", $lang);
-
- #
- # This finds the source file to use. If we can't find a translation
- # into the language, it uses the english version instead, or that in
- # the local language. Or the first version it finds. This should be
- # made prettier.
- #
- my $document = $dom->createElement("document");
- $document->setAttribute("language", $lang);
- $root->appendChild($document);
-
- my $source = "$opts{i}/$file.$lang.xhtml";
- unless (-f $source) {
- my $missingsource = $source;
- if (-f "$opts{i}/$file.en.xhtml") {
- $document->setAttribute("language", "en");
- $source = "$opts{i}/$file.en.xhtml";
- } elsif (-f "$opts{i}/$file.".$root->getAttribute("original").".xhtml") {
- $document->setAttribute("language", $root->getAttribute("original"));
- $source = "$opts{i}/$file.".$root->getAttribute("original").".xhtml";
- } else {
- my $l = (keys %{$bases{$file}})[0];
- $document->setAttribute("language", $l);
- $source = "$opts{i}/$file.$l.xhtml";
- }
- if ($dir eq "global") {
- lock(*TRANSLATIONS);
- print TRANSLATIONS "$lang $missingsource $source\n";
- unlock(*TRANSLATIONS);
- }
- }
-
- if ( (stat("$opts{o}/$dir/$file.$lang.html"))[9] >
- (stat($source))[9] && $opts{u} && ! -f "$opts{i}/$file.xsl" ) {
- next;
- }
-
- #
- # Here begins automated magic for those pages which we need to
- # assemble other sets of informations for first (automatically
- # updated pages).
- #
- if (-f "$opts{i}/$file.xsl") {
- #
- # Settle down please, children. First we remove all previous
- # document leftovers.
- #
- foreach ($root->getElementsByTagName("document")) {
- $root->removeChild($_);
- }
- $root->appendChild($document);
-
- # Create the <timestamp> tag automatically for these documents
- my $timestamp = $dom->createElement("timestamp");
- $timestamp->appendText("\$"."Date: ".$current_time." \$ \$"."Author: automatic \$");
- $document->appendChild($timestamp);
-
- #
- # Get the list of sources and create the files hash. The files
- # hash contains the base name for each file we want to use, and
- # then the language for it as a value. It prefers a file in the
- # language we're building into, but will accept an English file as
- # a substitute.
- #
- # "Learn all that is learnable and return that information
- # to the Creator."
- #
- open(IN, '<', "$opts{i}/$file.sources");
- my @auto_sources = <IN>;
- close IN;
- my %files;
- foreach (@auto_sources) {
- if (/(.*):[a-z,]*global/ || /(.*):[a-z,]*$dir/) {
- foreach my $f (glob("$1*")) {
- if ($f =~ /(.*)\.([a-z][a-z])\.xml$/) {
- if (!$files{$1}) {
- $files{$1} = $2;
- } elsif ($2 eq $lang) {
- $files{$1} = $2;
- } elsif (($2 eq "en") &&
- ($files{$1} ne $lang)) {
- $files{$1} = $2;
- }
- }
- }
- }
- }
-
- #
- # With that information, we load the source document and create
- # a new element in it, called <set>, which will hold the combined
- # knowledge of all the sets in the source files.
- #
- my $sourcedoc = $parser->parse_file($source);
- $sourcedoc->documentElement->setAttribute("date", $current_date);
- $sourcedoc->documentElement->setAttribute("lang", $lang);
- my $auto_data = $sourcedoc->createElement("set");
-
- while (my ($base, $l) = each %files) {
- if (($dir eq "global") && ($l ne $lang)) {
- lock(*TRANSLATIONS);
- print TRANSLATIONS "$lang $base.$lang.xml $base.$l.xml\n";
- unlock(*TRANSLATIONS);
- }
- print STDERR "Loading $base.$l.xml\n" if $opts{d};
- my $source_data = $parser->parse_file("$base.$l.xml");
- foreach ($source_data->documentElement->childNodes) {
- my $c = $_->cloneNode(1);
- # add the filename to nodes (news, events, …) so that we can use it as an identifier (e.g. for RSS)
- if (ref($c) eq "XML::LibXML::Element") {
- $base =~ /.*[\/_]([^\/_]*$)/;
- $c->setAttribute( "filename", $1 );
- }
- $auto_data->appendChild($c);
- }
- }
- $sourcedoc->documentElement->appendChild($auto_data);
-
- #
- # Get the appropriate textset for this language. If one can't be
- # found, use the English. (I hope this never happens)
- #
- my $textlang = $lang;
- unless (-f $opts{i}."/tools/texts-content-$textlang.xml") {
- $textlang = "en";
- }
-
- my $textdoc = $sourcedoc->createElement("textset-content");
- $auto_data->appendChild($textdoc);
- clone_document($textdoc, $opts{i}."/tools/texts-content-$textlang.xml");
-
- # Get also backup texts from the English file
- my $textdocbak = $sourcedoc->createElement("textset-content-backup");
- $auto_data->appendChild($textdocbak);
- clone_document($textdocbak, $opts{i}."/tools/texts-content-en.xml");
-
- # TODO: optimise getting texts-content-xx.xml and texts-content-en.xml,
- # since it does not depend on the xsl file being treated, we should do it only once!
-
- #
- # Transform the document using the XSL file and then push the
- # result into the <document> element of the document we're building.
- #
- my $style_doc = $parser->parse_file("$opts{i}/$file.xsl");
- my $stylesheet = $xslt_parser->parse_stylesheet($style_doc);
- my $results = $stylesheet->transform($sourcedoc);
-
- foreach ($results->documentElement->childNodes) {
- my $c = $_->cloneNode(1);
- $document->appendChild($c);
- }
-
- #
- # Now, while we're just at it, we create the RSS feeds if we want any
- #
- if (-f "$opts{i}/$file.rss.xsl") {
- my $style_doc = $parser->parse_file("$opts{i}/$file.rss.xsl");
- my $stylesheet = $xslt_parser->parse_stylesheet($style_doc);
- my $results = $stylesheet->transform($sourcedoc);
- $stylesheet->output_file($results, "$opts{o}/$dir/$file.$lang.rss")
- unless $opts{n};
- }
-
- #
- # and possibly the corresponding iCal (ics) file
- #
- if (-f "$opts{i}/$file.ics.xsl") {
- my $style_doc = $parser->parse_file("$opts{i}/$file.ics.xsl");
- my $stylesheet = $xslt_parser->parse_stylesheet($style_doc);
- my $results = $stylesheet->transform($sourcedoc);
- $stylesheet->output_file($results, "$opts{o}/$dir/$file.$lang.ics")
- unless $opts{n};
- }
-
- } else {
- #
- # If this wasn't an automatically updating document, we simply
- # clone the contents of the source file into the document.
- #
- clone_document($document, $source);
- }
-
- #
- # Find out if this translation is to be regarded as outdated or not.
- # A translation is deemed outdated if it is more than 2 hours older
- # than the original. This makes sure a translation committed together
- # with the original (but maybe a second earlier) isn't marked outdated.
- #
- my $originalsource = "$file.".$root->getAttribute("original").".xhtml";
- if (( stat("$opts{i}/$originalsource"))[9] > (stat($source))[9] + 7200
- and not $cant_be_outdated{$file} ) {
- $root->setAttribute("outdated", "yes");
- if ($dir eq "global") {
- lock(*TRANSLATIONS);
- print TRANSLATIONS "$lang $source $originalsource\n";
- unlock(*TRANSLATIONS);
- }
- } else {
- $root->setAttribute("outdated", "no");
- }
-
- #
- # Get the appropriate textset for this language. If one can't be
- # found, use the English. (I hope this never happens)
- #
- my $textlang = $lang;
- unless (-f $opts{i}."/tools/texts-$textlang.xml") {
- $textlang = "en";
- }
-
- my $textdoc = $dom->createElement("textset");
- $root->appendChild($textdoc);
- clone_document($textdoc, $opts{i}."/tools/texts-$textlang.xml");
-
- #
- # Read the fundraising text, if it exists.
- #
- if (-f $opts{i}."/fundraising.$lang.xml") {
- my $fundraisingdoc = $dom->createElement("fundraising");
- $root->appendChild($fundraisingdoc);
- clone_document($fundraisingdoc, $opts{i}."/fundraising.$lang.xml");
- } elsif (-f $opts{i}."/fundraising.en.xml") {
- my $fundraisingdoc = $dom->createElement("fundraising");
- $root->appendChild($fundraisingdoc);
- clone_document($fundraisingdoc, $opts{i}."/fundraising.en.xml");
- }
-
-
- #
- # And then we do the same thing for the menues. But first we take the
- # global menu here, then we add any information that is specific to
- # the focus.
- #
- foreach ($root->getElementsByTagName("menuset")) {
- $root->removeChild($_);
- }
-
- my %menu;
- foreach ('global', $dir) {
- if (-f $opts{i}."/tools/menu-$_.xml") {
- my $menudoc = $parser->parse_file($opts{i}."/tools/menu-$_.xml");
- foreach my $n ($menudoc->documentElement->getElementsByTagName("menu")) {
- $menu{$n->getAttribute("id")} = $n;
- }
- }
- }
- my $menuroot = $dom->createElement("menuset");
- while (my ($id, $n) = each %menu) {
- my $m = $n->cloneNode(1);
- $menuroot->appendChild($m);
- }
- $root->appendChild($menuroot);
-
- #
- # Do the actual transformation.
- #
- my $results = $global_stylesheet->transform($dom);
-
- #
- # In post-processing, we replace links pointing back to ourselves
- # so that they point to the correct language.
- #
- foreach ($results->documentElement->getElementsByTagName("a")) {
- my $href = $_->getAttribute("href");
- if ($href =~ /^http:\/\/www.fsfe.org/) {
- if ($_->textContent != "Our global work") {
- $href =~ s/http:\/\/www.fsfe.org//;
- }
- }
- if (($href !~ /^http/) && ($href !~ /^#/)) {
- # Save possible anchor and remove it from URL
- my $anchor = $href;
- if (!($anchor =~ s/.*#/#/)) {
- $anchor = "";
- }
- $href =~ s/#.*//;
- # process URL
- if (($href =~ /\.html$/) && ($href !~ /\.[a-z][a-z]\.html$/)) {
- $href =~ s/\.html$/\.$lang.html/;
- } elsif (($href =~ /\.rss$/) && ($href !~ /\.[a-z][a-z]\.rss$/)) {
- $href =~ s/\.rss$/\.$lang.rss/;
- } elsif (($href =~ /\.ics$/) && ($href !~ /\.[a-z][a-z]\.ics$/)) {
- $href =~ s/\.ics$/\.$lang.ics/;
- } else {
- if (-d $opts{i}."/$href") {
- $href =~ s/\/?$/\/index.$lang.html/;
- } elsif ($href =~ /\/\w+$/) {
- $href .= ".$lang.html";
- }
- }
- # replace anchor
- $href .= $anchor;
- # For pages running on an external server, use full URL
- if ($document->getAttribute("external")) {
- $href = "http://www.fsfe.org$href";
- }
- $_->setAttribute("href", $href);
- }
- }
-
- print "Writing: $opts{o}/$dir/$file.$lang.html\n" if $opts{d};
-
- $global_stylesheet->output_file($results, "$opts{o}/$dir/$file.$lang.html")
- unless $opts{n};
- # Add foo.html.xx link which is used by Apache's MultiViews option when
- # a user enters foo.html as URL.
- link("$opts{o}/$dir/$file.$lang.html", "$opts{o}/$dir/$file.html.$lang")
- unless $opts{n};
- }
- }
- print STDERR "\n" unless $opts{q};
- }
-
- # Close the logfile for outdated and missing translations
- close (TRANSLATIONS);
-
- print STDERR "Fixing index links\n" unless $opts{q};
-
- while (my ($path, undef) = each %countries) {
- my @dirs = File::Find::Rule->directory()
- ->in($opts{o}."/$path");
- foreach (@dirs) {
- my $base = basename($_);
- while (my ($lang, undef) = each %languages) {
- if (-f "$_/$base.$lang.html" &&
- ! -f "$_/index.$lang.html") {
- link("$_/$base.$lang.html", "$_/index.$lang.html")
- unless $opts{n};
- link("$_/$base.html.$lang", "$_/index.html.$lang")
- unless $opts{n};
- }
- }
- }
- }
-
- #
- # For all files that are not XHTML source files, we copy them verbatim to
- # the final location, for each focus. These should be links instead to
- # prevent us from wasting disk space.
- #
- print STDERR "Copying misc files\n" unless $opts{q};
- foreach (grep(!/\.sources$/, grep(!/\.xsl$/, grep(!/\.xml$/, grep(!/\.xhtml$/,
- @files))))) {
- while (my ($dir, undef) = each %countries) {
- if (-f "$opts{i}/$_" && !$opts{n}) {
- link("$opts{i}/$_", "$opts{o}/$dir/$_");
- }
- }
- }
- #
- # Helper function that clones a document. It accepts an XML node and
- # a filename as parameters. Using this, it loads the source file into
- # the XML node.
- #
- sub clone_document {
- my ($doc, $source) = @_;
- my $root = $doc->parentNode;
-
- print "Source: $source\n" if $opts{d};
-
- foreach ($root->getElementsByTagName($doc->nodeName)) {
- $root->removeChild($_);
- }
- $root->appendChild($doc);
-
- my $parser = XML::LibXML->new('encoding'=>'utf-8');
- $parser->load_ext_dtd(0);
- $parser->recover(1);
-
- my $sourcedoc = $parser->parse_file($source);
- foreach ($sourcedoc->documentElement->childNodes) {
- $_->unbindNode();
- my $n = $_->cloneNode(1);
- $doc->appendChild($n);
- }
- if ($sourcedoc->documentElement->getAttribute("external")) {
- $doc->setAttribute("external", "yes");
- }
- if ($sourcedoc->documentElement->getAttribute("newsdate")) {
- # necessary for xhtml news files
- $doc->setAttribute("newsdate", $sourcedoc->documentElement->getAttribute("newsdate"));
- }
- if ($sourcedoc->documentElement->getAttribute("type")) {
- # necessary to differentiate news and newsletter pages
- # TODO: find a way to copy all such attributes!
- $doc->setAttribute("type", $sourcedoc->documentElement->getAttribute("type"));
- }
- }
-
- #
- # Helper functions to lock and unlock the translation log.
- #
- sub lock {
- my ($fh) = @_;
-
- flock($fh, LOCK_EX);
- seek($fh, 0, 2);
- }
-
- sub unlock {
- my ($fh) = @_;
- flock($fh, LOCK_UN);
- }
|