The following code is the XSH source used to generate HTML pages on this web site from simple XML sources and templates.
#!/usr/bin/env xsh2
# I use -*-cperl-*- mode to edit XSH files in Emacs
# This script is written in XSH2 language (XSH 2.0 pre-release)
# The structure of the site is given by a simple XML file "meta.xml",
# which contains <var> tags with simple substitution patterns,
# <docvar> tags with file substitution patterns and <page> tags which
# associate XML sources with HTML templates to be used.
# quiet;
keep_blanks 1;
switch-to-new-documents 0;
indent 0;
perl { use Time::Local };
$M := open meta.xml;
# preprocess
for $M//xsh:eval {
my $text := eval string(.);
xinsert text $text replace .;
}
$pages=$M/make/pages/page;
# count number of lines of this code
$LINES = { `wc -l < $PROGRAM_NAME` };
# create a new page $T and populate it with a
# links to other pages
# returns new a page
def new_page
$name # filename of the resulting page
$template # template to use
{
validation 0;
my $T := open $template;
# create links to other pages
my $sitemap=$T//text()[ . = '%sitemap%' ];
if ($sitemap) {
foreach ($pages[ @index='1' ]) {
my $pagename = string(@name);
my $pagedoc = { $pagedocs{ $pagename } };
my $pagetitle =
xsh:if($pagedoc/template/shorttitle,
string($pagedoc/template/shorttitle/text()),
string($pagedoc/template/title/text()));
my $chunk := insert chunk
"<a href='${pagename}.html'><${pagetitle}/></a><br/><br/>"
append $sitemap/..;
if ($pagename = $name)
wrap 'u' $chunk/a/text();
}
remove $sitemap;
}
return $T;
}
# expand %content% in a template to a given nodelist
def insert_content $T $content {
xcopy $content replace $T//text()[string(.)='%content%'];
}
# expand %title% in a template to a given nodelist
def insert_title $T $title {
xcopy $title replace $T//text()[string(.)='%title%'];
}
# substitute %make_xsh_lines% with number of lines of this code
def substitute_makexsh_lines $T {
map :i { s/%make_xsh_lines%/$LINES/g; } $T//text()[contains(.,'%make_xsh_lines%')];
}
# substitute document variables with asociated document content
def substitute_docvars $T {
foreach $M/make/docvar {
my $name = string(@name);
my $target = $T//text()[string(.)=concat('%',$name,'%')];
if ($target) {
my $doc = { $docvar{$name} };
xcopy $doc/* replace $target;
}
};
}
# substitute common variables
def substitute_vars $T {
foreach $M/make/var {
my $name = string(@name);
my $content = string(.);
map :i { s/%${name}%/$content/g; } $T//text()[contains(.,'%${name}%')];
map :i { s/%${name}%/$content/g; } $T//@*[contains(.,'%${name}%')];
}
}
def finish_page $T $filename {
substitute_makexsh_lines $T;
substitute_docvars $T;
substitute_vars $T;
save --file {"html/${filename}.html"} $T;
echo {"FINISHED: html/${filename}.html"};
close $T;
}
# replace %target with links to all news pages
def insert_span $total_news $max_news $current_series $total_series $target
{
my $links =
{
join "|",
map {
my $first=(($_-1)*$max_news+1);
my $last=($total_news<($_*$max_news) ? $total_news : ($_*$max_news));
my $span= ($first==$last) ? "$first" : "$first-$last";
if ($_ != $current_series) {
" <a href='news_$_.html'>$span</a> "
} else {
" <b>$span</b> "
}
} (1..$total_series);
};
insert chunk concat("<small>News: ",$links,"</small>") replace $target;
}
# create news pages
def insert_news
$T
$template # template for later news
$news_page_template # news template
$news_template # news template
$max_news
$rss
$rss_template
$news
{
my $news_count=1;
my $total_news=count($news);
my $later_news_links='';
my $news_series=1;
# compute number of news pages
my $total_series= { (int($total_news/$max_news)+($total_news % $max_news>0)) };
my $target=$T//text()[string(.)='%news%'];
my $RSS := open :B $rss_template;
my $N := open $news_template;
foreach ($news) {
if ($max_news > 0 and $news_count>1 and
(($news_count - 1) mod $max_news) = 0) {
# finish the current page
insert_span $total_news $max_news $news_series $total_series $target;
# leaving the first news page open, will be finished by the calling
# part of the script
if ($news_series > 1) finish_page $T "news_${news_series}";
# start a new page
$news_series += 1;
$T := new_page "news_${news_series}" $template;
do {
my $LTN := open $news_page_template;
insert_title $T $LTN/template/title/node();
insert_content $T $LTN/template/page/node();
};
$target = $T//text()[string(.)='%news%'];
}
my $guid = concat('g-',translate(normalize-space(date),' :,+','-_'));
do {
my $NTT := clone $N;
xcopy title/text() replace $NTT//text()[string(.)='%title%'];
xcopy date/node() replace $NTT//text()[string(.)='%date%'];
xcopy content/node() replace $NTT//text()[string(.)='%content%'];
my $a := wrap :i 'a' $NTT/template;
xcopy xsh:new-attribute('name',$guid) into $a;
xcopy $NTT/template/node() before $target;
};
my $rss_item := xinsert element "item" into $RSS/rss/channel;
set $rss_item/title string(title);
set $rss_item/link "http://xsh.sourceforge.net/news_${news_series}.html#${guid}";
set $rss_item/guid/@isPermaLink "false";
set $rss_item/guid $guid;
my $date = string(date);
perl {
if ($date=~/^\s*(\w+)\s+(\w+)\s+(\d+)\s+(\d{2}:\d{2}:\d{2})\s+(\d{4})\s*$/) {
$date = "$1, $3 $2 $5 $4 GMT";
} else {
warn "Wrong date format: $date\n";
}
};
set $rss_item/pubDate $date;
set $rss_item/description xsh:serialize(content/node());
$news_count += 1;
};
if ($news_series > 0) {
insert_span $total_news $max_news $news_series $total_series $target;
finish_page $T "news_${news_series}";
} else {
remove $target;
}
substitute_makexsh_lines $RSS;
substitute_docvars $RSS;
substitute_vars $RSS;
my $rss_items = $RSS/rss/channel/item;
move { reverse @$rss_items } replace $rss_items;
save --indent --file "html/${rss}" $RSS;
close $RSS;
close $N;
}
#end insert_news
# open XML page templates
validation 0; # turn validation off
foreach $pages {
my $name = string(@name);
my $doc := open { "t_${name}.xml" };
perl { $pagedocs{$name} = $doc };
# echo "OPENED: t_${name}.xml"; # this is just my debug print
}
# open documents for docvars
foreach $M/make/docvar { # some content may come directly from
my $name = string(@name); # some XML/HTML source or pipe
my $id = "docvar_${name}";
my $doc = &{
if (string(@type)='pipe') {
open --pipe (.);
} elsif (string(@type)='html') {
open --format html (.);
} else {
open (.);
}
};
perl { $docvar{ $name } = $doc };
}
# Create pages from templates
foreach $pages {
$this_page = .;
my $name = string(@name);
echo {"PROCESSING: $name"}; # this is just my debug print
my $T := new_page $name concat(@template,".xml");
my $pagedoc = { $pagedocs{$name} };
# copy the content and titile of the page from the XML template to
# the HTML template
insert_title $T $pagedoc/template/title/node();
insert_content $T $pagedoc/template/page/node();
# if the page is a news page, create news boxes from the XML templates
if (@news) {
my $replace = $T//text()[string(.)='%news%'];
if ($replace) {
my $max_news = xsh:if(@max-news,string(@max-news),0);
my $N := open @news;
insert_news $T
concat(@template,".xml")
string(@later-news)
string(@news-template)
$max_news
string(@rss)
string(@rss-template)
$N//news;
}
};
finish_page $T $name;
};

