#!/usr/local/bin/perl
##############################################################################
# makedict.pl - Perl script to generate LaTeX version of CIF dictionary      #
##############################################################################
# Usage: makedict.pl -[v] [dictfile] [part] [ch] [title] > [outfile.tex]     #
#          where -[v] is DDL version (-1 or -2, -1 is default and can be     #
#          omitted), [dictfile] is a CIF dictionary file, [part] is the Part #
#          number in International Tables G, [ch] the chapter number within  #
#          that Part, [title] the chapter title, and [outfile.tex] is the    #
#          output file (i.e. the script writes to stdout)                    #
# Complex formatted definitions can be stored as cached TeX files in the     #
# directory cifdic_work/[dictname+version]/cache/ where [dictname+version]   #
# is a name containing the dictionary name and version found within the file #
# as _dictionary_name and -dictionary_version (DDL1) or _dictionary.title    #
# and  _dictionary.version (DDL2). For example cif_core.dic+2.3.1. To help   #
# create TeX fragments, all the definitions are dumped into a directory      #
# cifdic_work/[dictname+version]/work/ the first time the utility is run for #
# each dictionary. These ASCII files can be copied into the work/ directory  #
# and marked up in TeX as required. If it is necessary to force a fresh dump #
# of the ASCII definitions (e.g. after editing the definitions in the        #
# file itself, use the -f flag:  makedict.pl -f -[version] [dictfile]  ...   #
##############################################################################
# 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);
use vars qw($ROOTDIR $CACHEDIR $workdir $cachedir $dump);
use vars qw($symctr); # counts no. elements per line (_diffrn_source.target)

$version = "version 0.01";
$ROOTDIR = "./cifdic_work"; # Parent directory for cached file fragments
$CACHEDIR = ''; # initialise
$dictname = '';
$dictversion = '';
$dump = 0; # flag to say whether this is the first run for this dictionary
$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
$options .= 'f' if ( $opt_f );   # force dump
$ddl = 1 if ( $opt_1 );   # DDL version
$ddl = 2 if ( $opt_2 );   # DDL version
$dump = 1 if ( $opt_f );   # force dump
$dict = 1; # by default (this is a dictionary-based application)

my $file = $ARGV[0];
my $part =  $ARGV[1] || '4';
my $chapter =  $ARGV[2] || '5';
my $title =  $ARGV[3] || 'Macromolecular dictionary (mmCIF)';

# 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
}


# Print the standard LaTeX introduction to the output file
print "\\documentclass[it]{iucr}\n";
print "  \\part{$part}\n";
print "  \\chapter{$chapter}\n";
print "  \\parttitle{DATA DICTIONARIES}\n";
print "\\setcounter{page}{1}\n";
print "\\begin{document}\n";
print "\\title{$title}\n";

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 $cifdicname;
  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 ;

    # 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);

    $CACHEDIR = $ROOTDIR . "/" . $dictname . "+" . $dictversion;

    $workdir = $CACHEDIR . "/work";
    $cachedir = $CACHEDIR . "/cache";
    unless (-d $CACHEDIR) {
      mkdir ($CACHEDIR, 0775) || die "Couldn't make " . $CACHEDIR . "\n";
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
      mkdir ($cachedir, 0775) || die "Couldn't make " . $cachedir . "\n";
      $dump = 1;
    }
    # Check again against workdir (allows workdir to be flushed while
    # retaining existing cached files)
    unless (-d $workdir) {
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
      $dump = 1;
    }

  } # end of foreach loop per included data block

  # Emit the name of the dictionary as a TeX macro
    ($cifdicname = $dictname) =~ s/_/\\_/g;
    $cifdicname =~ s/\%/\\\%/g;

  # If there is a cached author description, use it
  if (-s $cachedir . "/author" ) {
    if ($verbose) { print STDERR "Using cached author info\n"; }
    open (INTRO, "< " . $cachedir . "/author");
    while (<INTRO>) { print; }
    close(INTRO);
  }
    print "\\gdef\\cifdicname{$cifdicname}\n";

    print "\\maketitle\n";
    print "\\begin{multicols}{2}\n";

    print "\\batchmode\n";
  # If there is a cached introduction, use it
  if (-s $cachedir . "/intro" ) {
    if ($verbose) { print STDERR "Using cached introduction\n"; }
    open (INTRO, "< " . $cachedir . "/intro");
    while (<INTRO>) { print; }
    close(INTRO);
  }

  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\]$/) {
	    if ($verbose) { print STDERR "Category $name\n"; }
	    format_category1($dblock);
	  } else {
	    if ($verbose) { print STDERR "    item $name\n"; }
	    if ($name =~ /[a-zA-Z]/) {format_item1($dblock); }
	  }
	}
      }
    }
  } else {
    # For other dictionaries the ordering is more orderly by category
    #
    # Now we have a list of categories, which we sort in alphabetic order, and
    # we re-traverse the dictionary to extract the items belonging to each
    # category in turn
    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;

      foreach $dblock (@dblocks) {
	my $title = $dblock->title;
	my $name = ($dblock->get_item_data(-item=>"_name"))[0];
	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"; }
	  format_category1($dblock);
	} elsif ($categoryassignment eq $categoryid) {
	  if ($verbose) { print STDERR "    item $name\n"; }
	  format_item1($title);
	}
      }
    } # end of foreach loop per category
  }

} # 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];

    my $cifdicname;
    ($cifdicname = $dictname) =~ s/_/\\_/g;
    $cifdicname =~ s/\%/\\\%/g;
    print "\\gdef\\cifdicname{$cifdicname}\n";

    $CACHEDIR = $ROOTDIR . "/" . $dictname . "+" . $dictversion;

    $workdir = $CACHEDIR . "/work";
    $cachedir = $CACHEDIR . "/cache";
    unless (-d $CACHEDIR) {
      mkdir ($CACHEDIR, 0775) || die "Couldn't make " . $CACHEDIR . "\n";
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
      mkdir ($cachedir, 0775) || die "Couldn't make " . $cachedir . "\n";
      $dump = 1;
    }
    # Check again against workdir (allows workdir to be flushed while
    # retaining existing cached files)
    unless (-d $workdir) {
      mkdir ($workdir, 0775) || die "Couldn't make " . $workdir . "\n";
      $dump = 1;
    }

  # If there is a cached author description, use it
  if (-s $cachedir . "/author" ) {
    if ($verbose) { print STDERR "Using cached author info\n"; }
    open (INTRO, "< " . $cachedir . "/author");
    while (<INTRO>) { print; }
    close(INTRO);
  }
    print "\\gdef\\cifdicname{$cifdicname}\n";

    print "\\maketitle\n";
    print "\\begin{multicols}{2}\n";

    print "\\batchmode\n";
  # If there is a cached introduction, use it
  if (-s $cachedir . "/intro" ) {
    if ($verbose) { print STDERR "Using cached introduction\n"; }
    open (INTRO, "< " . $cachedir . "/intro");
    while (<INTRO>) { print; }
    close(INTRO);
  }
