#!/usr/local/bin/perl
##############################################################################
# makedicthtml.pl - Perl script to generate HTML version of CIF dictionary   #
##############################################################################
# Usage: makedicthtml.pl -[version] [dictfile]                               #
#          where -[version] is DDL version (-1 or -2, -1 is default and can  #
#          be omitted), and [dictfile] is a CIF dictionary file              #
# The results are written to a directory html/[version]/[xxx_yyy.dic]/       #
# where [xxx_yyy.dic] is the name of the dictionary file advertised within   #
# the file as _dictionary_name (DDL1) or _dict.title (DDL2). Running titles  #
# for well known public dictionaries are stored in the %dictionaries hash,   #
# which may be augmented as desired.                                         #
##############################################################################
# Author: Brian McMahon bm@iucr.org                                          #
# copyright (c) International Union of Crystallography 2005                  #
##############################################################################

use STAR::Parser;
use STAR::DataBlock;
use STAR::Dictionary;
use Getopt::Std;
use lib qw(/pkg/wdc/perl /pkg/sgml/utilities/perl/lib/site_perl/5.005);

use strict;

use vars qw($opt_d $opt_D $opt_l $opt_s $opt_1 $opt_2 $opt_f);
use vars qw($version $options $dict $dictionary $dblock $verbose $ddl);
use vars qw($dictname $dictversion $dicthistory);
use vars qw($ROOTDIR $workdir );
use vars qw($symctr); # counts no. elements per line (_diffrn_source.target)
use vars qw(%dictionaries $dicttitle); # names of dictionaries

%dictionaries = (
		 "cif_core.dic"=>"Core dictionary (coreCIF)",
		 "cif_ms.dic"=>"Modulated and composite structures dictionary (msCIF)",
		 "cif_pd.dic"=>"Powder dictionary (pdCIF)",
		 "cif_rho.dic"=>"Electron density dictionary (rhoCIF)",
		 "cif_mm.dic"=>"Macromolecular dictionary (mmCIF)",
		 "mmcif_std.dic"=>"Macromolecular dictionary (mmCIF)",
		 "mmcif_pdbx.dic"=>"Protein Data Bank exchange dictionary (pdbx)",
		 "cif_img.dic"=>"Image dictionary (imgCIF)",
		 "cif_sym.dic"=>"Symmetry dictionary (symCIF)",
		 "ddl_core.dic"=>"DDL dictionary",
		 "ddl_core"=>"DDL dictionary",
		 "mif_core.dic"=>"Molecular Information File dictionary (MIF)",
		);

$version = "version 0.01";
$ROOTDIR = "./cifdic_html"; # Parent directory for html files
$dictname = '';
$symctr = 0;

# Version of DDL (can be 1 or 2)
$ddl = 1;

$verbose = 1;

getopt('');
$options .= 'd' if ( $opt_d );   # debug
$options .= 'l' if ( $opt_l );   # logfile
$ddl = 1 if ( $opt_1 );   # DDL version
$ddl = 2 if ( $opt_2 );   # DDL version
# $dict = 1 if ( $opt_D );         #Dictionary
$dict = 1; # by default (this is a dictionary-based application)

my $file = $ARGV[0];

# DDL1 and DDL2 dictionaries are structured differently. For DDL1, every
# definition (whether category or individual item) has its own datablock;
# for DDL2 a single datablock includes all the definitions as a series of
# saveblocks. The next call partitions the input file into data blocks (for
# caution we assume there might be multiple datablocks corresponding to
# different dictionaries in a DDL2 file). We name the resulting arrays
# differently to keep this distinction in mind

