#!/usr/bin/perl -w
#
# MENUIZE
#
# This script provides a set of HTML files with a menu structure, based on 
# metadata embedded in comments. Makes it easy to have menus that don't use
# frames or server-side includes or scripts, without maintaining them by hand.
#
# The input files are named on the command line; last argument is the target
# directory.
#
# Input documents may contain any number of menu items. Each item starts with
# <!-- MENU and may contain an arbitrary number of attr=value pairs before the
# comment is closed. 
#
# Available attributes:
#
# PARENT		- Defines title of parent to group under in menu
# TITLE			- Defines title in menu
# URL			- Defines URL for this document. If FILE occurs in it,
#			  it is replaced with the current filename.
#			  If FILE doesn't occur in it at all, it is assumed to
#			  be an external link, so it won't be greyed out while
#			  viewing the file in which the entry appears.
#
# Within the menu, all items are grouped under the same parent item in the
# order as specified on the command line.
#
# The generated menu is inserted in the output document at a location
# designated by <!-- INSERT_MENU -->. Otherwise the output is the same
# as the input.
#
# Implementation notes:
#
# * first the files are walked through in command line order, building a tree.
#   The tree looks like a list of references to hashes. Each hash has three
#   keys other than the attributes listed above: file, parref and subref. 
#   The latter two are references to this node's parent hash and child list(!), 
#   respectively.
#   
#   This is done by building a hash from each MENU pseudo-tag that we find.
#
# * Next, the tree is walked in list order, outputting the documents
#   containing a menu created by walking the whole tree again while being 
#   aware of the current document (for highlighting), and the original bare 
#   HTML document, embedded in tags to form the full one.
#
# Author: Emile van Bergen, emile@evbergen.xs4all.nl


$BORDER=0;
$SELCLR="#6b7eb2";


##
#
# MAIN

die("Not enough arguments!\n") unless $#ARGV >= 1;
my $outputdir = pop(@ARGV);

# Save @ARGV
my @argv = @ARGV;

# Loop through files
$/ = '-->';
my %root;
my $curref;
while(<>) {
	s/.*<!-- MENU//s or next;	# Delete before tag; skip if none
	s/-->.*//s;			# Delete content after close

	# Keep current relative file name

	my $file = $ARGV; $file =~ s/.*\///;

	# We now have left a series of attrs and values - take them one by one

	my $newref;
	while(s/\s*			# optional whitespace
		([A-Za-z0-9_]+)		# Attribute ($1)
		\s*=\s*			# optional ws., equals sign, opt. ws.
		(
		  "(([^"]|\\")*[^\\])"| # quoted Value ($3), or
		  ([^\s]+)		# single word Value ($5)
		)
		(
		  \s+|			# ended by one or more spaces
		  -->|			# or end-of-tag
		  $			# or end-of-record
		)//x) {

		my $atr = lc $1;	# Attributes are lowercase
		my $val = $5 || $3;	# Take single word or non-quoted part

		$$newref{$atr} = $val;	# Save into new hash
	}

	# Select parent node to use

	if ($$newref{'parent'}) {

		# Search for it, starting from last added node
		while($$curref{'parref'} &&
		      (! $$curref{'title'} ||
		       ! $$newref{'parent'} ||
		       $$curref{'title'} ne $$newref{'parent'})) {
			$curref = $$curref{'parref'};
		}
	
		# Not found: add automatic parent to root first
		unless ($$curref{'parref'}) {
			my $tmpref;
			$$tmpref{'title'} = $$newref{'parent'};
			push(@{$root{'subref'}}, $tmpref);
			$$tmpref{'parref'} = \%root;
			$curref = $tmpref;
		}
	}
	else {
		# No parent specified; use root as parent
		$curref = \%root;
	}

	# Add new to parent and make current

	push(@{$$curref{'subref'}}, $newref);
	$$newref{'parref'} = $curref;
	$curref = $newref;

	# Disable link for title if URL equals FILE.
	# Highlight title and parents if URL contains FILE.

	if ($$curref{'url'}) {
		if ($$curref{'url'} eq 'FILE') {
			$$curref{"dis:$file"} = 1;
		}
		if ($$curref{'url'} =~ s/FILE/$file/ ) {

			# URL contains FILE; higlight this and parents 
			for($tmpref = $curref; 
			    $$tmpref{'parref'}; 
			    $tmpref=$$tmpref{'parref'}) {
				$$tmpref{"hl:$file"} = 1;
			}
		}
		else {
			# URL does not contain FILE; add ... to title
			$$curref{'title'} .= '<B>...</B>';
		}		
	}
}

#print STDERR "\nTREE:\n\n";
#showtree(\%root);
#exit 0;

# Loop through files mentioned on saved command line