#-----------------------------------------------------------------------
    # Get the list of category groups

    my %categorygroupattributes =
      (
       "_category_group_list.id"=>"\\categorygrouplistid",
       "_category_group_list.description"=>"\\categorygrouplistdescription",
      );

    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!
      for (my $index = 0; $index <= $#catdescs ; $index++) {
	if ($catgroups[$index] && $catgroups[$index] !~ /^inclusive_group$/ ) {
	  print $categorygroupattributes{"_category_group_list.id"}
	    . "{" . format_attribute($catgroups[$index],
				     "_category_group_list.id")
	      . "}%\n";
	  # }
	  print $categorygroupattributes{"_category_group_list.description"}
	    . "{" . format_attribute($catdescs[$index],
				     "_category_group_list.description")
	      . "}%\n";
	}
      }
      print "\\vskip\\interblockskip\n";
    }

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

    # 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
	format_category2($saveblock); # Format the category description
	my $category = $categories[0] ;
	foreach my $block (@saveblocks) { # Re-traverse the list of blocks
	  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
	    format_item2($block) if $itemcategoryids[0] =~ /^$category$/i;
	  } elsif ( $#itemname == 0 && $itemname[0] =~ /^_$category\./i ) {
	    print "% DEBUG: entry for " . $block . " located implicitly\n";
	    format_item2($block);
	  } elsif ( $itemname[0] =~ /^_$category\./i ) {
	    print "% DEBUG: looks like multiple ids defined in $block \n";
	    format_item2($block, "multiple");
	  }

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

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

  } # 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!";
}

print "\\end{multicols}\n";
print "\\end{document}\n";
exit;

########################################################################
# Output LaTeX formatting for a category block
########################################################################
######################## DDL1 ##########################################
sub format_category1 {
  # my $block = shift;
    my $categoryname = ($dblock->get_item_data(-item=>"_name"))[0];
    $categoryname =~ s/^_+//;
    $categoryname =~ s/_\[[A-Za-z0-9]*\]$//;

  my %categoryattributes = (
    "_name"=>"\\categoryname",
    "_definition"=>"\\categorydescription",
    "_example_detail"=>"\\categoryexamplesdetail",
    "_example"=>"\\categoryexamplescase",
  );

  print "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
  print "% " . $categoryname . "\n";
  print "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
  print "\\begin{boxedcifdefblock}\n";
  print "\\noindent\\DATA{" . uc(texify($categoryname)) . "}%\n";
  my $cifdicrhead ;
  ($cifdicrhead = uc($categoryname) ) =~ s/^_*//;
  $cifdicrhead =~ s/\..*//;
  $cifdicrhead =~ s/_/\\_/g;
  $cifdicrhead =~ s/\%/\\\%/g;
  print "\\gdef\\cifdicrhead{$cifdicrhead}%\n";

  foreach my $item ( # specifies ordering of output items
    "_name",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	if ($item =~ /^_name$/) {
	  my $s;
	  ($s = uc($attribute)  ) =~ s/^_*//;
	  $s =~ s/\..*//;
	  $s =~ s/_/\\_/g;
	  $s =~ s/\%/\\\%/g;
	  $s =~ s/\\_\[[a-zA-Z]*\] *$//g;
	  $attribute = $s;
			}
	my $text = format_attribute($attribute, $item);
	print $categoryattributes{$item} . "{" . $text . "}\n";

      }
    }
  }
  foreach my $item ( # specifies ordering of output items
    "_definition",
		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	if ($item =~ /^_name$/) {
	  my $s;
	  ($s = uc($attribute)  ) =~ s/^_*//;
	  $s =~ s/\..*//;
	  $s =~ s/_/\\_/g;
	  $s =~ s/\%/\\\%/g;
	  $s =~ s/\\_\[[a-zA-Z]*\] *$//g;
	  $attribute = $s;
			}
#========================================================================
	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];

	# First run: dump all the definition blocks
	if ($dump == 1 && $item =~/^_definition$/ ) {
	  my $text = format_attribute($definition, $item);
	  open (DEFINITION, "> ". $workdir . "/" . $blockname);
	  print DEFINITION $categoryattributes{$item}. "{";
	  print DEFINITION $text;
	  print DEFINITION "}%\n";
	  close DEFINITION;
	  print $categoryattributes{$item} . "{" . $text . "}%\n";
	} else {
	  # If there is a cached definition, use it
	  if (-s $cachedir . "/" . $blockname ) {
	    if ($verbose) { print STDERR "Using cached category entry " . $blockname . "\n"; }
	    open (DEFINITION, "< " . $cachedir . "/" . $blockname);
	    while (<DEFINITION>) { print; }
	    close(DEFINITION);
	  } else {
	    my $text = format_attribute($definition, $item);
	    print $categoryattributes{$item} . "{" . $text . "}%\n";
	  }
	}
#========================================================================
	# my $text = format_attribute($attribute, $item);
	# print $categoryattributes{$item} . "{" . $text . "}\n";

      }
    }
  }
#-----------------------------------------------------------------------
# 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!
    for (my $index = 0; $index <= $#example ; $index++) {
      if ($details[$index]) {
	$details[$index] =~ s/^\n *//;
	$details[$index] =~ s/ - / -- /g;

	  print $categoryattributes{"_example_detail"} . "{" .
	    format_attribute($details[$index],"_category_examples.detail") .
	      "}%\n";
	}
      print $categoryattributes{"_example"} . "{" .
	format_attribute($example[$index], "_category_examples.case") . "}%\n";
    }
  }

  print "\\end{boxedcifdefblock}\n";
  # print "\\bigskip\n";
  print "\\vskip\\interblockskip\n";
  return;
}

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

  my %categoryattributes = (
    "_category.id"=>"\\categoryid",
    "_category.description"=>"\\categorydescription",
    "_category.mandatory_code"=>"\\categorymandatorycode",
    "_category_group.id"=>"\\categorygroupid",
    "_category_key.name"=>"\\categorykeyname",
    "_category_examples.detail"=>"\\categoryexamplesdetail",
    "_category_examples.case"=>"\\categoryexamplescase",
  );

  print "\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
  print "% " . $saveblock . "\n";
  print "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
  print "\\begin{boxedcifdefblock}\n";
  print "\\noindent\\DATA{" . uc(texify($saveblock)) . "}%\n";
  my $cifdicrhead ;
  ($cifdicrhead = uc($saveblock) ) =~ s/^_*//;
  $cifdicrhead =~ s/\..*//;
  $cifdicrhead =~ s/_/\\_/g;
  print "\\gdef\\cifdicrhead{$cifdicrhead}%\n";

  foreach my $item ( # specifies ordering of output items
    "_category.id",
    "_category.description",
    "_category.mandatory_code",
    "_category_group.id",
    "_category_key.name",
		   ) {
    my @attributes = $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);
    if ( $#attributes >= 0) {
      foreach my $attribute (@attributes) {
	my $text = format_attribute($attribute, $item);
	  print $categoryattributes{$item} . "{" . $text . "}\n";

      }
    }
  }