my @dblocks ; # DDL1
my @dicts ;   # DDL2
if ($ddl == 1 ) { # DDL1
  @dblocks = STAR::Parser->parse(-file=>$file,
				    -dict=>$dict,
				    -options=>$options);
  exit unless ( $#dblocks >= 0 ); # cut and run if nothing there
} else {
  @dicts = STAR::Parser->parse(-file=>$file,
				  -dict=>$dict,
				  -options=>$options);
  exit unless ( $#dicts >= 0 ); # cut and run if nothing there
}

if ($ddl == 1 ) { # DDL1

  # In DDL1 categories aren't explicit; instead, certain pseudo-definitions
  # imply a category description. We begin by working through the file
  # (collecting the dictionary name as we go) and storing all the item
  # names that by convention define a category
  my @categories = ();

  print STDERR "Handling DDL1 dictionary!\n";

  my @categorynames = ();
  my @extracategorynames = ();
  my @blocknames = ();
  foreach $dblock (@dblocks) { # For each data block (usually many)

    # Get the dictionary name and version
    $dictname = ($dblock->get_item_data(-item=>"_dictionary_name"))[0]
      || $dictname ;
    $dictversion = ($dblock->get_item_data(-item=>"_dictionary_version"))[0]
      || $dictversion ;
    $dicthistory = ($dblock->get_item_data(-item=>"_dictionary_history"))[0]
      || $dicthistory ;
    $dicttitle = $dictionaries{$dictname} || 'Generic CIF Dictionary';

    # Collect the data blocks that describe categories
    my $name = ($dblock->get_item_data(-item=>"_name"))[0];
    my $assignedcategory = ($dblock->get_item_data(-item=>"_category"))[0];
    $assignedcategory = "_" . $assignedcategory; # compare with categorynames
    if ($verbose) { print STDERR $name . "\n"; } # DEBUG

    # First we store categories defined in data blocks like data_atom_site_[]
    if ( $name =~ /\[[a-zA-Z0-9]*\]$/ ) {
      push (@categories,  $dblock );
      push (@categorynames, $name);
    } else {
      # but we also need to collect categories that are referenced but not
      # defined in this dictionary: we'll hoover them up from the _category
      # attributes, but need to make sure that we don't override any of the
      # definitions we've already collected (since we'll later need to
      # identify those for printing out)
      push (@extracategorynames, $assignedcategory)
	unless
	  ( grep /^$assignedcategory$/, @extracategorynames # already seen
	    or grep /^$assignedcategory$/, @categorynames # already seen
	    or grep /^${assignedcategory}_\[.*\]$/, @categorynames # seen
	    or $assignedcategory =~ /^category_overview$/ # category overview
	    or $assignedcategory =~ /^_ *$/ ); # null (leading _ was added)
    }
    push (@blocknames, $name);

    $workdir = $ROOTDIR . "/" . $ddl . "/" . $dictname ;

    unless (-d $workdir) {
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
    }

  } # end of foreach loop per included data block

  my $indexfile = $workdir . "/index.html";
  open INDEX, ">$indexfile";

  # Create the index file
  # print header HTML for file
  print INDEX '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print INDEX "(IUCr) ${dicttitle} definitions</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";
  print INDEX standard_header();

  if ($dictversion) {
    print INDEX "
<h1>$dicttitle version ${dictversion} definitions</h1>
";
  } else {
  print INDEX "
<h1>$dicttitle definitions</h1>
";
  }
  print INDEX "
Definitions are arranged alphabetically by category and within category.

<ul><ul>\n" ;

  # Output revision history if it exists
  if ($dicthistory) {
    my $file = "Dhistory.html";
    print INDEX "
</ul><li><a class=\"itemlink\" href=\"$file\">Revision history</a><br /></li><ul>";
    print_revision_history();
  }

  if ($dictname =~ /^cif_pd\.dic$/      # Powder dictionary is ordered oddly
      || $dictname =~ /ddl_core.dic/    # DDL1 dict has no category descriptions
      || $dictname =~ /mif_core.dic/) { # MIF dict has no category descriptions
    foreach my $blockname (sort @blocknames) {
      foreach $dblock (sort @dblocks) {
	my $name = ($dblock->get_item_data(-item=>"_name"))[0];
	if ($name eq $blockname) {
	  if ($name =~ /_\[pd\]$/) {

	    my $file = $name ;
	    $file =~ s/^_+//;
	    $file =~ s/_\[[A-Za-z0-9]*\]$//;
	    $file = "C" . $file . ".html";
	    $file =~ s/\//=over=/g;

	    print INDEX "
</ul><li><a class=\"itemlink\" href=\"$file\">$name</a></li><ul>";
	    if ($verbose) { print STDERR "Category $name\n"; }
	    # Collect categories for use by other utilities
	    my $categoryfile = "/agatehome/bm/cifdic_perl/categories";
	    open (FILE, ">> " . $categoryfile);
	    print FILE "\"" . $name . "\"=>\"" . $dictname . "\",\n";
	    close FILE;

	    format_category1($dblock);
	  } else {
	    if ($verbose) { print STDERR "    item $name\n"; }
	    if ($name =~ /[a-zA-Z]/) {

	      # my $title;
	      my $title = $dblock->title;
	      # ($title = $name) =~ s/^_//;
	      my $file = "I" . $title . ".html";
	      $file =~ s/\//=over=/g;
	      my @names = $dblock->get_item_data(-item=>"_name");
	      foreach my $name (@names) {
		print INDEX "
<li><a class=\"itemlink\" href=\"$file\">$name</a></li>";
	      }
	      format_item1($dblock);
	    }
	  }
	}
      }
    }
  } else {
    # For other dictionaries the ordering is more orderly by category
    #
    push @categorynames, @extracategorynames;
    my @seen = ();
    foreach my $categoryname (sort { lc($a) cmp lc ($b) } @categorynames) {
      my $categoryid;
      ($categoryid =  $categoryname) =~ s/^_*//;
      $categoryid =~ s/_\[[A-Za-z0-9]*\]$//;
      next if (grep /^$categoryid$/, @seen);
      push @seen, $categoryid;

      # The outer "blockname" loop is to ensure alphabetisation
#     foreach my $blockname (sort { lc($a) cmp lc ($b) } @blocknames) {
	foreach $dblock (@dblocks) {
	  my $title = $dblock->title;
	  my $name = ($dblock->get_item_data(-item=>"_name"))[0];
#  next unless ($name eq $blockname);
	  my $categoryassignment =
	    ($dblock->get_item_data(-item=>"_category"))[0];

	  # if the current datablock is the category definition, print it
	  if ($name eq $categoryname ) {
	    if ($verbose) { print STDERR "Category $categoryname\n"; }
	    # Collect categories for use by other utilities
	    my $categoryfile = "/agatehome/bm/cifdic_perl/categories";
	    open (FILE, ">> " . $categoryfile);
	    print FILE "\"" . $name . "\"=>\"" . $dictname . "\",\n";
	    close FILE;

	    my $file = $categoryname ;
	    $file =~ s/^_+//;
	    $file =~ s/_\[[A-Za-z0-9]*\]$//;
	    $file = "C" . $file . ".html";
	    $file =~ s/\//=over=/g;

	    print INDEX "
</ul><li><a class=\"itemlink\" href=\"$file\">$categoryname</a></li><ul>";

	    format_category1($dblock);
	  } elsif ($categoryassignment eq $categoryid) {
	    if ($verbose) { print STDERR "    item $name\n"; }

	    my $file = "I" . $title . ".html";
	    $file =~ s/\//=over=/g;
	    my @names = $dblock->get_item_data(-item=>"_name");
	    foreach my $name (@names) {
	    print INDEX "
<li><a class=\"itemlink\" href=\"$file\">$name</a></li>";
	  }
	    format_item1($title);
	  }
	}
#     } # end of foreach loop per blockname
    } # end of foreach loop per category
  }

    print INDEX "\n</ul></ul>\n";();
    print INDEX standard_footer();
    close INDEX;

} # end of handler for DDL1 dictionaries

elsif ($ddl == 2 ) { # DDL2

  foreach $dictionary (@dicts) { # For each dictionary (usually one)

    # Get the dictionary name and version
    $dictname = ($dictionary->get_item_data(-item=>"_dictionary.title"))[0];
    $dictversion = ($dictionary->get_item_data(-item=>"_dictionary.version"))[0];
    $dicttitle = $dictionaries{$dictname} || 'Generic CIF Dictionary';


    $workdir = $ROOTDIR . "/" . $ddl . "/" . $dictname ;

    unless (-d $workdir) {
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
    }

  my $indexfile = $workdir . "/index.html";
  open INDEX, ">$indexfile";

  # Create the index file
  # print header HTML for file
  print INDEX '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print INDEX "(IUCr) ${dicttitle} definitions</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";
  print INDEX standard_header();

  if ($dictversion) {
    print INDEX "
<h1>$dicttitle version ${dictversion} definitions</h1>
";
  } else {
  print INDEX "
<h1>$dicttitle definitions</h1>
";
  }
  print INDEX "
Definitions are arranged alphabetically by category and within category.

<ul><ul>\n" ;

#-----------------------------------------------------------------------
  # Output revision history if it exists
  my @dicthistv =
    $dictionary->get_item_data(-item=>"_dictionary_history.version");
  my @dicthistu =
    $dictionary->get_item_data(-item=>"_dictionary_history.update");
  my @dicthistr =
    $dictionary->get_item_data(-item=>"_dictionary_history.revision");
  if (@dicthistv) {
    $dicthistory = "";
    if (@dicthistv) { # only do the next bit if there are any!
      for (my $index = 0; $index <= $#dicthistv ; $index++) {
	$dicthistory .=
	  "========================================================================\n"
	  . "Version " . $dicthistv[$index] . " (" . $dicthistu[$index] . ")\n" .
	  "------------------------------------------------------------------------\n"
	  . $dicthistr[$index] . "\n";
      }
      $dicthistory .=
	"========================================================================\n" ;
  }

    my $file = "Dhistory.html";
    print INDEX "
</ul><li><a class=\"itemlink\" href=\"$file\">Revision history</a><br /></li><ul>";
    print_revision_history();
  }

#-----------------------------------------------------------------------
    # Get the list of category groups

    my @catgroups =
      $dictionary->get_item_data(-item=>"_category_group_list.id");
    my @catdescs =
      $dictionary->get_item_data(-item=>"_category_group_list.description");

    if (@catgroups) {
      die "Category groups and accompanying descriptions do not match!"
	unless ( $#catdescs == $#catgroups);
    }

    if (@catdescs) { # only do the next bit if there are any!
      my $cgfile = $workdir . "/Dcategorygroups.html";
      open (CATGROUPS, "> $cgfile");
      print INDEX "
</ul><li><a class=\"itemlink\" href=\"Dcategorygroups.html\">Category groups</a><br /></li><ul>";

      # print header HTML for file
      print CATGROUPS '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print CATGROUPS "(IUCr) CIF dictionary $dictname category groups</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

      print CATGROUPS standard_header();
      print CATGROUPS "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>";
      print CATGROUPS "<h1>Category groups</h1>";
      print CATGROUPS "<p>The following category groups are defined in this dictionary:</p>\n";

      print CATGROUPS "<table class=\"cifcatexample\" width=\"100%\"><tr><td>";
      print CATGROUPS "<dl>";
      for (my $index = 0; $index <= $#catdescs ; $index++) {
	# if ($catgroups[$index] && $catgroups[$index] !~ /^inclusive_group$/ ) {
	if ($catgroups[$index]) {
	  print format_attribute($catgroups[$index],
				     "_category_group_list.id");
	  print CATGROUPS "<dt>" . $catgroups[$index] . "</dt>\n";
	  print format_attribute($catdescs[$index],
				     "_category_group_list.description");
	  print CATGROUPS "<dd>" . $catdescs[$index] . "</dd>\n";
	}
      }
      print CATGROUPS "</dl>";
      print CATGROUPS "</td></tr></table>";


      # print footer HTML for file
      print CATGROUPS standard_footer();

      close (CATGROUPS);
    }
#-----------------------------------------------------------------------
    # Get the list of extended data types

    my @typecodes =
      $dictionary->get_item_data(-item=>"_item_type_list.code");
    my @typepcodes =
      $dictionary->get_item_data(-item=>"_item_type_list.primitive_code");
    my @typeconstructs =
      $dictionary->get_item_data(-item=>"_item_type_list.construct");
    my @typedetails =
      $dictionary->get_item_data(-item=>"_item_type_list.detail");

    if (@typecodes) {
      die "Category groups and accompanying descriptions do not match!"
	unless ( $#typecodes == $#typedetails);
    }

    if (@typecodes) { # only do the next bit if there are any!
      my $tfile = $workdir . "/Dtypecodes.html";
      open (TYPECODES, "> $tfile");
      print INDEX "
</ul><li><a class=\"itemlink\" href=\"Dtypecodes.html\">Extended data types</a><br /></li><ul>";

      # print header HTML for file
      print TYPECODES '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print TYPECODES "(IUCr) CIF dictionary $dictname Extended data types</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

      print TYPECODES standard_header();
      print TYPECODES "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>";
      print TYPECODES "<h1>Extended data types</h1>";
      print TYPECODES "<p>The following extended data types are defined in this dictionary:</p>\n";

      print TYPECODES "<table class=\"cifcatexample\" width=\"100%\">";
      print TYPECODES "<tr><td valign=\"top\" width=\"10%\"><b>Code</b></td>
                           <td valign=\"top\"><b>Primitive data type</b></td>
                           <td valign=\"top\"><b>Regular expression construct</b></td>
                           <td valign=\"top\" width=\"30%\"><b>Description</b></td></tr>\n";
      print TYPECODES "<tr><td colspan=\"4\">&nbsp;</td></tr>\n";
      for (my $index = 0; $index <= $#typecodes ; $index++) {
	if ($typedetails[$index]) {
	  print TYPECODES "<tr><td valign=\"top\">" . $typecodes[$index] . "</td>\n";
	  print TYPECODES "    <td valign=\"top\">" . $typepcodes[$index] . "</td>\n";
	  print TYPECODES "    <td valign=\"top\"><tt>" . htmlsafe($typeconstructs[$index]) . "</tt></td>\n";
	  print TYPECODES "    <td valign=\"top\">" . $typedetails[$index] . "</td></tr>\n";
	}
      }
      print TYPECODES "</table>";


      # print footer HTML for file
      print TYPECODES standard_footer();

      close (TYPECODES);
    }

#-----------------------------------------------------------------------
    # Get the list of units codes

    my @unitcodes =
      $dictionary->get_item_data(-item=>"_item_units_list.code");
    my @unitdetails =
      $dictionary->get_item_data(-item=>"_item_units_list.detail");

    if (@unitcodes) {
      die "Category groups and accompanying descriptions do not match!"
	unless ( $#unitcodes == $#unitdetails);
    }

    if (@unitcodes) { # only do the next bit if there are any!
      my $ufile = $workdir . "/Dunitcodes.html";
      open (UNITCODES, "> $ufile");
      print INDEX "
</ul><li><a class=\"itemlink\" href=\"Dunitcodes.html\">Unit codes</a><br /></li><ul>";

      # print header HTML for file
      print UNITCODES '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print UNITCODES "(IUCr) CIF dictionary $dictname Unit codes</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

      print UNITCODES standard_header();
      print UNITCODES "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>";
      print UNITCODES "<h1>Unit codes</h1>";
      print UNITCODES "<p>The following unit codes are defined in this dictionary:</p>\n";

      print UNITCODES "<table class=\"cifcatexample\" width=\"100%\">";
      for (my $index = 0; $index <= $#unitcodes ; $index++) {
	if ($unitdetails[$index]) {
	  print UNITCODES "<tr><td>" . $unitcodes[$index] . "</td>\n";
	  print UNITCODES "<td>" . $unitdetails[$index] . "</td></tr>\n";
	}
      }
      print UNITCODES "</table>";


      # print footer HTML for file
      print UNITCODES standard_footer();

      close (UNITCODES);
    }

#-----------------------------------------------------------------------

    # Get the list of save frame names, sorted alphabetically
    my @saveblocks = sort $dictionary->get_save_blocks;

    foreach my $saveblock (@saveblocks) { # For each save frame
      # if ($verbose) { print STDERR $saveblock . "\n"; }

      my @categories = $dictionary->get_item_data(-save=>$saveblock,
						  -item=>"_category.id");
      if ( $#categories == 0) { # This save frame describes a category
	my $category = $categories[0] ;

	    my $file = $category ;
	    $file = "C" . $file . ".html";

	    print INDEX "
</ul><li><a class=\"itemlink\" href=\"$file\">$category</a></li><ul>";
	    if ($verbose) { print STDERR "Category $category\n"; }
	    # Collect categories for use by other utilities
	    my $categoryfile = "/agatehome/bm/cifdic_perl/categories";
	    open (FILE, ">> " . $categoryfile);
	    print FILE "\"" . $category . "\"=>\"" . $dictname . "\",\n";
	    close FILE;
	format_category2($saveblock); # Format the category description
	foreach my $block (@saveblocks) { # Re-traverse the list of blocks

	    my $file;
	    ($file = $block) =~ s/^_// ;
	    $file = "I" . $file . ".html";
	    $file =~ s/\//=over=/g;

	  my @itemcategoryids
	    = $dictionary->get_item_data(-save=>$block,
					 -item=>"_item.category_id");
	  my @itemname = $dictionary->get_item_data(-save=>$block,
						    -item=>"_item.name");
	  if ( $#itemcategoryids == 0) { # There's a category pointer
	    if ($itemcategoryids[0] =~ /^$category$/i) {
	      format_item2($block) if $itemcategoryids[0] =~ /^$category$/i;
	      print INDEX "
<li><a class=\"itemlink\" href=\"$file\">$block</a></li>";
	      if ($verbose) { print STDERR "    Item $block\n"; }
	  # Collect categories for use by other utilities
	  my $categorylistfile = "/agatehome/bm/cifdic_perl/categorylist";
	  open (FILE, ">> " . $categorylistfile);
	  print FILE "\"" . $block . "\"=>\"" . $category . "\",\n";
	  close FILE;
	    }
	  } elsif ( $#itemname == 0 && $itemname[0] =~ /^_$category\./i ) {
	    # print "[DEBUG]: entry for " . $block . " located implicitly\n";
	    print INDEX "
<li><a class=\"itemlink\" href=\"$file\">$block</a></li>";
	    if ($verbose) { print STDERR "    Item $block\n"; }
	    format_item2($block);
	  # Collect categories for use by other utilities
	  my $categorylistfile = "/agatehome/bm/cifdic_perl/categorylist";
	  open (FILE, ">> " . $categorylistfile);
	  print FILE "\"" . $block . "\"=>\"" . $category . "\",\n";
	  close FILE;
	  } elsif ( $itemname[0] =~ /^_$category\./i ) {
	    # print "[DEBUG]: looks like multiple ids defined in $block \n";
	    print INDEX "
<li><a class=\"itemlink\" href=\"$file\">$block</a></li>";
	    if ($verbose) { print STDERR "    Item $block\n"; }
	    format_item2($block, "multiple");
	  # Collect categories for use by other utilities
	  my $categorylistfile = "/agatehome/bm/cifdic_perl/categorylist";
	  open (FILE, ">> " . $categorylistfile);
	  print FILE "\"" . $block . "\"=>\"" . $category . "\",\n";
	  close FILE;
	  }

	} # end of foreach loop to find items matching current category
	
      }

    } # end of foreach loop for each save frame in the dictionary

    print INDEX "\n</ul></ul>\n";();
    print INDEX standard_footer();
    close INDEX;

  } # end of foreach loop per included dictionary

} # end of handler for DDL2 dictionaries

else {
  die "Do not know what version of DDL is in use!";
}

exit;

########################################################################
# Output HTML formatting for a category block
########################################################################

######################## DDL1 ##########################################
sub format_category1 {
  my $categoryname = ($dblock->get_item_data(-item=>"_name"))[0];
  my $label = $categoryname;
  $categoryname =~ s/^_+//;
  $categoryname =~ s/_\[[A-Za-z0-9]*\]$//;

  my $file = $workdir . "/" . "C" . $categoryname . ".html";

  open (ENTRY, "> " . $file);

  # print header HTML for file
  print ENTRY '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print ENTRY "(IUCr) CIF Definition data${label}</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

print ENTRY standard_header();

  if ($dictversion) {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle version ${dictversion}</h5>
";
  } else {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>
";
  }
  print ENTRY "<h1>Category " . uc($categoryname) . "</h1>";


  # Name
  foreach my $item ( # specifies ordering of output items
		    "_name",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      if ( $#attributes == 0) {
	print ENTRY "\n<b>Name:</b><br />\n";
      } else {
	print ENTRY "\n<b>Names:</b><br />\n";
      }
      foreach my $attribute (@attributes) {

	print ENTRY format_attribute($attribute, $item);

      }
    }
  }

  # Definition
  foreach my $item ( # specifies ordering of output items
		    "_definition",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
#========================================================================
	my $blockname = $dblock->title;
	$blockname =~ s/\//=over=/g;
	my @definitions =  $dblock->get_item_data(-item=>$item);

	if ( $#definitions > 0) {
	  print "WARNING! Multiple definitions returned for $blockname\n";
	  print "  - ignoring all but first\n";
	}

	my $definition = $definitions[0];

	print ENTRY format_attribute($definition, $item);
#========================================================================
      }
    }
  }

  # Examples
  my @details = $dblock->get_item_data(-item=>"_example_detail");
  my @example = $dblock->get_item_data(-item=>"_example");
  if (@details) {
    die "Examples and accompanying details do not match!"
      unless ( $#example == $#details);
  }

  if (@example) { # only do the next bit if there are any!
    if ( $#example == 0) {
	print ENTRY "\n<b>Example:</b><br /><br />\n";
      } else {
	print ENTRY "\n<b>Examples:</b><br /><br />\n";
      }
    for (my $index = 0; $index <= $#example ; $index++) {
      print ENTRY "<table class=\"cifcatexample\" width=\"100%\"><tr><td>";
      if ($details[$index]) {
	print ENTRY format_attribute($details[$index],"_category_examples.detail") ;
      }
      print ENTRY format_attribute($example[$index], "_category_examples.case") ;
      print ENTRY "</td></tr></table><br />\n";
    }
  }


  # Type and category
  foreach my $item ( # specifies ordering of output items
		    "_type",
		    "_category",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
#========================================================================
	my $blockname = $dblock->title;
	$blockname =~ s/\//=over=/g;
	my @definitions =  $dblock->get_item_data(-item=>$item);

	if ( $#definitions > 0) {
	  print "WARNING! Multiple definitions returned for $blockname\n";
	  print "  - ignoring all but first\n";
	}

	my $definition = $definitions[0];

	print ENTRY format_attribute($definition, $item);
#========================================================================
      }
    }
  }

  # print footer HTML for file
  print ENTRY standard_footer();

  close (ENTRY);

  return;
}

sub format_item1 {
  # my $saveblock = shift;
  my $multiple = shift || '';
  my $itemname = ($dblock->get_item_data(-item=>"_name"))[0];
  my $catname = ($dblock->get_item_data(-item=>"_category"))[0];
  my $blockname = $dblock->title;
  my $label = $blockname;
  $label =~ s/^([^_])/_$1/; # prepend an underscore if missing
  $blockname =~ s/\//=over=/g;

  my $file = $workdir . "/" . "I" . $blockname . ".html";

  open (ENTRY, "> " . $file);

  # print header HTML for file
  print ENTRY '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print ENTRY "(IUCr) CIF Definition data_${label}</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

print ENTRY standard_header();

  if ($dictversion) {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle version ${dictversion}</h5>
";
  } else {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>
";
  }
  print ENTRY "<h1>" . $label . "</h1>";


  # Name
  foreach my $item ( # specifies ordering of output items
		    "_name",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      if ( $#attributes == 0) {
	print ENTRY "\n<b>Name:</b><br />\n";
      } else {
	print ENTRY "\n<b>Names:</b><br />\n";
      }
      foreach my $attribute (@attributes) {

	print ENTRY format_attribute($attribute, $item);

	# Collect categories for use by other utilities
	  my $categorylistfile = "/agatehome/bm/cifdic_perl/categorylist";
	  open (FILE, ">> " . $categorylistfile);
	  print FILE "\"" . $attribute . "\"=>\"" . $catname . "\",\n";
	  close FILE;
      }
    }
  }

  #-----------------------------------------------------------------------
  # We search for related items twice, here and after the definition. At this
  # point we are interested only in '_related_function  replace' so that we
  # can significantly change the presentation of the subsequent definition
  # Related items
  my @relname = $dblock->get_item_data(-item=>"_related_item");
  my @relcode = $dblock->get_item_data(-item=>"_related_function");
  die "Related names and codes do not match!"
    unless ( $#relname == $#relcode);

  if (@relname && (grep /^replace$/, @relcode) ) { # only if there are any!
    print ENTRY deprecated();
    for (my $index = 0; $index <= $#relname; $index++) {
      next unless ($relcode[$index] =~ /^replace$/);
      print ENTRY format_attribute($relname[$index], "deprecated") ;
    }
    print ENTRY "</p>\n";
  }

  # Definition
  foreach my $item ( # specifies ordering of output items
		    "_definition",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
#========================================================================
	my $blockname = $dblock->title;
	$blockname =~ s/\//=over=/g;
	my @definitions =  $dblock->get_item_data(-item=>$item);

	if ( $#definitions > 0) {
	  print "WARNING! Multiple definitions returned for $blockname\n";
	  print "  - ignoring all but first\n";
	}

	my $definition = $definitions[0];

	print ENTRY format_attribute($definition, $item);
#========================================================================
      }
    }
  }

  # Examples
  my @details = $dblock->get_item_data(-item=>"_example_detail");
  my @example = $dblock->get_item_data(-item=>"_example");
  if (@details) {
    die "Examples and accompanying details do not match!"
      unless ( $#example == $#details);
  }

  if (@example) { # only do the next bit if there are any!
    if ( $#example == 0) {
	print ENTRY "\n<b>Example:</b><br /><br />\n";
      } else {
	print ENTRY "\n<b>Examples:</b><br /><br />\n";
      }
    for (my $index = 0; $index <= $#example ; $index++) {
      print ENTRY "<table width=\"100%\"><tr class=\"cifexample\"><td width=\"50%\">";
      print ENTRY format_attribute($example[$index], "_example") ;
      #if ($details[$index]) {
	print ENTRY "</td><td width=\"50%\">";
	print ENTRY format_attribute($details[$index],"_example_detail") ;
      #}
      print ENTRY "</td></tr></table>\n";
    }
  }

  #-----------------------------------------------------------------------
  # List membership, reference etc
  foreach my $item ( # specifies ordering of output items
		    "_list",
		    "_list_mandatory",
		    "_list_level",
		    "_list_reference",
		    "_list_link_parent",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      my $counter = 0; # special wording for multiple cases
      foreach my $attribute (@attributes) {
	if ($item =~ /^_list_reference$/) {
	  if ($counter > 0) {
	    print ENTRY " &nbsp; ";
	  } else {
	    print ENTRY " containing ";
	  }
	  $counter++;
	}
	print ENTRY format_attribute($attribute, $item);
      }
    }
}
  #-----------------------------------------------------------------------
  # List link children
  my @children =  $dblock->get_item_data(-item=>"_list_link_child");

  if (@children) { # only do the next bit if there are any!
    print ENTRY "\n<br />May match subsidiary data name(s) <br />\n" ;

    for (my $index = 0; $index <= $#children; $index++) {
      my $child = $children[$index];

      print ENTRY format_attribute($child, "_list_link_child");
    }
  }

  #-----------------------------------------------------------------------
  # Numerical ranges
  my @ranges =  $dblock->get_item_data(-item=>"_enumeration_range");

  if (@ranges) { # only do the next bit if there is a range!

    for (my $index = 0; $index <= $#ranges; $index++) {
      my $range = $ranges[$index];

      $range =~ s/^:$/-infinity -&gt; infinity/g;
      $range =~ s/:$/ -&gt; infinity/g;
      $range =~ s/^:/-infinity -&gt; /g;
      $range =~ s/:/ -&gt; /g;

      print ENTRY "\n<br />The permitted range is " . $range ;

      if ($index > 2) { print "% WARNING! More than three range values\n"; }
    }

  }

  #-----------------------------------------------------------------------
  # Second pass at related items - we keep those that aren't "replace"
  # Related items
  my @relname = $dblock->get_item_data(-item=>"_related_item");
  my @relcode = $dblock->get_item_data(-item=>"_related_function");
  die "Related names and codes do not match!"
    unless ( $#relname == $#relcode);

  if (@relname) { # only do the next bit if there are any!
    if ($#relname == 0 ) {
      print ENTRY "\n<p class=\"pack\"><b>Related item: </b>";
    } else {
      print ENTRY "\n<p class=\"pack\"><b>Related items : </b>";
    }
    for (my $index = 0; $index <= $#relname; $index++) {
      next if ($relcode[$index] =~ /^replace$/);
      print ENTRY format_attribute($relname[$index], "_related_item") ;
      print ENTRY format_attribute($relcode[$index],"_related_function") ;
    }
  }

  #-----------------------------------------------------------------------
  # Enumeration
  my @enumval = $dblock->get_item_data(-item=>"_enumeration");
  my @enumdtl = $dblock->get_item_data(-item=>"_enumeration_detail");
  if (@enumdtl) {
    die "Examples and accompanying details do not match!"
      unless ( $#enumval == $#enumdtl);
  }

  if (@enumval) { # only do the next bit if there are any!
    print ENTRY
    "\n<br /><b>The data value must be one of the following:</b><br /><br />\n";
    if ($blockname =~ /^(diffrn_source_target|atom_local_axes_ax[12])$/ ) {
      print ENTRY "<table width=\"100%\"><tr class=\"cifexample\">";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' ' ; }
	print ENTRY "<td class=\"cifexample\">" . format_attribute($symbol, "_enumeration" ) . "</td>\n";
	$symctr++;
	if ($symctr > 14) {
	  print ENTRY "</tr><tr class=\"cifexample\">\n";
	  $symctr = 0;
	}
      }
      print ENTRY "</td></tr></table>\n";
    }  else {
      for (my $index = 0; $index <= $#enumval ; $index++) {
	print ENTRY "<table width=\"100%\"><tr class=\"cifexample\"><td width=\"50%\">";
	print ENTRY format_attribute($enumval[$index], "_enumeration" ) ;
	print ENTRY "</td><td width=\"50%\">";
	print ENTRY format_attribute($enumdtl[$index],"_enumeration_detail") ;
	print ENTRY "</td></tr></table>\n";
      }
    }
  }
  #-----------------------------------------------------------------------
  # Enumeration default
  foreach my $item (
		    "_enumeration_default",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	print ENTRY format_attribute($attribute, $item);
      }
    }
  }


  # Category
  foreach my $item ( # specifies ordering of output items
		    "_type",
		    "_category",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	print ENTRY format_attribute($attribute, $item);

      }
    }
  }

  # print footer HTML for file
  print ENTRY standard_footer();

  close (ENTRY);

  return;
}

