Generating website navigation using perl, sed, and make

The problem – Site navigation without frames

I wanted to add an easy way to navigate to any of my web pages, but I didn’t want to use frames, or break any of the current links. I also have a “nested” organization – where I have directories that contain page, so some pages are “deeper” than others.

One way to do this is to add a navigation panel to the left of every page, but this required changing each and every page. I wanted something that would be simple and quick, so obviously I used some simple UNIX utilities.  To see how the final results look, visit my web page on Unix Tutorials.

[I improved this later. See this post – Bruce]

Defining the navigation structure

I wanted to have one file that defines the navigation I wanted to use.  I decided on a text file called navigation.txt that has three columns of information.

  • The first column determines the “depth” of the index, where the top-most level is 1, while the next level of indentation is 2, etc.
  • The second field is the filename for the link
  • The third column is the name that will be displayed.

Here is a subset sample of my input file, called navigation.txt:

1 Unix/index.html Unix/Linux
2 Unix/Quote.html Quotes
2 Unix/Grep.html grep
2 Unix/Awk.html awk
2 Unix/sed.html sed
1 Security/index.html Security
2 Security/Wireless.html Wireless

Generating the navigation HTML using perl

I next needed a way to read this file and generate some HTML. While I could have used a language like python or AWK, I decided to write a simple perl script called makenav.pl

#!/usr/bin/perl -w
use strict;
print "<div id=\"navigation\">\n";
print "<img src=\"pic.jpg\" alt=\"pic\" width=\"240\" height=\"80\">\n";
print "<h2>Site<br> Navigation</h2>\n";
my $indent = 0;
$|=1; # don't buffer output
my $line;
my ($deep, $link, $desc);
while (defined($line=<>)) {
    chomp($line);
    if ($line =~ /^\s*$/) {
      # ignore blank lines
    } else {
      ($deep,$link,$desc) = split(/\s+/,$line,3);
      if (!(defined($deep))) {
        printf(STDERR "Deep not defined in line '%s'\n", $line);
        die "ERROR";
      }
      if ($deep !~ /\d+/) {
        printf(STDERR "Bad value of depth (column 1) in line '%s'\n", $line);
        die "ERROR";
      }
      if ($deep > $indent) {
        $indent++;
        print "\n<ul>\n";
      } elsif ($deep == $indent) {
        print "</li>\n";
      } elsif ($deep < $indent) {
        $indent--;
        print "</li>\n</ul>\n";
      } else {
        die "How did I get here?!"; # paranoid programming
      }
      printf("<li><a href = \"../%s\">%s</a>",$link, $desc);
    }
}
print "</li>\n</ul>\n";

I used this script to generate a file called navigation.nav:

makenav.pl <navigation.txt >navigation.nav

Preparing the HTML files beforehand

Since I was going to be modifying all of my *.html files, I decided to rename them, and use a different extension besides *.html. So I chose *.in for “input.” I also wanted to add a special “marker” line right after the <body> line in the file.

I wrote a shell script, called makein,  that searched for “<body>” and added a new line afterwards. I also added a reference to a new CSS file, called myCSS.css.

#!/bin/sh
IFILE=${1?'Missing argument'}
OFILE="$IFILE.in"
sed  <$IFILE >$OFILE '
/<\/[hH][eE][aA][dD]>/ i \
<link href="myCSS.css" rel="stylesheet" type="text/css"
/<[Bb][Oo][Dd][Yy]>/ a\
<!-- INCLUDE Navigation -->
'

I then executed this on each of the HTML files

for i in `find . -name \*.html`
do
    makein "$i"
done

Creating the modified *.html files

I created a shell script called include:

#!/bin/sh
INCLUDE=${1?'Missing include file'}
shift
IFILE=${1?'Missing input file'}
OFILE=`echo "$IFILE"  | sed 's/\.in$//'` # remove the ".in"
if [ "$IFILE" = "$OFILE" ]
then
    echo input file $IFILE same as output file $OFILE - exit
    exit
fi
sed "/<!-- INCLUDE [Nn]avigation/ r $INCLUDE
" <$IFILE >$OFILE

The sample usage is

./include navigation.nav file.html.in

I added a check to make sure I didn’t overwrite the same file, because that’s the safe thing to do.

Putting it all together with a Makefile

As I wrote this, I also created a Makefile for the unix make(1) utility.

all: include_all navigation.nav index.html
clean:
	rm include_all
install: include_all myCSS.css
	cp *.html *.css /var/www/html
	cp Unix/*.html *.css /var/www/html/Unix
	cp Security/*.html *.css /var/www/html/Security
	touch install
navigation.nav: navigation.txt makenav.pl
	./makenav.pl <navigation.txt > navigation.nav

index.html: navigation.nav  include makenav.pl
	./include navigation.nav index.html.in 

include_all: index.html Makefile navigation.nav include navigation.nav
	for i in `find . -name \*.in`;do ./include navigation.nav $$i;echo generated $$i;done
	touch include_all

I could have make a recursive Makefile, but this example was simple. Notice that this makefile recognizes when the input or the scripts are modified.

Regenerating the HTML files

If I change my navigation.txt file, I can just type “make.” However, if I change my input files (the *.in files), I need to do a “make clean”

make clean
make install

Once I preview the files locally, I copy them onto the remote web server.

Advertisements
This entry was posted in Shell Scripting, Technology and tagged , , , , , , . Bookmark the permalink.

One Response to Generating website navigation using perl, sed, and make

  1. Pingback: Creating Table of Contents for static web pages using sed, make, and perl | The Grymoire

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s