#-----------------------------------------------------------------------
# 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!
    for (my $index = 0; $index <= $#example ; $index++) {
      if ($details[$index]) {
	if ($saveblock =~ /^STRUCT_SHEET$/i) {
	  my $text = $details[$index];
	  $text =~ s/^ *\n//mg;
	  $text =~ s/(Example [0-9] - [a-zA-Z -]*)/$1.}{\n\\begin{verbatim}/;
	  print $categoryattributes{"_category_examples.detail"} . "{" .
	    $text . " \\end{verbatim}}%\n";
	} else {
	  $details[$index] =~ s/^\n *//;
	  $details[$index] =~ s/ - / -- /g;

	  print $categoryattributes{"_category_examples.detail"} . "{" .
	    format_attribute($details[$index],"_category_examples.detail") .
	      "}%\n";
	}
      }
      print $categoryattributes{"_category_examples.case"} . "{" .
	format_attribute($example[$index], "_category_examples.case") . "}%\n";
    }
  }

  print "\\end{boxedcifdefblock}\n";
  # print "\\bigskip\n";
  print "\\vskip\\interblockskip\n";
  return;
}

########################################################################
# Output LaTeX formatting for an item
########################################################################
######################## DDL1 ##########################################
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;
  $blockname =~ s/\//=over=/g;

  my %itemattributes = (
    # "_item.mandatory_code"=>"\\itemmandatorycode",
    "_name"=>"\\itemname",
    "_category"=>"\\itemcategoryid",
    "_type"=>"\\itemtypecode",
    "_type_conditions"=>"\\itemtypeconditionscode",
    "_definition"=>"\\itemdescriptiondescription",
    # "_item_linked.child_name"=>"\\itemlinkedchildname",
    "_list"=>"\\Dlistmember",
    "_list_reference"=>"\\Dlistreference",
    "_list_level"=>"\\Dlistlevel",
    "_list_mandatory"=>"\\Dlistmandatory",
    "_list_link_child"=>"\\Dlistlinkchild",
    "_list_link_parent"=>"\\Dlistlinkparent",
    "_enumeration_range"=>"\\itemrange",
    "_related_item"=>"\\itemrelatedrelatedname",
    "_related_function"=>"\\itemrelatedfunctioncode",
    "_enumeration"=>"\\itemenumerationvalue",
    "_enumeration_detail"=>"\\itemenumerationdetail",
    "_enumeration_default"=>"\\itemenumerationdefault",
    "_example"=>"\\itemexamplescase",
    "_example_detail"=>"\\itemexamplesdetail",
  );

  print "\n%--------------------------------------------------------\n";
  print "% " . $itemname . "\n";
  print "%--------------------------------------------------------\n";
  print "\\begin{cifdefblock}\n";
  print "\\noindent\\DATA{" . lc(texify($itemname)) . "}\\par\n";
  my $cifdicrhead ;
  ($cifdicrhead = uc($catname) ) =~ s/^_*//;
  if ($dictname =~ /^cif_pd\.dic$/ ) {     # Powder dictionary is ordered oddly
    $cifdicrhead = $itemname ;
  }
  $cifdicrhead =~ s/\..*//;
  $cifdicrhead =~ s/_/\\_/g;
  $cifdicrhead =~ s/\%/\\\%/g;
  print "\\gdef\\cifdicrhead{$cifdicrhead}\n";

#-----------------------------------------------------------------------
# # Name
    foreach my $item (
		      "_name",
		     ) {
      my @attributes = $dblock->get_item_data(-item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }

#-----------------------------------------------------------------------
# Type
    foreach my $item ( # specifies ordering of output items
		      # "_item.mandatory_code",
		      "_type_conditions",
		      "_type",
		     ) {
      my @attributes = $dblock->get_item_data(-item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }
#-----------------------------------------------------------------------
# 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) { # only do the next bit if there are any!
    for (my $index = 0; $index <= $#relname; $index++) {
      next unless ($relcode[$index] =~ /^replace$/);
      print "\\Dreplacedby{" .
	format_attribute($relname[$index], "_related_item") .
	  "}%\n";
    }
  }

#-----------------------------------------------------------------------
# Text definition block
  my $item = "_definition";
  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];

  # First run: dump all the definition blocks
  if ($dump == 1) {
    my $text = format_attribute($definition, $item);
    open (DEFINITION, "> ". $workdir . "/" . $blockname);
    print DEFINITION $itemattributes{$item}. "{";
    print DEFINITION $text;
    print DEFINITION "}%\n";
    close DEFINITION;
    print $itemattributes{$item} . "{" . $text . "}%\n";
  } else {
    # If there is a cached definition, use it
    if (-s $cachedir . "/" . $blockname ) {
      if ($verbose) { print STDERR "Using cached entry " . $blockname . "\n"; }
      open (DEFINITION, "< " . $cachedir . "/" . $blockname);
      while (<DEFINITION>) { print; }
      close(DEFINITION);
    } else {
      my $text = format_attribute($definition, $item);
      print $itemattributes{$item} . "{" . $text . "}%\n";
    }
  }

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

  print "\\raggedright\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) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }
#-----------------------------------------------------------------------
# List link children
  my @children =  $dblock->get_item_data(-item=>"_list_link_child");

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

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

      print "\\Dlistlinkchild{" . $child . "}\%\n";
    }
  }

#-----------------------------------------------------------------------
# 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/^:$/-\\infty\\rightarrow\\infty/g;
      $range =~ s/:$/\\rightarrow\\infty/g;
      $range =~ s/^:/-\\infty\\rightarrow /g;
      $range =~ s/:/\\rightarrow /g;

      print "\\itemrange{\$" . $range . "\$}\%\n";

      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 "\\def\\rihead{\\fontsize{7}{10}\\selectfont \\par\\noindent Related item: }\n";
      print "\\def\\rihead{\\ifneedpoint.\\needpointfalse\\fi\\fontsize{7}{10}\\selectfont \\par\\noindent Related item: }%\n";
    } else {
      # print "\\def\\rihead{\\fontsize{7}{10}\\selectfont \\par\\noindent Related items:\\par}\n";
      print "\\def\\rihead{\\ifneedpoint.\\needpointfalse\\fi\\fontsize{7}{10}\\selectfont \\par\\noindent Related items:\\par}%\n";
    }
    for (my $index = 0; $index <= $#relname; $index++) {
      next if ($relcode[$index] =~ /^replace$/);
      print $itemattributes{"_related_item"} . "{" .
	format_attribute($relname[$index], "_related_item") .
	  "}%\n";
      print $itemattributes{"_related_function"} . "{" .
	format_attribute($relcode[$index],"_related_function") .
	  "}%\n";
    }
  }

#-----------------------------------------------------------------------
# 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!
    if ($blockname =~ /^diffrn_source_target$/ ) {
      print "\\fontsize{7}{10}\\selectfont\\par
             The data value must be one of the following: \\par";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' ' ; }
	print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{" . $symbol . ' ' . "}}%\n";
	$symctr++;
	if ($symctr > 14) {
	  print "\\par\\noindent\n";
	  $symctr = 0;
	}
      }
    }  elsif  ($blockname =~ /^atom_local_axes_ax[12]$/ ) {
      print "\\fontsize{7}{10}\\selectfont\\par
             The data value must be one of the following: \\par";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Za-z]$/ ) { $symbol = ' ' . $symbol ; }
	print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{" . $symbol . ' ' . "}}%\n";
	$symctr++;
	if ($symctr > 14) {
	  print "\\par\\noindent\n";
	  $symctr = 0;
	}
      }
    }  else {
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $text = '';
	print $itemattributes{"_enumeration"} . "{" .
	  format_attribute($enumval[$index], "_enumeration" ) .
	    "}%\n";
	if ($enumdtl[$index]) {
	  print $itemattributes{"_enumeration_detail"} . "{" .
	    format_attribute($enumdtl[$index],"_enumeration_detail") .
	      "}%\n";
	}
      }
    }
  }