########################################################################
# Rather basic subroutine just to handle the DDL1 revision history
########################################################################
sub print_revision_history {
  my $file = $workdir . "/" . "Dhistory.html";

  open (ENTRY, "> " . $file);

  # print header HTML for file
  print ENTRY '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print ENTRY "(IUCr) CIF dictionary $dictname revision history</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

print ENTRY standard_header();

  if ($dictversion) {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle version ${dictversion}</h5>
";
  } else {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>
";
  }
  my $label = "Revision history";
  print ENTRY "<h1>" . $label . "</h1>";

  print ENTRY "\n<pre>" . findlinks(htmlsafe($dicthistory)) . "</pre>\n";


  # print footer HTML for file
  print ENTRY standard_footer();

  close (ENTRY);

  return;
}

######################## DDL2 ##########################################
sub format_category2 {
  my $saveblock = shift;

  my $categoryname = ($dictionary->get_item_data(-save=>$saveblock,
					      -item=>"_category.id"))[0];

  my $file = $workdir . "/" . "C" . $categoryname . ".html";
  open (ENTRY, "> " . $file);

  # print header HTML for file
  print ENTRY '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';

print ENTRY "(IUCr) CIF Definition save_${categoryname}</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

print ENTRY standard_header();

  if ($dictversion) {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle version ${dictversion}</h5>
";
  } else {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>
";
  }
  print ENTRY "<h1>Category " . uc($categoryname) . "</h1>";
  print ENTRY "\n<b>Name:</b><br />\n";
  print ENTRY format_attribute($categoryname, "_category.id");

  foreach my $item ( # specifies ordering of output items
		    "_category.description",
		   ) {
    my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	print ENTRY format_attribute($attribute, $item);
      }
    }
  }
  #-----------------------------------------------------------------------
  # Examples
  my @details = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_category_examples.detail");
  my @example = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_category_examples.case");
  if (@details) {
    die "Examples and accompanying details do not match!"
      unless ( $#example == $#details);
  }

  if (@example) { # only do the next bit if there are any!
    if ( $#example == 0) {
      print ENTRY "\n<b>Example:</b><br /><br />\n";
    } else {
      print ENTRY "\n<b>Examples:</b><br /><br />\n";
    }
    for (my $index = 0; $index <= $#example ; $index++) {
      print ENTRY "<table class=\"cifcatexample\" width=\"100%\"><tr><td>";
      if ($details[$index]) {
	my $eg = format_attribute($details[$index],"_category_examples.detail");
	if ($saveblock =~ /^STRUCT_SHEET$/i) {
	  $eg =~ s/(Example [0-9] - [a-zA-Z -]*)/$1.<pre>/;
	  $eg =~ s/<\/i> *$/<\/pre><\/i>/;
	}
	print ENTRY $eg;
	# format_attribute($details[$index],"_category_examples.detail") ;
      }
      print ENTRY
	format_attribute($example[$index], "_category_examples.case") ;
      print ENTRY "</td></tr></table><br />\n";
    }
  }

  foreach my $item ( # specifies ordering of output items
		    "_category_group.id",
		    "_category_key.name",
		    "_category.mandatory_code",
		   ) {
    my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);
    if ( $#attributes >= 0) {
      if ( $#attributes == 0) {
	if ( $item =~ /^_category_group.id$/) {
	  print ENTRY "\n<b>Category group:</b><br />\n";
	}
	if ( $item =~ /^_category_key.name$/) {
	  print ENTRY "\n<b>Category key:</b><br />\n";
	}
      } else {
	if ( $item =~ /^_category_group.id$/) {
	  print ENTRY "\n<b>Category groups:</b><br />\n";
	}
	if ( $item =~ /^_category_key.name$/) {
	  print ENTRY "\n<b>Category keys:</b><br />\n";
	}
      }
      foreach my $attribute (@attributes) {
	print ENTRY format_attribute($attribute, $item);

      }
    }
  }

  # print footer HTML for file
  print ENTRY standard_footer();

  close (ENTRY);

  return;
}


