...XSH - XML Editing Shell

Support This Project


SF project page
Hosted on SourceForge
RSS feed
 

How this website was made with XSH

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'>&lt;${pagetitle}/&gt;</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;
};