#-----------------------------------------------------------------------
# Enumeration default
    foreach my $item (
		      "_enumeration_default",
		     ) {
      my @attributes = $dblock->get_item_data(-item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }

#-----------------------------------------------------------------------
# Examples
  my @example = $dblock->get_item_data(-item=>"_example");
  my @details = $dblock->get_item_data(-item=>"_example_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 "\\def\\eghead{\\fontsize{7}{10}\\selectfont\\par\\noindent Example: }%\n";
    } else {
      print "\\def\\eghead{\\fontsize{7}{10}\\selectfont\\par\\noindent Examples: }%\n";
    }
    for (my $index = 0; $index <= $#example ; $index++) {
      my $text = '';
      if ($example[$index] =~ /\n/) { $text = 'text'; }
      print $itemattributes{"_example"} . $text . "{" .
	format_attribute($example[$index], "_example" . $text ) .
	  "}%\n";
      if ($details[$index]) {
	print $itemattributes{"_example_detail"} . "{" .
	  format_attribute($details[$index],"_example_detail") .
	    "}%\n";
      }
    }
  }

#-----------------------------------------------------------------------
foreach my $item ( # specifies ordering of output items
    "_category",
 		   ) {
    my @attributes = $dblock->get_item_data(-item=>$item);
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
    }
  }

  print "\\end{cifdefblock}\n";
  print "\\vskip\\interblockskip\n";
  return;
}

######################## DDL2 ##########################################
sub format_item2 {
  my $saveblock = shift;
  my $multiple = shift || '';

  my %itemattributes = (
    "_item.mandatory_code"=>"\\itemmandatorycode",
    "_item.name"=>"\\itemname",
    "_item.category_id"=>"\\itemcategoryid",
    "_item_type.code"=>"\\itemtypecode",
    "_item_type_conditions.code"=>"\\itemtypeconditionscode",
    "_item_aliases.alias_name"=>"\\itemaliasesaliasname",
    "_item_aliases.dictionary"=>"\\itemaliasesdictionary",
    "_item_aliases.version"=>"\\itemaliasesversion",
    "_item_description.description"=>"\\itemdescriptiondescription",
    "_item_linked.child_name"=>"\\itemlinkedchildname",
    "_item_range.minimum"=>"\\itemrangeminimum",
    "_item_range.maximum"=>"\\itemrangemaximum",
    "_item_related.related_name"=>"\\itemrelatedrelatedname",
    "_item_related.function_code"=>"\\itemrelatedfunctioncode",
    "_item_enumeration.value"=>"\\itemenumerationvalue",
    "_item_enumeration.detail"=>"\\itemenumerationdetail",
    "_item_default.value"=>"\\itemenumerationdefault",
    "_item_examples.case"=>"\\itemexamplescase",
    "_item_examples.detail"=>"\\itemexamplesdetail",
  );

  print "\n%--------------------------------------------------------\n";
  print "% " . $saveblock . "\n";
  print "%--------------------------------------------------------\n";
  print "\\begin{cifdefblock}\n";
  print "\\noindent\\DATA{" . lc(texify($saveblock)) . "}\\par\n";
  my $cifdicrhead ;
  ($cifdicrhead = uc($saveblock) ) =~ s/^_*//;
  $cifdicrhead =~ s/\..*//;
  $cifdicrhead =~ s/_/\\_/g;
  print "\\gdef\\cifdicrhead{$cifdicrhead}\n";

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

#-----------------------------------------------------------------------
# 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!
    for (my $index = 0; $index <= $#aliasname; $index++) {
      print $itemattributes{"_item_aliases.alias_name"} . "{" .
	format_attribute($aliasname[$index], "_item_aliases.alias_name") .
	  "}%\n";
      if ($aliasvers[$index]) {
	print $itemattributes{"_item_aliases.version"} . "{" .
	  format_attribute($aliasvers[$index],"_item_aliases.version") .
	    "}%\n"};
      if ($aliasdict[$index]) {
	print $itemattributes{"_item_aliases.dictionary"} . "{" .
	  format_attribute($aliasdict[$index],"_item_aliases.dictionary") .
	    "}%\n"};
    }
  }

#-----------------------------------------------------------------------
# Text definition block
  my $item = "_item_description.description";
  my @definitions =  $dictionary->get_item_data(-save=>$saveblock,
						-item=>$item);

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

  my $definition = $definitions[0];

  # First run: dump all the definition blocks
  if ($dump == 1) {
    my $text = format_attribute($definition, $item);
    open (DEFINITION, "> ". $workdir . "/" . $saveblock);
    print DEFINITION $itemattributes{$item}. "{";
    print DEFINITION $text;
    print DEFINITION "}%\n";
    close DEFINITION;
    print $itemattributes{$item} . "{" . $text . "}%\n";
  } else {
    # If there is a cached definition, use it
    if (-s $cachedir . "/" . $saveblock ) {
      if ($verbose) { print STDERR "Using cached entry " . $saveblock . "\n"; }
      open (DEFINITION, "< " . $cachedir . "/" . $saveblock);
      while (<DEFINITION>) { print; }
      close(DEFINITION);
    } else {
      my $text = format_attribute($definition, $item);
      print $itemattributes{$item} . "{" . $text . "}%\n";
    }
  }

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

  print "\\raggedright\n";

#-----------------------------------------------------------------------
# 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 $itemattributes{"_item_linked.child_name"} . "{" .
	format_attribute($childname[$index], "_item_linked.child_name") .
	  "}%\n";
    }
  }
}

#-----------------------------------------------------------------------
# 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 = '-\\infty';
	$floor = 0;
	if ($max eq '.' ){
	  $high = '\\infty';
	  $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 = '\\infty';
	  $ceil = 0;
	}
      }
      if ($index > 2) { print "% WARNING! More than three range values\n"; }
    }
    print "\\itemrange{\$";
    if ($floor) { print "["; } else { print "("; }
    print $low . ", " . $high;
    if ($ceil) { print "]"; } else { print ")"; }
    print "\$}%\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 "\\def\\rihead{\\fontsize{7}{10}\\selectfont \\par\\noindent Related item: }\n";
    } else {
      print "\\def\\rihead{\\fontsize{7}{10}\\selectfont \\par\\noindent Related items: }\n";
    }
    for (my $index = 0; $index <= $#relname; $index++) {
      print $itemattributes{"_item_related.related_name"} . "{" .
	format_attribute($relname[$index], "_item_related.related_name") .
	  "}%\n";
      print $itemattributes{"_item_related.function_code"} . "{" .
	format_attribute($relcode[$index],"_item_related.function_code") .
	  "}%\n";
    }
  }