sub format_item2 {
  my $saveblock = shift;

  my $multiple = shift || '';

  my $file ;
  ($file = $saveblock) =~ s/^_// ;
  $file =~ s/\//=over=/g;
  $file = $workdir . "/" . "I" . $file . ".html";

  open (ENTRY, "> " . $file);

  # print header HTML for file
  print ENTRY '<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">

<head>
<title>';
print ENTRY "(IUCr) CIF Definition save_${saveblock}</title>
  <link rel=\"stylesheet\" type=\"text/css\" href=\"../../itg.css\" />
</head>

<body>
";

print ENTRY standard_header();

  if ($dictversion) {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle version ${dictversion}</h5>
";
  } else {
  print ENTRY "
<p class=\"index\"><a class=\"index\" href=\"index.html\">Index</a></p>
<h5>$dicttitle</h5>
";
  }
  print ENTRY "<h1>" . $saveblock . "</h1>";

  my $itemname = ($dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item.name"))[0];

  # Text definition block
  #-----------------------------------------------------------------------
  if ($multiple) { # multiple item names occur in an id listing
    foreach my $item ( # specifies ordering of output items
		      "_item.name",
		      "_item_description.description",
		     ) {
      my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						  -item=>$item);
      if ( $#attributes >= 0) {
	print ENTRY format_attribute($attributes[0], $item);
      }
    }
  } else {
    foreach my $item ( # specifies ordering of output items
		      "_item.name",
		      "_item_description.description",
		     ) {
      my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						  -item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  print ENTRY format_attribute($attribute, $item);
	}
      }
    }
  }

  #-----------------------------------------------------------------------
  # Examples
  my @example = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_examples.case");
  my @details = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_examples.detail");
  if (@details) {
    die "Examples and accompanying details do not match!"
      unless ( $#example == $#details);
  }

  if (@example) { # only do the next bit if there are any!
    if ($#example == 0 ) {
	print ENTRY "\n<b>Example:</b><br /><br />\n";
      } else {
	print ENTRY "\n<b>Examples:</b><br /><br />\n";
      }
    for (my $index = 0; $index <= $#example ; $index++) {
      print ENTRY "<table width=\"100%\"><tr class=\"cifexample\"><td width=\"50%\">";
      print ENTRY
	# format_attribute(cifquote($example[$index]), "_item_examples.case" ) ;
	format_attribute($example[$index], "_example" ) ;
      ## if ($details[$index]) {
	print ENTRY "</td><td width=\"50%\">";
	# print ENTRY format_attribute($details[$index],"_item_examples.detail") ;
	print ENTRY format_attribute($details[$index],"_example_detail") ;
      ## }
      print ENTRY "</td></tr></table>\n";
    }
  }

  #-----------------------------------------------------------------------
  # Further item attributes
  if ($multiple) { # multiple item names occur in an id listing
    foreach my $item ( # specifies ordering of output items
		      "_item_type.code",
		      "_item_type_conditions.code",
		      "_item.mandatory_code",
		     ) {
      my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						  -item=>$item);
      if ( $#attributes >= 0) {
	print ENTRY format_attribute($attributes[0], $item);
      }
    }
  } else {
    foreach my $item ( # specifies ordering of output items
		      "_item_type.code",
		      "_item_type_conditions.code",
		      "_item.mandatory_code",
		     ) {
      my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						  -item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  print ENTRY format_attribute($attribute, $item);
	}
      }
    }
  }

  #-----------------------------------------------------------------------
  # Aliases
  my @aliasname = $dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item_aliases.alias_name");
  my @aliasdict = $dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item_aliases.dictionary");
  my @aliasvers = $dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item_aliases.version");
  warn "Alias names and dictionary references do not match ($saveblock)!"
    unless ( $#aliasname == $#aliasdict);

  if (@aliasname) { # only do the next bit if there are any!
    if ( $#aliasname == 0) {
      print ENTRY "\n<b>Alias:</b>\n";
    } else {
      print ENTRY "\n<b>Aliases:</b><br /><br />\n";
    }
    for (my $index = 0; $index <= $#aliasname; $index++) {
      # print ENTRY
	# format_attribute($aliasname[$index], "_item_aliases.alias_name") ;
      if ($aliasdict[$index]) {
	print ENTRY "\n<br /><tt> " . linkalias(htmlsafe($aliasname[$index]), $aliasdict[$index]) . "</tt>\n";

	print ENTRY "(" . format_attribute($aliasdict[$index],"_item_aliases.dictionary") ;
	if ($aliasvers[$index]) {
	  print ENTRY format_attribute($aliasvers[$index],"_item_aliases.version") ;
	}
	print ENTRY ")\n";
      } else {
      print ENTRY
	format_attribute($aliasname[$index], "_item_aliases.alias_name") ;
    }
    }
  }

  #-----------------------------------------------------------------------
  # Parent/child links
  if ($multiple ) { # skip otherwise
    my @childname = $dictionary->get_item_data(-save=>$saveblock,
					       -item=>"_item_linked.child_name");

    if (@childname) { # only do the next bit if there are any!
      for (my $index = 0; $index <= $#childname; $index++) {
	print ENTRY
	  format_attribute($childname[$index], "_item_linked.child_name") ;
      }
    }
  }

  #-----------------------------------------------------------------------
  # Numerical ranges
  my @rangemin =  $dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item_range.minimum");
  my @rangemax =  $dictionary->get_item_data(-save=>$saveblock,
					     -item=>"_item_range.maximum");
  die "Maximum and minimum ranges have different sizes!"
    unless ( $#rangemin == $#rangemax);

  if (@rangemin) { # only do the next bit if there is a range!
    # the starting max, min values are based on the usual arrangement in
    # mmCIF of giving these in descending order; but it shouldn't matter
    # to the sorting algorithm
    my $low = $rangemin[$#rangemin];
    my $high = $rangemax[0];
    my $floor = 0;
    my $ceil = 0;

    for (my $index = 0; $index <= $#rangemin; $index++) {
      my $min = $rangemin[$index];
      my $max = $rangemax[$index];
      # Logic: test first for min = '.' If it is and max = '.' then you
      # have an open interval from minus to plus infinity
      if ($min eq '.' ) {
	$low = '-infinity';
	$floor = 0;
	if ($max eq '.' ){
	  $high = 'infinity';
	  $ceil = 0;
	} else {
	  # ... but if max is a numeric value test whether it's the largest
	  # seen so far, and assign it as highest if need be
	  if ($max > $high) {
	    $high = $max
	  }
	}
      } else {
	# On the other hand, if min is a numeric value, then test whether
	# it's the lowest
	if ($min <= $low) {
	  $low = $min;
	  if ( $min == $max ) { $floor = 1; }
	} else {
	  if ($max >= $high) {
	    $high = $max;
	    if ( $min == $max ) { $ceil = 1; }
	  }
	}
	if ($max eq '.' ){
	  $high = 'infinity';
	  $ceil = 0;
	}
      }
      if ($index > 2) { print "% WARNING! More than three range values\n"; }
    }
    print ENTRY "\n<br />The permitted range is " ;
    if ($floor) { print ENTRY "["; } else { print ENTRY "("; }
    print ENTRY $low . ", " . $high;
    if ($ceil) { print ENTRY "]"; } else { print ENTRY ")"; }
    print ENTRY "<br />\n";
  }

  #-----------------------------------------------------------------------
  # Related items
  my @relname = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_related.related_name");
  my @relcode = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_related.function_code");
  die "Related names and codes do not match!"
    unless ( $#relname == $#relcode);

  if (@relname) { # only do the next bit if there are any!
    if ($#relname == 0 ) {
      print ENTRY "\n<p class=\"pack\"><b>Related item: </b>";
    } else {
      print ENTRY "\n<p class=\"pack\"><b>Related items: </b>";
    }
    for (my $index = 0; $index <= $#relname; $index++) {
      print ENTRY
	format_attribute($relname[$index], "_related_item") ;
	# format_attribute($relname[$index], "_item_related.related_name") ;
      print ENTRY
	format_attribute($relcode[$index],"_related_function") ;
	# format_attribute($relcode[$index],"_item_related.function_code") ;
    }
  }

  #-----------------------------------------------------------------------
  # Enumeration
  my @enumval = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_enumeration.value");
  my @enumdtl = $dictionary->get_item_data(-save=>$saveblock,
					   -item=>"_item_enumeration.detail");
  if (@enumdtl) {
    die "Examples and accompanying details do not match!"
      unless ( $#enumval == $#enumdtl);
  }

  if (@enumval) { # only do the next bit if there are any!
    print ENTRY
      "\n<br /><b>The data value must be one of the following:</b><br /><br />\n";
    if ($saveblock =~ /^_diffrn.source\.target$/ ) {
      $symctr = 0;
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' '; }
	print ENTRY "<td class=\"cifexample\">" . format_attribute($symbol, "_enumeration" ) . "</td>\n";
	$symctr++;
	if ($symctr > 14) {
	  print ENTRY "</tr><tr class=\"cifexample\">\n";
	  $symctr = 0;
	}
      }
    } elsif ($saveblock =~ /^_space_group\.name_Schoenflies$/ ) {
      print ENTRY "<table width=\"100%\"><tr class=\"cifexample\">";
      $symctr = 0;
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = sprintf("%-6s", $enumval[$index]);
	print ENTRY "<td class=\"cifexample\">" . format_attribute($symbol, "_enumeration" ) . "</td>\n";
	$symctr++;
	if ($symctr > 6) {
	  print ENTRY "</tr><tr class=\"cifexample\">\n";
	  $symctr = 0;
	}
      }
      print ENTRY "</td></tr></table>\n";
    } elsif ($saveblock =~ /^_space_group_Wyckoff\.letter$/ ) {
      print ENTRY "<table width=\"100%\"><tr class=\"cifexample\">";
      $symctr = 0;
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' '; }
	print ENTRY "<td class=\"cifexample\">" . format_attribute($symbol, "_enumeration" ) . "</td>\n";
	$symctr++;
	if ($symctr > 14) {
	  print ENTRY "</tr><tr class=\"cifexample\">\n";
	  $symctr = 0;
	}
      }
      print ENTRY "</td></tr></table>\n";
# } elsif  ($saveblock =~ /^_space_group\.name_H-M_ref$/ )  {
# print "\\begin{tabular}{llll}\n";
# $symctr = 0;
# for (my $index = 0; $index <= $#enumval ; $index++) {
# my $text = '';
# my $symbol = $enumval[$index];
# print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{"
# . $symbol . ' ' . "}}  & %\n";
# 
# if ($enumdtl[$index]) {
# # parse the argument to detect the Schoenflies symbol and format
# my $d = $enumdtl[$index];
# my $a;
# my $b;
# ($a = $d) =~ s/^( *[0-9]* *)(.*)$/$1/;
# ($b = $d) =~ s/^( *[0-9]* *)(.*)$/$2/;
# my $c = $a . " " . Schoenflies($b);
# print "{\\fontsize{9}{10pt}\\selectfont{" . $c . "}}";
# $symctr++;
# if ($symctr > 1) {
# print " \\\\ %\n";
# $symctr = 0;
# } else { print " & %\n"; }
# }
# }
# print "\\end{tabular}\n";
# } elsif  ($saveblock =~ /^_space_group\.reference_setting$/ )  {
# print "\\begin{tabular}{ll}\n";
# $symctr = 0;
# for (my $index = 0; $index <= $#enumval ; $index++) {
# my $text = '';
# my $symbol = $enumval[$index];
# print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{"
# . $symbol . ' ' . "}}  & %\n";
# 
# if ($enumdtl[$index]) {
# # parse the argument to detect the Schoenflies symbol and format
# my $d = $enumdtl[$index];
# my $a;
# my $b;
# ($a = $d) =~ s/^( *[A-Za-z0-9\.]* *)(.*)$/$1/;
# ($b = $d) =~ s/^( *[A-Za-z0-9\.]* *)(.*)$/$2/;
# my $c = Schoenflies($a) . "\\quad " . HM($b);
# print "{\\fontsize{9}{10pt}\\selectfont{" . $c . "}}";
# # $symctr++;
# # if ($symctr > 1) {
# print " \\\\ %\n";
# # $symctr = 0;
# # } else { print " & %\n"; }
# }
# }
# print "\\end{tabular}\n";
    } else {
      if ($saveblock =~ /^_space_group\.name_H-M_ref$/ ) {
	print ENTRY '
<table width="100%"><tr><td width="50%"><i>Enumeration</i></td>
<td width="50%"><i>Number and Schoenflies symbol</i></td></tr></table>';
      }
      for (my $index = 0; $index <= $#enumval ; $index++) {
	print ENTRY "<table width=\"100%\"><tr class=\"cifexample\"><td width=\"50%\">";
	print ENTRY format_attribute(cifquote($enumval[$index]), "_enumeration" ) ;
	print ENTRY "</td><td width=\"50%\">";
	print ENTRY format_attribute($enumdtl[$index],"_enumeration_detail") ;
	print ENTRY "</td></tr></table>\n";
      }
    }
}
  #-----------------------------------------------------------------------
  # Enumeration default
  foreach my $item (
		    "_item_default.value",
		   ) {
    my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	print ENTRY format_attribute($attribute, $item);
      }
    }
  }

  #-----------------------------------------------------------------------
  foreach my $item ( # specifies ordering of output items
		    "_item.category_id",
		   ) {
    my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);
    if ($multiple) { # multiple categories occur in an id listing
      # print "[DEBUG]: Multiple category entries for $saveblock\n";
      print ENTRY format_attribute($attributes[0], $item);
    } else {
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  print ENTRY format_attribute($attribute, $item);
	  # # Collect categories for use by other utilities
	  # my $categorylistfile = "/agatehome/bm/cifdic_perl/categorylist";
	  # open (FILE, ">> " . $categorylistfile);
	  # print FILE "\"" . $itemname . "\"=>\"" . $attribute . "\",\n";
	  # close FILE;
	}
      }
    }
  }

  # print footer HTML for file
  print ENTRY standard_footer();

  close (ENTRY);


  return;
}

