This chapter gives a few more practical examples of the use of
chpp
, illustrating a few of its many possible applications.
Let us assume you have gathered a collection of song lyrics by various performers as text files. You wish to generate not only HTML files for all the songs but also an index page for each performer which contains, sorted by album, hyperlinks to all the song HTML files.
The song files you have collected all look like this:
Gloria I try to sing this song. I try to stand up. But I can't find my feet. I try, I try to speak up. But only in you I'm complete. ...
Furthermore, assume you have created a directory for each performer
containing a directory for each album where the song files reside. The
names of the directories not necessarily match the corresponding
performer's name or the album's, as these often contain spaces, which
are uncommon in file names. The same applies to the song file
names. Thus, our first task is to somehow associate the names of the
performers, albums and songs with the song files. We have a bonus for
the song names since these are included in the song files themselves (in
the first line). For simplicity we will include this information in the
files themselves, namely as HTML comments containing assignments in
chpp
syntax. To modify the files, we will use the following
chpp
script:
#include files.chh %<file=%fopen(%SONGFILENAME)>\ %<songname=%removews(%fgets(%file))>\ <!-- %%<song=%songname>%%<album=%ALBUM>%%<perf=%PERF> --> %frest(%file)\ %fclose(%file)\
The variables SONGFILENAME
, ALBUM
and PERF
must be
set via the command-line. We can now convert all files in one album with
the following shell-command (assuming we are in the correct directory
and that all song files have the extension `.txt'):
$ pwd /home/schani/lyrics/u2/october $ for fn in *.txt ; do > chpp -DSONGFILENAME=$fn -DALBUM='October' \ > -DPERF='U2' ../../convert.ch >../$fn > done
Note that we have generated the modified files in the performer's directory. After we have done this for all albums, we can delete the album directories. The song files now look like this:
<!-- %<song=Gloria>%<album=October>%<perf=U2> --> I try to sing this song. I try to stand up. But I can't find my feet. I try, I try to speak up. But only in you I'm complete. ...
We will now generate HTML files for the songs. For simplicity, we will
generate a simple HTML file which contains the song text in a
<pre>
block:
#include files.chh %<file=%fopen(%SONGFILENAME)>%void(%{%fgets(%file)})\ \ <html> <head> <title>%song - %album - %perf</title> </head> <body> <h1>%song</h1> <pre> %frest(%file)\ </pre> </body> </html> %fclose(file)\
Since the information about the song title, album and performer is coded
as chpp
code in the first line, we can obtain it by just
evaluating it and ignoring its result. With this script we can now
generate HTML files for all songs of one performer with the following
bash
command:
$ pwd /home/schani/lyrics/u2 $ for fn in *.txt ; do > chpp -DSONGFILENAME=$fn ../template.ch >${fn%.txt}.html > done
Finally, we need to generate an index file for each performer containing
hyperlinks to all songs. This file is generated by the following
chpp
script:
#include files.chh %<albums=%hash()>\ %<file=%fpipe(/bin/sh,-c,ls *.txt)>\ %foreach(songfilename,%split(%removews(%frest(file)),%"[ \n]+"), %<songfile=%fopen(%songfilename)>\ %void(%{%fgets(%songfile)})\ %fclose(%songfile)\ %<htmlfile=%void(%match(%"(.*)\\.",%songfilename))%1.html>\ %ifdefkey(%albums,%album, %<albums{%album}{%song}=%htmlfile> , %<albums{%album}=%hash(%song,%htmlfile)> )\ )\ %fclose(file)\ \ <html> <head> <title>%perf</title> </head> <body> <h1>%perf</h1> %foreach(album,%sort(%keys(%albums)),\ <h2>%album</h2>%"\n"<blockquote>%"\n"\ %foreach(song,%sort(%keys(%albums{%album})),\ <p><a href="%albums{%album}{%song}">%song</a>%"\n"\ )\ </blockquote>%"\n"\ )\ </body> </html>
The first part of the file gets all information about the songs in the
current directory. It generates an entry in the hash %albums
for
each album, which in turn is a hash containing all names of the HTML
files indexed by the song names. The second part just iterates through
this table, producing an <h2>
for each album and an anchor for
each song within.
Suppose you were to create a web-site with three main pages: News, Tips and Tricks. Each of these pages should have a layout similar to this:
N N EEEE W W SSS News NN N E W W S N N N EEE W W W SSS _Tips_ N NN E W W W S N N EEEE W W SSS _Tricks_ -------------------------------------------- This is good news! -------------------------------------------- News | _Tips_ | _Tricks_
The header consists of a big graphic banner denoting the title of this particular page. On the right side of the banner are three little graphics, each standing for one of the pages. Two of them lead to the other two pages, whereas the one for the actual page is not functional and grayed out.
The footer consists of a textual link bar with two active links and one 'inactive link' (normal text) for the actual page.
Although these three pages are easy to implement with conventional
methods, this becomes increasingly difficult when there are more pages
or even hierarchical structures of pages. We will thus use this as an
example of how to easily create HTML pages with chpp
, leaving
more sophisticated designs to the gentle reader.
Ideally, we would like, say, the news source file (which we name `news.csml'), as rendered above, to look like this:
#include header.chml This is good news! #include footer.chml
This way, we need not be concerned with the design of the header and footer when we work on the pages and we can easily modify the header and footer in one central place, although they apply to our whole web-site.
Now we need a place where we can enter all the names of the main pages of our web-site and associate them with their corresponding files. We call this file `menu.chml':
%addmenuentry(News,news.csml) %addmenuentry(Tips,tips.csml) %addmenuentry(Tricks,tricks.csml)
We simply assume that the generated HTML files will have the same base-name as the sources but the extension `.html'. We furthermore assume that for each main-page we have three additional graphics available, namely one containing the large banner and two containing the small icons, where one of the two is grayed out. These images have the format JPEG, hence the extension `.jpg'. Their base-names consist of the base-names of their main-pages followed by one of the suffixes `_l', `_s' and `_s_g', where `l' denotes large, `s' small and `g' gray.
The include-file `header.chml' fulfills two purposes: Firstly, it
must process the information of the file `menu.chml' and secondly,
it must generate the header of the HTML file. Since the latter depends
on the former, we will focus our attention on processing the information
of `menu.chml'. Each main page will be represented by a hash
containing the keys filename
, name
, htmlfilename
,
imglarge
, imgsmall
and imgsmallgray
, the meanings
of which should be obvious. We will collect all these hashes in a list
named menu
, which will contain these hashes in the order in which
they were entered in `menu.chml'. Since `menu.chml' is already
in chpp
syntax, all we need to do is to define a macro
addmenuentry
which creates such a hash and appends it to the list
menu
:
%<menu=%list()>\ %define(addmenuentry,name,filename, %<basename=%void(%match(%"(.*)\\.csml$",%filename))%1>\ %<menu=%append(%menu, %hash(filename,%filename, name,%name, htmlfilename,%basename.html, imglarge,%<basename>_l.jpg, imgsmall,%<basename>_s.jpg, imgsmallgray,%<basename>_s_g.jpg))> )\
Now we can include `menu.chml' for its side-effects:
%void( #include menu.chml )\
Finally, for convenience, we define a variable thisentry
which
contains the hash that applies to the currently processed page:
%<thisentry=%foreach(menuentry,%menu, %if(%streq(%menuentry{filename},%mainfilename),%menuentry))>\
Now we are set and can generate the header of the HTML file:
<html> <head> <title>%thisentry{name}</title> </head> <body> <table> <td> <img src="%thisentry{imglarge}" alt="%thisentry{name}"> <td> #include choicestrip.chml </table> <hr>
The file `choicestrip.chml' generates a vertical table consisting of the small images for the main-pages with links. It is quite simple:
<table border=0 cellspacing=0 cellpadding=0> %foreach(menuentry,%menu, <tr><td>\ %if(%streq(%menuentry{filename},%thisentry{filename}), <img src="%menuentry{imgsmallgray}" alt="%menuentry{name}"> , <a href="%menuentry{htmlfilename}">\ <img border=0 src="%menuentry{imgsmall}" alt="%menuentry{name}">\ </a> ) ) </table>
The footer is even more simple: It contains a horizontal rule and a choice-bar:
<hr> #include choicebar.chml </body> </html>
`choicebar.chml' is similar to `choicestrip.chml':
<h5><center> %<barentries=%list()>\ %foreach(menuentry,%menu, %<barentries=%append(%barentries, %if(%streq(%menuentry{filename},%thisentry{filename}), %menuentry{name} , <a href="%menuentry{htmlfilename}">%menuentry{name}</a> ) )> )\ %join(%" | ",%barentries) </center></h5>
All we need now is a comfortable way to create all HTML files from the
sources. This is what makefiles are for. They have the additional
advantage that files are regenerated only when needed, i.e. when of the
files that the file to be created depends on has changed. A makefile for
gnumake
suitable for our simple purposes would look like this:
CHFILES=header.chml footer.chml choicebar.chml choicestrip.chml all : news.html tips.html tricks.html %.html : %.csml ../../macros -o $ $< news.html tricks.html tips.html : $(CHFILES) clean : rm -f news.html tips.html tricks.html
Suppose we have a simple context-sensitive grammar, which we want to use to generate sentences, which is quite the opposite of parsing sentences of that grammar. To illustrate this more clearly, let us assume we have file like this:
--sentence $subject $verb $object. $subject, while $gerund $object, $verb $object. $subject watches $object $gerund $object. --subject $person The $adjective $person --object $person --person Butthead Mrs Krabappel Charlie Brown Mrs Robinson --adjective observant naive embarassed --verb kisses kicks envies --gerund holding zapping hugging smashing
The file is separated into several categories containing so-called
productions for so-called non-terminals. The first non-terminal is
called the start non-terminal, which is, in out case, sentence
.
We start by picking by random one of the right-hand-sides of the
productions of the start non-terminal (i.e. one of the lines following
the introduction of sentence
). Now we repeat the following cycle
until our string contains no more placeholders (words prefixed by the
dollar sign ($
)): Replace the first placeholder by the
right-hand-side of a randomly picked production for that placeholder.
The process could evolve like this:
$sentence $subject, while $gerund $object, $verb $object. The $adjective $person, while $gerund $object, $verb $object. The naive $person, while $gerund $object, $verb $object. The naive Charlie Brown, while $gerund $object, $verb $object. The naive Charlie Brown, while hugging $object, $verb $object. The naive Charlie Brown, while hugging $person, $verb $object. The naive Charlie Brown, while hugging Mrs Robinson, $verb $object. The naive Charlie Brown, while hugging Mrs Robinson, kicks $object. The naive Charlie Brown, while hugging Mrs Robinson, kicks $person. The naive Charlie Brown, while hugging Mrs Robinson, kicks Butthead.
It is easy to see that this simple algorithm even allows for recursive grammars.
This example is not a typical application for a pre-processor. It should
rather demonstrate that chpp
can be used very succesfully for
tackling problems not in its direct field of application, i.e. that it
is suitable for more general problems.
You may have noticed that our grammar file is not in chpp
syntax,
so we have the choice of either converting it or parsing it at
run-time. Since the former has the disadvantage of being more
complicated in usage (the grammar would have to be converted each time
it is changed) and is not easier to implement than the letter, the
choice is obvious.
The first step of our application is reading in the grammar file, which
we call `grammar'. Its content will be stored in a hash
data
, where the keys are the names of the non-terminals and the
values are the lists of the right-hand-sides of the corresponding
productions.
%<file=%fopen(grammar)>\ %<current=runaway>\ %<data=%hash(runaway,%list())>\ %until(%feof(%file), %<line=%removews(%fgets(%file))>\ %if(%[%match(%"^--([a-zA-Z0-9_]+)",%line)!=-1], %<current=%1>\ %<data{%current}=%list()>\ %ifdef(start,,%<start=%current>) , %if(%line,%<data{%current}[%listlength(%data{%current})]=%line>) ) )\ %fclose(%file)\
We then proceed to define some variables and macros. First, if the
variable n
, which will denote the number of sentences generated,
is not defined (it could be defined on the command-line), it is set to
10
:
%ifdef(n,,%<n=10>)\
The macro some
, when called with the name of a non-terminal,
returns the right-hand-side of a random production for that
non-terminal:
%define(some,nt,%data{%nt}[%random(%listlength(%data{%nt}))])\
The generation of the sentences is now a fairly trivial task:
%for(i,1,%n, %<current=%some(%start)>\ %while(%<mp=%match(%"\\$([a-zA-Z0-9_]+)",%current)>%[mp!=-1], %<current=%replacesubstring(%current,%mp,%strlength(%0),%some(%1))> )\ %current%"\n\n" )\
Go to the first, previous, next, last section, table of contents.