#-----------------------------------------------------------------------
# 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!
    if ($saveblock =~ /^_diffrn.source\.target$/ ) {
      $symctr = 0;
      print "\\fontsize{7}{10}\\selectfont\\par
             The data value must be one of the following: \\par";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' '; }
	print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{" . $symbol . ' ' . "}}%\n";
	$symctr++;
	if ($symctr > 14) {
	  print "\\par\\noindent\n";
	  $symctr = 0;
	}
      }
    } elsif ($saveblock =~ /^_space_group\.name_Schoenflies$/ ) {
      $symctr = 0;
      print "\\fontsize{7}{10}\\selectfont\\par
             The data value must be one of the following: \\par";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = sprintf("%-6s", $enumval[$index]);
	print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{" . $symbol . ' ' . "}}%\n";
	$symctr++;
	if ($symctr > 6) {
	  print "\\par\\noindent\n";
	  $symctr = 0;
	}
      }
    } elsif ($saveblock =~ /^_space_group_Wyckoff\.letter$/ ) {
      $symctr = 0;
      print "\\fontsize{7}{10}\\selectfont\\par
             The data value must be one of the following: \\par";
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $symbol = $enumval[$index];
	if ($symbol =~ /^[A-Z]$/ ) { $symbol .= ' '; }
	print "{\\obeyspaces\\fontsize{9}{10pt}\\selectfont\\texttt{" . $symbol . ' ' . "}}%\n";
	$symctr++;
	if ($symctr > 14) {
	  print "\\par\\noindent\n";
	  $symctr = 0;
	}
      }
    } 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 {
      for (my $index = 0; $index <= $#enumval ; $index++) {
	my $text = '';
	my $symbol = $enumval[$index];
	if ($symbol =~ / /) { $symbol = "'" . $symbol . "'" ; }
	print $itemattributes{"_item_enumeration.value"} . "{" .
	  format_attribute($symbol, "_item_enumeration.value" ) .
	    "}%\n";
	if ($enumdtl[$index]) {
	  print $itemattributes{"_item_enumeration.detail"} . "{" .
	    format_attribute($enumdtl[$index],"_item_enumeration.detail") .
	      "}%\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) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }

#-----------------------------------------------------------------------
# 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 "\\def\\eghead{\\fontsize{7}{10}\\selectfont\\par\\noindent Example: }\n";
    } else {
      print "\\def\\eghead{\\fontsize{7}{10}\\selectfont\\par\\noindent Examples: }\n";
    }
    for (my $index = 0; $index <= $#example ; $index++) {
      my $text = '';
      if ($example[$index] =~ /\n/) { $text = 'text'; }
      print $itemattributes{"_item_examples.case"} . $text . "{" .
	format_attribute($example[$index], "_item_examples.case" . $text ) .
	  "}%\n";
      if ($details[$index]) {
	print $itemattributes{"_item_examples.detail"} . "{" .
	  format_attribute($details[$index],"_item_examples.detail") .
	    "}%\n";
      }
    }
  }

#-----------------------------------------------------------------------
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
      my $text = format_attribute($attributes[0], $item);
      print $itemattributes{$item} . "{" . $text . "}%\n";
    } else {
      if ( $#attributes >= 0) {
	foreach my $attribute (@attributes) {
	  my $text = format_attribute($attribute, $item);
	  print $itemattributes{$item} . "{" . $text . "}%\n";
	}
      }
    }
  }

  print "\\end{cifdefblock}\n";
  print "\\vskip\\interblockskip\n";
  return;
}