######################## DDL-independent ###############################
########################################################################
# Format individual attribute text (modifications depend on the
# type of item)
########################################################################
sub format_attribute {
  my $attribute = shift;
  my $item = shift;
  my $text = '';

  if ($item =~ /^_name$/) {
    $text = "<tt class=\"cifdefinition\">'" . htmlsafe($attribute) . "'</tt> ";
  }
  elsif ($item =~ /^_category\.id$/) {
    $text = "<tt class=\"cifdefinition\">'" . htmlsafe($attribute) . "'</tt> ";
  }
  elsif ($item =~ /^_category\.description$/) {
$attribute =~ s/^        /  /;
    $text = "\n<p><b>Description:</b></p><pre class=\"cifdefinition\">\n"
      . findlinks(htmlsafe($attribute)) . "\n</pre>";
      # . leftpad(findlinks(htmlsafe($attribute)), 6) . "\n</pre></p>";
  }
  elsif ($item =~ /^_category\.mandatory_code$/) {
    $text = "\n<p class=\"pack\"><b>Mandatory category:</b> " . htmlsafe($attribute) . "</p>\n";
  }
  elsif ($item =~ /^_category_group\.id$/) {
    $text = "\n<tt>&nbsp;&nbsp;&nbsp;&nbsp;" . htmlsafe($attribute) . "</tt><br />\n";
  }
  elsif ($item =~ /^_category_key\.name$/) {
    $text = "\n<tt>&nbsp;&nbsp;&nbsp;&nbsp;" . htmlsafe($attribute) . "</tt><br />\n";
  }
  elsif ($item =~ /^(_definition|_item_description\.description)$/) {
$attribute =~ s/^        /  /;
    $text = "\n<p><b>Definition:</b></p><pre class=\"cifdefinition\">\n"
      . findlinks(htmlsafe($attribute)) . "\n</pre>";
      # . leftpad(findlinks(htmlsafe($attribute)), 6) . "\n</pre></p>";
  }
  elsif ($item =~ /^_category_examples\.case$/) {
    $text = "<br /><pre class=\"cifexample\">\n " . htmlsafe($attribute)
      . "\n</pre><br />";
  }
  elsif ($item =~ /^_category_examples\.detail$/) {
    $text = "<i>\n " . htmlsafe($attribute) . "\n</i>";
  }
  elsif ($item =~ /^_item_aliases\.dictionary$/) {
    $text = htmlsafe($attribute) ;
  }
  elsif ($item =~ /^_item_aliases\.alias_name$/) {
    $text = "\n<br /><tt> " . htmlsafe($attribute) . "</tt>\n";
  }
  elsif ($item =~ /^_item_aliases\.version$/) {
    $text = "\nversion " . htmlsafe($attribute) ;
  }
  elsif ($item =~ /^_item_linked\.child_name$/) {
    $text = linkitem(htmlsafe($attribute)) . "<br />\n" ;
  }
  elsif ($item =~ /^_item\.category_id$/) {
    $text = "\n<p class=\"pack\"><b>Category:</b> " . linkcategory(htmlsafe($attribute))
      . "</p>\n";
  }
  elsif ($item =~ /^_item\.mandatory_code$/) {
    $text = "\n<p class=\"pack\"><b>Mandatory item:</b> " . htmlsafe($attribute) . "</p>\n";
  }
  elsif ($item =~ /^_item\.name$/) {
    $text = "<b>Name:</b><br /><tt class=\"cifdefinition\">'" . htmlsafe($attribute) . "'</tt> ";
  }
  elsif ($item =~ /^_item_type\.code/) {
    $text = "\n<p><b>Type:</b> " . htmlsafe($attribute) . "</p>\n";
  }
  elsif ($item =~ /^_item_type_conditions\.code$/) {
    $text = "\n<p class=\"pack\"><b>Type conditions:</b> " . htmlsafe($attribute) . "</p>\n";
  }
  elsif ($item =~ /^_category$/) {
    $text = "\n<p class=\"pack\"><b>Category:</b> " . linkcategory(htmlsafe($attribute))
      . "</p>\n";
  }
  elsif ($item =~ /^_enumeration$/) {
    $text = "\n<br /><tt>" .  htmlsafe($attribute) . "</tt>\n";
  }
  elsif ($item =~ /^_enumeration_detail$/) {
    $text = " " .  htmlsafe($attribute) . "\n";
  }
  elsif ($item =~ /^(_enumeration_default|_item_default\.value)$/) {
    $text = "\n<br />Enumeration default:  </b>" .  htmlsafe($attribute);
  }
  elsif ($item =~ /^_example$/) {
    if ($attribute =~ /\n/ ) {
      $text = "<pre class=\"cifexample\">" . cifquote(htmlsafe($attribute))
	. "</pre>\n";
    } else {
      $text = "<tt class=\"cifexample\">" . cifquote(htmlsafe($attribute))
	. "</tt>\n";
    }
  }
  elsif ($item =~ /^_example_detail$/) {
    $text = "<i>" . htmlsafe($attribute) . "</i>\n";
  }
  elsif ($item =~ /^_list$/) {
    if ($attribute =~ /^yes$/) { $text = "\n<p class=\"pack\">Appears in list "; }
    if ($attribute =~ /^both$/) { $text = "\n<p class=\"pack\">May appear in list "; }
    if ($attribute =~ /^no$/) { $text = "\n<p class=\"pack\">Does not appear in list "; }
  }
  elsif ($item =~ /^_list_level$/) {
    if ($attribute =~ /^yes$/) { $text = "as essential element of loop structure "; }
  }
  elsif ($item =~ /^_list_link_child$/) {
    $text = linkitem(htmlsafe($attribute)) . "<br />\n" ;
  }
  elsif ($item =~ /^_list_link_parent$/) {
    $text = "<p class=\"pack\"><b>Must</b> match data name" .
      linkitem(htmlsafe($attribute)) . "</p>\n" ;
  }
  elsif ($item =~ /^_list_mandatory$/) {
    if ($attribute =~ /^yes$/) { $text = "as essential element of loop structure "; }
  }
  elsif ($item =~ /^_list_reference$/) {
    $text = linkitem(htmlsafe($attribute));
  }
  elsif ($item =~ /^_related_function$/) {
    $text = " (" . linkitem(htmlsafe($attribute)) . ")<br />\n";
  }
  elsif ($item =~ /^_related_item$/) {
    $text = linkitem(htmlsafe($attribute));
  }
  elsif ($item =~ /^_type$/) {
    $text = "\n<p><b>Type:</b> " . htmlsafe($attribute) . "</p>\n";
  }
  elsif ($item =~ /^deprecated$/) {
    $text = "<tt class=\"deprecated\">'" . linkitem(htmlsafe($attribute))
      . "'</tt> ";
  }

  # Default - no specific transformation
  else {
    $text = htmlsafe($attribute);
  }

  return $text;
}

