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; };