########################################################################
# Format individual attribute text (modifications depend on the
# type of item)
########################################################################
sub format_attribute {

  my $attribute = shift;
  my $item = shift;
  my $modified_attribute = '';

  if ($item =~ /^_category.id$/) {
    ($modified_attribute = $attribute) =~ s/_/\\_/g ;

    # _category_group_list.id
  } elsif ($item =~ /^_category_group_list.id$/ ) {
    ($modified_attribute = $attribute) =~ s/_/\\_/g ;

  # _category.description, _item_description.description
  } elsif ($item =~ /^_(category|item_description).description$/ 
	  || $item =~ /^_definition$/) {
    # trim leading whitespace on every line
    ($modified_attribute = $attribute) =~ s/^  *//mg ;
    # markup cif data names and category names
    $modified_attribute =~ s/\b(_[^ \t\n]*)\b/\\cifname{\1}/g ;
    $modified_attribute =~ s/\b([A-Z0-9]*_[A-Z_]*)\b/\\cifcategory{\1}/g ;
    # handle subscripts and superscripts
    $modified_attribute =~ s/~([a-zA-Z][a-zA-Z\.]+)~/\$_\\mathrm{\1}\$/g ;
    $modified_attribute =~ s/~([a-zA-Z])~/\$_{\1}\$/g ;
    $modified_attribute =~ s/~([^a-zA-Z~\.]*)~/\$_{\1}\$/g ;
    $modified_attribute =~ s/\^([a-zA-Z][a-zA-Z\.]+)\^/\$^\\mathrm{\1}\$/g ;
    $modified_attribute =~ s/\^([a-zA-Z])\^/\$^{\1}\$/g ;
    $modified_attribute =~ s/\^([^a-zA-Z^\.]*)\^/\$^{\1}\$/g ;
    $modified_attribute =~ s/\&/\\\&/g;
    # Torsion etc angles
    $modified_attribute =~ s/([A-Z])([0-9]'*)*-([A-Z])([0-9]'*)*-([A-Z])([0-9]*')*-([A-Z])([0-9]*')*/$1\$$2\{\}\$---$3\$$4\{\}\$---$5\$$6\{\}\$---$7\$$8\{\}\$/g;
    $modified_attribute =~ s/'([a-zA-Z0-9 +-_\\{}]+)'/`$1'/g;
    $modified_attribute =~ s/"([a-zA-Z0-9 +-_\\{}]+)"/`$1'/g;
    $modified_attribute =~ s/'(\.)'/`$1'/g;
    $modified_attribute =~ s/'(,)'/`$1'/g;
    $modified_attribute =~ s/([^_])(database)_2/\1\2\\_2/gi;
    $modified_attribute =~ s/\bATOM\b/\\cifcategory{ATOM}/g;
    $modified_attribute =~ s/\bAXIS\b/\\cifcategory{AXIS}/g;
    $modified_attribute =~ s/\bAUDIT\b/\\cifcategory{AUDIT}/g;
    $modified_attribute =~ s/\bCELL\b/\\cifcategory{CELL}/g;
    $modified_attribute =~ s/\bCHEMICAL\b/\\cifcategory{CHEMICAL}/g;
    $modified_attribute =~ s/\bCITATION\b/\\cifcategory{CITATION}/g;
    $modified_attribute =~ s/\bCOMPUTING\b/\\cifcategory{COMPUTING}/g;
    $modified_attribute =~ s/\bDATABASE\b/\\cifcategory{DATABASE}/g;
    $modified_attribute =~ s/\bDATABASE_2\b/\\cifcategory{DATABASE_2}/g;
    $modified_attribute =~ s/\bDIFFRN\b/\\cifcategory{DIFFRN}/g;
    $modified_attribute =~ s/\bENTITY\b/\\cifcategory{ENTITY}/g;
    $modified_attribute =~ s/\bENTRY\b/\\cifcategory{ENTRY}/g;
    $modified_attribute =~ s/\bEXPTL\b/\\cifcategory{EXPTL}/g;
    $modified_attribute =~ s/\bGEOM\b/\\cifcategory{GEOM}/g;
    $modified_attribute =~ s/\bJOURNAL\b/\\cifcategory{JOURNAL}/g;
    $modified_attribute =~ s/\bPHASING\b/\\cifcategory{PHASING}/g;
    $modified_attribute =~ s/\bPUBL\b/\\cifcategory{PUBL}/g;
    $modified_attribute =~ s/\bREFINE\b/\\cifcategory{REFINE}/g;
    $modified_attribute =~ s/\bREFLN\b/\\cifcategory{REFLN}/g;
    $modified_attribute =~ s/\bREFLNS\b/\\cifcategory{REFLNS}/g;
    $modified_attribute =~ s/\bSOFTWARE\b/\\cifcategory{SOFTWARE}/g;
    $modified_attribute =~ s/\bSTRUCT\b/\\cifcategory{STRUCT}/g;
    $modified_attribute =~ s/\bSYMMETRY\b/\\cifcategory{SYMMETRY}/g;
    $modified_attribute =~ s/CBF_CANONICAL/CBF\\_CANONICAL/g;
    $modified_attribute =~ s/CBF_PACKED/CBF\\_PACKED/g;
    $modified_attribute =~ s/angstrom/\\aa{}ngstr\\"om/g;
    $modified_attribute =~ s/Angstrom/\\AA{}ngstr\\"om/g;
    $modified_attribute =~ s/(e\.g\.)/\\textit{$1}/g;
    $modified_attribute =~ s/(i\.e\.)/\\textit{$1}/g;
    $modified_attribute =~ s/\bvia\b/\\textit{via}/g;
    $modified_attribute =~ s/etc\./\\textit{etc.}/g;
    # Some rather specific substitutions
    $modified_attribute =~ s/\(sin theta\)\/lambda/\$(\\sin\\theta)\/\\lambda\$/g;
    $modified_attribute =~ s/[^\/]\blambda/\$\\lambda\$/g;
    $modified_attribute =~ s/\bsigma\b/\$\\sigma\$/g;
    $modified_attribute =~ s/(Miller index )([hkl]) /$1\$$2\$ /g;
    $modified_attribute =~ s/[Tt]heta angle/\$\\theta\$ angle/g;
    $modified_attribute =~ s/\bd value/\$d\$ value/g;
    $modified_attribute =~ s/\bR factor/\$R\$ factor/g;
    $modified_attribute =~ s/isotropic B\b/isotropic \$B\$/g;
    $modified_attribute =~ s/value of ([FI])\b/value of \$$1\$/g;
    $modified_attribute =~ s/value of (sigma)\(([FI])\)/value of \$\\$1($2)\$/g;
    $modified_attribute =~ s/([^\\])\b(alpha|beta|gamma|delta|chi|psi|kappa|omega|theta|zeta)\b/$1\$\\$2\$/g;
    $modified_attribute =~ s/([^\\])\b(epsilon|phi)\b/$1\$\\var$2\$/g;
    $modified_attribute =~ s/([^\\])\b(nu|tau)([0-9])\b/$1\$\\$2_$3\$/g;
    $modified_attribute =~ s/\b([xyz]) (axis|coordinates?|components?)\b/\$$1\$ $2/g;
    $modified_attribute =~ s/\b(Chemical Abstracts Service)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(Chemical Abstracts)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(International Tables for Crystallography)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(Space-group symmetry)(  *\(2002\))/\\textit{$1}$2/g;
    $modified_attribute =~ s/([^{])\b(International Tables)/$1\\textit{$2}/g;
    $modified_attribute =~ s/\b(Nomenclature of Inorganic Chemistry)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(J\. Mol\. Biol.)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(yyyy-mm-dd)/\\textit{$1}/g;
    $modified_attribute =~ s/\b3x3 (matrix|matrices)\b/3 \$\\times\$ 3 $1/g;
    # Insert an explicit paragraph indent where there are blank lines
    # $modified_attribute =~ s/^ *$/\n\\quad /mg;
    $modified_attribute =~ s/^ *$/\% /mg;
    $modified_attribute =~ s/\bReference:/\\par\\quad Reference:/mg;
    $modified_attribute =~ s/\bRef:/\\par\\quad Reference:/mg;
    $modified_attribute =~ s/\*\** *(This data item would not.*block\.) *\*\**/\\par\\quad\\textit{Note:} $1/msg;
    $modified_attribute =~ s/\*(if *\n one did not know its structure)\*/\textit{$1}/msg;
    $modified_attribute =~ s/'-1'/`-1'/g;
    $modified_attribute =~ s/\+\/-([XYZ])/\$\\pm $1\$/g;
    $modified_attribute =~ s/(torsion angle of) -60/$1 \$-60\$/g;
    $modified_attribute =~ s/(60 degree angle)/\$60^{\\circ}\$ angle/g;
    $modified_attribute =~ s/\b(DEPRECATED -- DO NOT USE)\b/\\emph{Deprecated: do not use.}/g;
    $modified_attribute =~ s/\b(NOT)\b/\\emph{not}/g;
    $modified_attribute =~ s/\b(ONLY)\b/\\emph{only}/g;
    $modified_attribute =~ s/\b(MUST)\b/\\emph{must}/g;
    $modified_attribute =~ s/\b(FULL)\b/\\emph{full}/g;
    $modified_attribute =~ s/TeX\b/\\TeX/g;
    $modified_attribute =~ s/\b(site[0-9])-(site[0-9])\b/$1--$2/g;
    $modified_attribute =~ s/(Experientia)/\\textit{$1}/g;
    $modified_attribute =~ s/lysozyme-FAB/lysozyme--FAB/g;
    $modified_attribute =~ s/\b(p\([oc]\))/\$$1\$/g;
    $modified_attribute =~ s/\b(cis)\b/\\textit{$1}/g;
    # The next set are primarily for the cif_sym.dic
    $modified_attribute =~ s/Hermann-Mauguin/Hermann--Mauguin/g;
    $modified_attribute =~ s/\b([om][ABCS])\b/\$$1\$/g;
    $modified_attribute =~ s/\b([h][R])\b/\$$1\$/g;
    $modified_attribute =~ s/\b(IT)\b/\\textit{$1}/g;
    # The next set are primarily for the cif_sym.dic
    $modified_attribute =~ s/\b(scaling|sqrt|logarithmic)_(offset|scaled)\b/$1\\_$2/g;
    $modified_attribute =~ s/\b([XYZ])[ -](axis)/\$$1\$ $2/g;
    $modified_attribute =~ s/\b(Z)\b/\\textit{$1}/g;
    # The next set are primarily for the cif_core.dic
    $modified_attribute =~ s/C233__ggg/C233\\_\\_ggg/g;
    $modified_attribute =~ s/(\(x,y\))/\$$1\$/g;
    $modified_attribute =~ s/cif_sym/cif\\_sym/g;
    $modified_attribute =~ s/cif_pd/cif\\_pd/g;
    # The next set are primarily for the cif_pd.dic
    $modified_attribute =~ s/\b\\q\b/\$\\theta\$/g;
    $modified_attribute =~ s/\b\\a\b/\$\\alpha\$/g;
    $modified_attribute =~ s/\b\\m\b/\$\\mu\$/g;
    $modified_attribute =~ s/\bQ\b/\$Q\$/g;
    $modified_attribute =~ s/\b(d)([- ]spac)/\$$1\$$2/g;
    $modified_attribute =~ s/\b(LAZY\/PULVERIX)\b/\\textit{$1}/g;
    $modified_attribute =~ s/(blue|bluish)_(green)/$1\\_$2/g;
    $modified_attribute =~ s/(reddish)_(orange)_(metallic)/$1\\_$2\\_$3/g;
    $modified_attribute =~ s/\b(loop_)\b/\\cifname{$1}/g;
    $modified_attribute =~ s/\b(Introduction to X-ray Powder Diffraction)/\\textit{$1}/g;
    $modified_attribute =~ s/\b(hkl)/\$$1\$/g;
    # The next set are primarily for the cif_ms.dic
    $modified_attribute =~ s/\b(JANA)/\\textit{$1}/g;
    $modified_attribute =~ s/<(string)>/\$<\$\\textit{$1}\$>\$/g;
    $modified_attribute =~ s/\\cifname{\\cifcategory{_(REFRNCE|MOD)}}/\\_$1/g;
    # The next set are primarily for the cif_rho.dic
    $modified_attribute =~ s/ -> /\$\\rightarrow\$/g;
    $modified_attribute =~ s/MOSFLM/\\textit{MOSFLM}/mg;
    # The next set are primarily for the mif_core.dic
    $modified_attribute =~ s/cas_([0-9]*)ci/cas\\_$1ci/mg;
    $modified_attribute =~ s/mif_core_(colours|molecules|bonds)/mif\\_core\\_$1/mg;
    $modified_attribute =~ s/(with a )\$( character)/$1\\\$$2/mg;
    $modified_attribute =~ s/(Angew. Chem\.)/\\textit{$1}/mg;
    $modified_attribute =~ s/(Int\. Ed\. Engl\. )(21)(, 567)-(583)/\\textit{$1}\\textbf{$2}$3--$4/mg;
    $modified_attribute =~ s/(J\. Chem\..*Sci\. )([0-9]+)(, [0-9]+)-([0-9]+)/\\textit{$1}\\textbf{$2}$3--$4/mg;
    $modified_attribute =~ s/\b(ALLENE)\b/\\emph{allene}/g;
    $modified_attribute =~ s/\b(Cahn)-(Ingold)-(Prelog)\b/$1--$2--$3/g;

  # _category_examples.detail
  } elsif ($item =~ /^_category_examples.detail$/) {
    ($modified_attribute = $attribute) =~ s/^  *//mg ;
    $modified_attribute =~ s/\&/\\\&/g;
    $modified_attribute =~ s/CBF_CANONICAL/CBF\\_CANONICAL/g;
    $modified_attribute =~ s/CBF_PACKED/CBF\\_PACKED/g;
    $modified_attribute =~ s/\b([A-Z0-9]+_[A-Z_]*)\b/\\cifcategory{$1}/g ;
    $modified_attribute =~ s/~([^a-zA-Z~]*)~/\$_{\1}\$/g ;
    $modified_attribute =~ s/~([a-zA-Z][a-zA-Z]+)~/\$_\\mathrm{$1}\$/g ;
    $modified_attribute =~ s/~([a-zA-Z])~/\$_{\1}\$/g ;
    $modified_attribute =~ s/\^([^a-zA-Z^]*)\^/\$^{\1}\$/g ;
    $modified_attribute =~ s/\^([a-zA-Z][a-zA-Z]+)\^/\$^\\mathrm{$1}\$/g ;
    $modified_attribute =~ s/\^([a-zA-Z])\^/\$^{\1}\$/g ;
    # The next set are primarily for the cif_sym.dic
    $modified_attribute =~ s/(SPACE_GROUP_SYMOP)/\\cifcategory{$1}/g;
    $modified_attribute =~ s/F d -3 c/\$Fd\\bar3c\$/g;
    $modified_attribute =~ s/(\(x,y,z,\))/\$$1\$/g;
    # The next set are primarily for the cif_img.dic
    # markup cif data names and category names
    $modified_attribute =~ s/\s(_[^ \t\n]*)\s/ \\cifname{$1} /msg ;
  # $modified_attribute =~ s/([^\$\\])_/$1\\_/g;
    $modified_attribute =~ s/array_1/array\\_1/g;
    $modified_attribute =~ s/(array|binary)_id/$1\\_id/g;
    $modified_attribute =~ s/\b\\q\b/\$\\theta\$/g;
    $modified_attribute =~ s/'([a-zA-Z0-9 +-_\\{}\.|]+)'/`$1'/g;
    $modified_attribute =~ s/\|/\$\\vert\$/g;
    $modified_attribute =~ s/MOSFLM/\\textit{MOSFLM}/mg;
    # The next set are primarily for the cif_sym.dic
    $modified_attribute =~ s/\b(ATOM)\\_(SITES*)\\_(DISPLACE|OCC|ROT|U)\\_(FOURIER)/$1_$2_$3_$4/g;

  # _category_examples.case
  } elsif ($item =~ /^_category_examples.case$/) {
    chop($attribute);
    $modified_attribute = "\\begin{ciflisting}\\begin{scriptsize}" . $attribute . " \\end{scriptsize}\\end{ciflisting}\n";
    $modified_attribute =~ s/\$/\\\$/mg;
    $modified_attribute =~ s/\\/\$\\backslash\$/mg;
    $modified_attribute =~ s/\$\\backslash\$(begin|end{)/\\$1/mg;
    $modified_attribute =~ s/\$\\backslash\$\$/\\\$/mg;
    $modified_attribute =~ s/#/\\#/mg;
    $modified_attribute =~ s/\%/\\%/mg;
    $modified_attribute =~ s/^    //mg;
    $modified_attribute =~ s/\~/\\~\{\}/mg;
    $modified_attribute =~ s/\^/\\^\{\}/mg;
    $modified_attribute .= "\\par\\smallskip\n";
    $modified_attribute =~ s/\&/\\\&/mg;

  # _item_examples.case
  } elsif ($item =~ /^_item_examples.case$/
           || $item =~ /^_example$/) {
    ($modified_attribute = $attribute) =~ s/^  *//mg ;
    $modified_attribute =~ s/\$/\\\$/mg;
    $modified_attribute =~ s/\\/\$\\backslash\$/mg;
    $modified_attribute =~ s/#/\\#/mg;
    $modified_attribute =~ s/\%/\\%/mg;
    $modified_attribute =~ s/_/\\_/mg;
    # $modified_attribute =~ s/\~/\\~\{\}/mg;
    $modified_attribute =~ s/\~/\\tilda\{\}/mg;
    # $modified_attribute =~ s/\^/\\^\{\}/mg;
    $modified_attribute =~ s/\^/\\carrot\{\}/mg;

  # _item_examples.detail
  } elsif ($item =~ /^_item_examples.detail$/
           || $item =~ /^_example_detail$/) {
    ($modified_attribute = $attribute) =~ s/^  *//mg ;
    $modified_attribute =~ s/7th symm. posn.; \+a on x; -b on y/7th symmetry position: \$+a\$ on \$x\$, \$-b\$ on \$y\$/g;
    $modified_attribute =~ s/P 42 21 2/\$P 4_2 2_1 2\$/mg;
    $modified_attribute =~ s/P n -3 n/\$P n \\bar3 n\$/mg;
    $modified_attribute =~ s/F d -3 c/\$F d \\bar3 c\$/mg;
    $modified_attribute =~ s/\b([26])b/$1\$b\$/mg;
    $modified_attribute =~ s/96f/96\$f\$/mg;
    $modified_attribute =~ s/\&/\\\&/mg;
    $modified_attribute =~ s/_date/\\_date/mg;

  # _item_examples.case/text (special voodoo)
  } elsif ($item =~ /^_item_examples.casetext$/
           || $item =~ /^_exampletext$/) {
    chomp($attribute);
    $modified_attribute = "\\begin{smallverbatim}" . "; " .  $attribute . "\n; " . "\\end{smallverbatim}\n";
    $modified_attribute =~ s/^  */  /mg;
    $modified_attribute =~ s/\\/\$\\backslash\$/mg;
    $modified_attribute =~ s/\$\\backslash\$(begin|end{smallverbatim})/\\$1/mg;
    $modified_attribute =~ s/^(\\begin{smallverbatim}; ) */\1/;
    $modified_attribute =~ s/#/\\#/mg;
    $modified_attribute =~ s/\%/\\%/mg;
    $modified_attribute =~ s/_/\\_/mg;
    $modified_attribute =~ s/\&/\\\&/mg;
    $modified_attribute =~ s/\~/\\~\{\}/smg;
    $modified_attribute =~ s/\^/\\^\{\}/smg;

  # _item_enumeration.detail
  } elsif ($item =~ /^_item_enumeration.detail$/
            || $item =~ /^_enumeration_detail$/) {
    ($modified_attribute = $attribute) =~ s/K\\a~1~/K\$\\alpha_1\$/mg ;
    $modified_attribute =~ s/K\\a~2~/K\$\\alpha_2\$/mg ;
    $modified_attribute =~ s/K\\b~1~/K\$\\beta\$/mg ;
    $modified_attribute =~ s/K-L~([0-9])~/K-L\$_{$1}\$/mg ;
    $modified_attribute =~ s/"no"/`no'/g ;
    $modified_attribute =~ s/"yes"/`yes'/g ;
    $modified_attribute =~ s/"calc"/`calc'/g ;
    $modified_attribute =~ s/\b(Acta [CE])/\\textit{$1}/g ;
    $modified_attribute =~ s/\b(Acta Crystallographica Section [CE])/\\textit{$1}/g ;
    $modified_attribute =~ s/\b(Phys. Rev. B)([0-9]+)/\\textit{$1}\\textbf{$2}/g ;
    $modified_attribute =~ s/\b(_[^ \t\n]*)\b/\\cifname{\1}/g ;
    $modified_attribute =~ s/\b([UB])\b/\$$1\$/;
    $modified_attribute =~ s/\b([UB])ij\b/\$$1^{ij}\$/;
    $modified_attribute =~ s/\b([UB])iso\b/\$$1_\\mathrm{iso}\$/;
    $modified_attribute =~ s/pi bond/\$\\pi\$ bond/g ;
    $modified_attribute =~ s/(absolute configuration) ([RS])/$1 \$$2\$/g ;
    $modified_attribute =~ s/(omega|theta)/\$\\$1\$/g ;
    $modified_attribute =~ s/Q-scan/\$Q\$-scan/g ;
    $modified_attribute =~ s/psi-scan/\$\\psi\$-scan/g ;
    $modified_attribute =~ s/delta-F/\$\\Delta F\$/g ;
    $modified_attribute =~ s/ls process/least-squares process/g ;
    $modified_attribute =~ s/\&/\\\&/mg;
    $modified_attribute =~ s/(along the )([abc])( axis)/$1\$\\mathbf{$2}\$$3/mg;
    $modified_attribute =~ s/(along an arbitrary )(a)([123])( axis)/$1\$\\mathbf{$2}_$3\$$4/mg;
    $modified_attribute =~ s/(around the )([abc])( axis)/$1\$\\mathbf{$2}\$$3/mg;
    $modified_attribute =~ s/(around an arbitrary )(a)([123])( axis)/$1\$\\mathbf{$2}_$3\$$4/mg;
    $modified_attribute =~ s/\b([UB])([123][123])\b/\$$1^{$2}\$/;
    $modified_attribute =~ s/\*(may)\*/\\textit{$1}/;

  # _item_enumeration.value
  } elsif ($item =~ /^_item_enumeration.value$/
            || $item =~ /^_enumeration$/) {
    $modified_attribute =~ s/\\/\$\\backslash\$/mg;
    ($modified_attribute = $attribute) =~ s/_/\\_/mg ;
    $modified_attribute =~ s/\~/\\~\{\}/mg;

  # _item.name
  } elsif ($item =~ /^_(item.)*name$/) {
    ($modified_attribute = $attribute) =~ s/\%/\\\%/mg ;

  # _item_related.related_code
  } elsif ($item =~ /^_item_related.function_code$/) {
    ($modified_attribute = $attribute) =~ s/_/ /mg ;

  # _item_aliases.alias_name
  } elsif ($item =~ /^_item_aliases.alias_name$/) {
    ($modified_attribute = $attribute) =~ s/\%/\\\%/mg ;

  # _item_aliases.dictionary
  } elsif ($item =~ /^_item_aliases.dictionary$/) {
    ($modified_attribute = $attribute) =~ s/_/\\_/g ;
    $modified_attribute =~ s/\%/\\\%/mg ;

  # Default - no specific transformation
  } else {
    $modified_attribute = $attribute;
  }

  return $modified_attribute;
}

# Protect characters special to TeX in individual strings
sub texify {
  my $string = shift;

  $string =~ s/\%/\\\%/mg;

  return $string;
}

# Special formatting for Schoenflies symbols
sub Schoenflies {
  my $string = shift;

  $string =~ s/^ *([A-Z])([a-z0-9]*)\./$1_{$2}./g;
  $string =~ s/\.([a-z0-9]*) *$/^{$1}/g;
  $string =~ s/(.*)/\$$1/g;

  return $string;
}

# Special formatting for Hermann-Mauguin symbols
sub HM {
  my $string = shift;
  my $element;
  my @HM;
  my $out = '';

  $string =~ s/  */ /g;
  @HM = split(/ /, $string);

  foreach $element (@HM) {
    $element =~ s/^([0-9])([0-9])/$1_{$2}/g;
    $element =~ s/^-([0-9])/\\bar{$1}/g;
    $element =~ s/:/{:}/g;
    $out .= $element . "\\,";
  }

  $out = "\$" . $out . "\$" ;

  return $out;
}