# hyperlink to definition of related item
sub linkitem {
  my $item = shift;
  my $popup = shift || '';
  my $string = $item;
  my $link;
  my $target;

  # the input string may well have been htmlised - reverse this
  chomp ($string);
  $string =~ s/\&lt;/</g;
  $string =~ s/\&gt;/>/g;
  $string =~ s/\&amp;/\&/g;

  # We're searching for a file to hyperlink to. We'll start by looking in
  # the current directory (for definitions in the current dictionary). If
  # that doesn't work, we'll look at other dictionaries also

  # Since it's an item, it will be named something like Icell_volume.html
  ($target = $item) =~ s/^_/I/;
  $target =~ s/$/.html/;
  $target =~ s/\//=over=/g;

  opendir (DIR, $workdir);
  while (defined(my $file = readdir(DIR))) {
      next if ($file =~ /^\.\.?$/); # skip '.' and '..'
      next unless $file eq $target;
      $link = $target;
  }
  closedir(DIR);

  # If that didn't work, we'll try again, but chop off the last component
  # of the name because we're looking for a set of common definitions
  my $newtarget;
  ($newtarget = $target) =~ s/_([^_])*\.html/_.html/;
  opendir (DIR, $workdir);
  while (defined(my $file = readdir(DIR))) {
      next if ($file =~ /^\.\.?$/); # skip '.' and '..'
      next unless $file eq $newtarget;
      $link = $newtarget;
  }
  closedir(DIR);

  # If we've had no luck, we'll check other dictionaries of the same DDL
  unless ($link) {
    opendir (DIR, $workdir . "/..");
    while (defined(my $subdir = readdir(DIR))) {
      next if ($link) ; # Bail out if we've now found it!
      next if ($subdir =~ /^\.\.?$/); # skip '.' and '..'
      # next unless  (-d $workdir . "/../" . $subdir); # skip unless directory
      opendir (SUBDIR, $workdir . "/../" . $subdir);
      while (defined(my $file = readdir(SUBDIR))) {
	next if ($link) ; # Bail out if we've now found it!
	next if ($file =~ /^\.\.?$/); # skip '.' and '..'
	next unless $file eq $target;
	$link = "../" . $subdir . "/" . $target;
	if ($verbose) {print "     Found link in other dictionary at $link\n";}
      }
      closedir(SUBDIR);
    }
    closedir(DIR);
  }

  # If still no luck, we'll try the trick of chopping off the last part again
  unless ($link) {
    opendir (DIR, $workdir . "/..");
    while (defined(my $subdir = readdir(DIR))) {
      next if ($link) ; # Bail out if we've now found it!
      next if ($subdir =~ /^\.\.?$/); # skip '.' and '..'
      # next unless  (-d $workdir . "/../" . $subdir); # skip unless directory
      opendir (SUBDIR, $workdir . "/../" . $subdir);
      while (defined(my $file = readdir(SUBDIR))) {
	next if ($link) ; # Bail out if we've now found it!
	next if ($file =~ /^\.\.?$/); # skip '.' and '..'
	next unless $file eq $newtarget;
	$link = "../" . $subdir . "/" . $newtarget;
	if ($verbose) {print "     Found link in other dictionary at $link\n";}
      }
      closedir(SUBDIR);
    }
    closedir(DIR);
  }

  if ($link) {
    if ($popup) {
      return "<a target=\"_blank\" href=\"$link\" class=\"itemlink\">"
	. $item . "</a>";
    } else {
      return "<a href=\"$link\" class=\"itemlink\">" . $item . "</a>";
    }
  } else {
    return $item;
  }
}