for my $file (@argv) {

	# Open input file
	print STDERR "CURFILE : $file\n\n";
	open(IN, "< $file") or die("Cannot open input file $file again: $!\n");

	# Make file name relative and open output file
	$file =~ s/.*\///;
	open(STDOUT, "> $outputdir/$file") 
		or die("Cannot open output file $outputdir/$file: $!\n");


	$/ = '-->';
	while(<IN>) {
		s/<!-- MENU.*-->//s;
		if (s/<!-- INSERT_MENU -->//) {
			print $_;
			print "<!-- Generated by menuize.pl -->\n\n";
			output(\%root, $file);
		}
		else {
			print $_;
		}
	}

	close(STDOUT) || die("Cannot close output file $outputdir/$file: $!\n");
	close(IN);
}

exit 0;


##
#
# SHOWTREE - Debug the tree assembly

sub showtree {
	(my $ref, my $depth) = @_;
	$depth ||= 0;
	my $indent = "\t" x $depth;
	
	print STDERR "$indent" . "NODE $ref:\n";

	# Print all other keys
	print STDERR "$indent" . "- keys:\n";
	for my $key (keys %$ref) {
		my $val = $$ref{$key} || "UNDEF";
		print STDERR "$indent\t$key=$val\n";
	}

	# Print all childs in array referenced to by data of 'subref' key
	if (defined($$ref{'subref'})) {
		print STDERR "$indent" . "- children:\n";
		foreach my $child (@{$$ref{'subref'}}) {
			showtree($child, $depth + 1);
		}
	}
}


##
#
# RECOUTPUT - Recursively writes menu

sub recoutput {
	(my $ref, my $curfile, my $depth) = @_; 
	$curfile ||= "";
	$depth ||= 0;

	if ($depth > 0) {

	    if ($$ref{'title'}) {
		
#		print STDERR "title     : $$ref{'title'}\n";
#		my $pre  = $tags->[$depth - 1][$hilighted][0];
#		my $post = $tags->[$depth - 1][$hilighted][1];

#		print STDERR "depth     : $depth\n";
		my $pre  = "&nbsp;&nbsp;&nbsp;" x ($depth - 1);
		my $post = ""; 		# "<BR>" x ($depth == 1);

		my $disabled = $$ref{"dis:$curfile"} || 0;
#		print STDERR "disabled  : $disabled\n";
		if ($$ref{'url'} && $disabled == 0) {
			$pre  = "$pre<A HREF=\"$$ref{'url'}\">";
			$post = "</A>$post"; 
		}

		my $ishead = $$ref{'menuhead'} || 0;
#		print STDERR "ishead    : $ishead\n";
		my $hilighted = $$ref{"hl:$curfile"} || 0;
#		print STDERR "hilighted : $hilighted\n";
		if ($depth == 1) {
			$pre  = $pre .
				"<FONT SIZE=\"+1\">" x $ishead .
				"<FONT COLOR=\"$SELCLR\">" x $hilighted .
				"<B>";
			$post = "</B>" .
				"</FONT>" x $hilighted .
				"</FONT>" x $ishead .
				$post;
		}
		elsif ($depth == 3) {
			$pre  = $pre .
				"<FONT COLOR=\"$SELCLR\">" x $hilighted .
				"<I>";
			$post = "</I>" .
				"</FONT>" x $hilighted .
				$post;
		}
		elsif ($hilighted) {
			$pre  = "$pre<FONT COLOR=\"$SELCLR\">";
			$post = "</FONT>$post";
		}

#		print STDERR "pre : $pre\n";
#		print STDERR "post: $post\n";
		print "$pre$$ref{'title'}$post<BR>\n";
	    }
	    else {
	    	# If no title, just a node, print an empty line
	    	print "<BR>\n";
	    }
	}

	# Print all childs in array referenced to by data of 'subref' key
	if ($$ref{'subref'}) {
		
#		if ($$ref{'title'}) {
#			# At depth 1, make a table row for all childs below
#			print "<TR BGCOLOR=\"#ffffff\"><TD ALIGN=LEFT><BR>\n"
#				if $depth == 1;
#		}

		foreach my $child (@{$$ref{'subref'}}) {
			recoutput($child, $curfile, $depth + 1);
		}

#		if ($$ref{'title'}) {
#			# At depth 1, close table row for all childs below
#			print "<BR></TD></TR>\n" 
#				if $depth == 1;
#
#		}
#
#		if ($$ref{'title'}) {
#
#			# When back at depth 2, skip an extra line
#			print "<BR>\n" 
#				if $depth == 2;
#		}
	}

	if ($$ref{'title'}) {

		# When back at depth 1, skip an extra line
		print "<BR>\n" 
			if $depth == 1;
	}
}


##
#
# OUTPUT - Wrapper around recoutput

sub output {
	print "<TABLE BORDER=$BORDER CELLPADDING=1 CELLSPACING=0><TR><TD ALIGN=LEFT>\n";
	recoutput(@_);
	print "</TD></TR></TABLE>";
}