# hyperlink to definition of alias - like linkitem, but can traverse DDLs
sub linkalias {
  my $item = shift;
  my $dict = shift;

  my $popup = 'yes';
  my $string = $item;
  my $link;
  my $target;

  # the input string may well have been htmlised - reverse this
  chomp ($string);
  $string =~ s/\&lt;/</g;
  $string =~ s/\&gt;/>/g;
  $string =~ s/\&amp;/\&/g;

  # We're searching for a file to hyperlink to. We'll start by searching
  # for a directory with the right dictionary name

  foreach my $ddl (2, 1) {

    my $ddldir = $workdir . "/../../" . $ddl;
    opendir (DDL, $ddldir);
    while (defined(my $dir = readdir(DDL))) {
      next if ($dir =~ /^\.\.?$/); # skip '.' and '..'
      next unless $dir eq $dict;

      # Since it's an item, it will be named something like Icell_volume.html
      ($target = $item) =~ s/^_/I/;
      $target =~ s/$/.html/;
      $target =~ s/\//=over=/g;

      opendir (DIR, $workdir . "/../../" . $ddl . "/" . $dir) ;
      while (defined(my $file = readdir(DIR))) {
	next if $link;
	next if ($file =~ /^\.\.?$/); # skip '.' and '..'
	next unless $file eq $target;
	$link = "../../".  $ddl . "/" . $dir . "/" . $target;
      }
      closedir(DIR);

      next if ($link);

      # If that didn't work, we'll try again, but chop off the last component
      # of the name because we're looking for a set of common definitions
      my $newtarget;
      ($newtarget = $target) =~ s/_([^_])*\.html/_.html/;
      opendir (DIR, $workdir . "/../../" . $ddl . "/" . $dir) ;
      while (defined(my $file = readdir(DIR))) {
	next if $link;
	next if ($file =~ /^\.\.?$/); # skip '.' and '..'
	next unless $file eq $newtarget;
	$link = "../../" . $ddl . "/" . $dir . "/" . $newtarget;
      }
      closedir(DIR);
      closedir(DDL);
    }

  }

    if ($link) {
if ($verbose) {print STDERR " Found alias as $link\n";}
      return "<a href=\"$link\" class=\"itemlink\">" .
	$item . "</a>";
    } else {
      return $item;
    }


# if ($link) {
#   if ($popup) {
#     return "<a target=\"_blank\" href=\"$link\" class=\"itemlink\">"
#. $item . "</a>";
#   } else {
#     return "<a href=\"$link\" class=\"itemlink\">" . $item . "</a>";
#   }
# } else {
#   return $item;
# }
}

# hyperlink to definition of category
sub linkcategory {
  my $item = shift;
  my $string = $item;
  my $link;
  my $target;

  # the input string may well have been htmlised - reverse this
  chomp ($string);
  $string =~ s/\&lt;/</g;
  $string =~ s/\&gt;/>/g;
  $string =~ s/\&amp;/\&/g;

  # We're searching for a file to hyperlink to. We'll start by looking in
  # the current directory (for definitions in the current dictionary). If
  # that doesn't work, we'll look at other dictionaries also

  # Since it's a category, it will be named something like Ccell.html
  ($target = $item) =~ s/^/C/;
  $target =~ s/$/.html/;

  opendir (DIR, $workdir);
  while (defined(my $file = readdir(DIR))) {
      next if ($file =~ /^\.\.?$/); # skip '.' and '..'
      next unless lc($file) eq lc($target);
      $link = $file;
  }
  closedir(DIR);

  # If we've had no luck, we'll check other dictionaries of the same DDL
  unless ($link) {
    opendir (DIR, $workdir . "/..");
    while (defined(my $subdir = readdir(DIR))) {
      next if ($link) ; # Bail out if we've now found it!
      next if ($subdir =~ /^\.\.?$/); # skip '.' and '..'
      # next unless  (-d $workdir . "/../" . $subdir); # skip unless directory
      opendir (SUBDIR, $workdir . "/../" . $subdir);
      while (defined(my $file = readdir(SUBDIR))) {
	next if ($link) ; # Bail out if we've now found it!
	next if ($file =~ /^\.\.?$/); # skip '.' and '..'
	next unless lc($file) eq lc($target);
	$link = "../" . $subdir . "/" . $file;
	if ($verbose) {print "     Found link in other dictionary at $link\n";}
      }
      closedir(SUBDIR);
    }
    closedir(DIR);
  }

  if ($link) {
    return "<a href=\"$link\" class=\"catlink\">" .
      $item . "</a>";
  } else {
    return $item;
  }
}

# Find links in a string
sub findlinks {
  my $string = shift;

  $string =~ s/\b(_[^ \t]*[\.,;:'")]?)\b/linkitem($1)/mge;
  $string =~ s/\b([A-Za-z][A-Za-z]*_[^ \t]*[\.,;:'"]?)\b/linkcategory($1)/mge;

  return $string;
}

# Boilerplate HTML fragments
sub standard_header {

  my $text =
'<!-- begin masthead -->
  <div class="masthead">
    <table class="masthead">
      <tr>
        <td class="masthead" width="30%">
          <p class="title">INTERNATIONAL TABLES</p>
          <p class="title"><font class="smalltitle">for</font> CRYSTALLOGRAPHY</p>
        </td>
        <td class="masthead" width="20%">
          <p class="redtitle">Volume <span class="volumeletter">G</span></p>
        </td>
        <td class="masthead">
          <p class="title">Definition and Exchange of Crystallographic Data</p>
        </td>
      </tr>
    </table>
  </div>
  <!-- end masthead -->

  <!-- begin navbar -->
  <div class="navbar">
    <table class="navbar">
      <tr>
        <td><a class="navbar" href="../../../../index.html">Contents</a></td>
        <td align="center"><a class="navbar" href="../../../../chapters/index.html">Resources</a></td>
        <td align="center"><a class="navbar" href="../../../../specs/index.html">Specifications</a></td>
        <td align="center"><a class="navbar" href="../../../../dicts/index.html">Dictionaries</a></td>
        <td align="right"><a class="navbar" href="../../../../software/index.html">Software</a></td>
      </tr>
    </table>
  </div>
  <!-- end navbar -->

';

  return $text;
}

sub standard_footer {
  my $text =
'
  <!-- begin copyright -->
  <div class="copyright">
    <p class="copyright">Copyright &copy; 2005 International Union of Crystallography</p>
  </div>
  <!-- end copyright -->

</body>
</html>
';

    return $text;
}

sub deprecated {
  my $text =
'
<p class="deprecated">This definition has been superseded and is
retained here only for archival purposes. Use instead
';
    return $text;
}


# Some HTML transformations
sub htmlsafe {
  my $string = shift;

  $string =~ s/\&/\&amp;/g;
  $string =~ s/</\&lt;/g;
  $string =~ s/</\&gt;/g;

  return $string;
}

# Generate appropriate CIF quoting for strings
sub cifquote {
  my $string = shift;

  chomp ($string);

  if ($string =~ /\n/ ) {
    $string =~ s/^/;/;
    $string =~ s/$/\n;/;
  }

  elsif ($string =~ / / ) {
    if ($string =~ /'/ ) {
      $string =~ s/^/"/;
      $string =~ s/$/"/;
      } else {
      $string =~ s/^/'/;
      $string =~ s/$/'/;
    }
  }

  return $string;
}

# Left pad a text field to a desired number of spaces
sub leftpad {
  my $string = shift;
  my $no = shift || 0;

  if ($no == 0) {
    $string =~ s/^  *//mg;
  } else {
    $string =~ s/^  *//mg;
    while ($no--) {
      $string =~ s/^/ /mg;
    }
  }

  return $string;
}
