#!/usr/bin/perl -w

# File        : csvtools.pl
# Author      : Dr Nicola Talbot
# Date        : 26 June 2007
# Description : Perl script to accompany csvtools.sty
#             : Allows you to substitute \CSVtotabular, \CSVtolongtable and \applyCSVfile commands with the appropriate LaTeX code
# Version     : 0.6b (1 September 2006).

# usage : csvtools <in-file> <out-file>

if ($#ARGV != 1)
{
   die "Syntax : $0 <in-file> <out-file>\n";
}

($FILENAME,$OUTPUT) = @ARGV;

open FILENAME or die "Can't open '$FILENAME'\n";

$ext = substr($FILENAME, -4);

if (($ext eq ".tex") or ($ext eq ".dtx") or ($ext eq ".ltx"))
{
   $LOGFILE =  substr($FILENAME,0,length($FILENAME)-4) . ".log";
}
else
{
   $LOGFILE = $FILENAME . ".log";
}

open LOGFILE or die "Can't open log file '$LOGFILE'.  Make sure you run LaTeX before using $0\n";

while (<LOGFILE>)
{
   if (/\\c@([a-zA-Z]+)=\\count/)
   {
      $counter{$1} = 0;
   }
}

$counter{'csvrownumber'} = 0;

close LOGFILE;

open OUTPUT, ">$OUTPUT" or die;

# global csvsort options

$globalsort{'verbose'}=1;
$globalsort{'sort'}='alphabetical ascending';
$globalsort{'variable'}='\field{1}';
$globalsort{'sfirstdataline'}=1;
$globalsort{'firstdataline'}=2;

$keyvaldefaults{'verbose'}='true';

$csvpieouterlabel="\\field{1}";
$csvpieinnerlabel="\\field{2}\\%";
$separator=",";

while (<FILENAME>)
{
   $restofline = $_;
   local($comment) = "";

   if ($restofline=~m/(\\*)%/)
   {
      $n = length($1);

      if ($n%2 == 0)
      {
         $restofline = "$`$1";
         $comment = "%$'";
         $_ = $restofline;
      }
   }

   if ($restofline=~/\\setcsvseparator\s*{([^}]*)}/)
   {
      $separator=$1 if defined($1);
   }

   if ($restofline=~/\\renewcommand\*?\s*{?\\csvpieinnerlabel}?/)
   {
      print OUTPUT $`;

      $restofline=$';
      $lineno=$.;

      ($csvpieinnerlabel,$restofline,$done)=&getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
            $restofline .= $nextline;
            ($csvpieinnerlabel,$restofline,$done)=&getnextgroup($restofline);
         }
         else
         {
            die "Unexpected EOF while parsing \\renewcommand on line $lineno\n";
         }
      }

      $_ = $restofline;
   }

   if ($restofline=~/\\renewcommand\*?\s*{?\\csvpieouterlabel}?/)
   {
      print OUTPUT $`;

      $restofline=$';
      $lineno=$.;

      ($csvpieouterlabel,$restofline,$done)=&getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
            $restofline .= $nextline;
            ($csvpieouterlabel,$restofline,$done)=&getnextgroup($restofline);
         }
         else
         {
            die "Unexpected EOF while parsing \\renewcommand on line $lineno\n";
         }
      }

      $_ = $restofline;
   }

   while ($restofline=~/\\stepcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = &getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      $counter{$ctr}++;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\\refstepcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = &getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      $counter{$ctr}++;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\\setcounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = &getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      ($num,$restofline,$done) = &getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($num,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning second argument to \\stepcounter on line $startline\n";
         }
      }

      $num=~s/\\value{(.+)}/$counter{$1}/;

      $counter{$ctr} = $num;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/\\addtocounter(.*)/)
   {
      $line = $_;

      ($ctr,$restofline,$done) = &getnextgroup($1);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\stepcounter on line $startline\n";
         }
      }

      ($num,$restofline,$done) = &getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($num,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning second argument to \\stepcounter on line $startline\n";
         }
      }

      $num=~s/\\value{(.+)}/$counter{$1}/;

      $counter{$ctr} = $counter{$ctr} + $num;

      $_ = $line;
   }

   $restofline = $_;

   while ($restofline=~/^(.*)\\csvGetEntry(.*)$/)
   {
      $start = $1;
      $restofline = $2;

      ($ctr,$restofline,$done) = &getnextgroup($restofline);

      $startline=$.;

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $restofline = $restofline . $nextline;

             ($ctr,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\csvGetEntry on line $startline\n";
         }
      }

      ($entry,$restofline,$done) = &getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $restofline = $restofline . $nextline;

             ($entry,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning first argument to \\csvGetEntry on line $startline\n";
         }
      }
      $_ = $start . "\\csname $entry\\roman{$ctr}\\endcsname" . $restofline;
      #$_ = $start. $data{$entry}[$counter{$ctr}] . $restofline;
   }

   $restofline = $_;

   while ($restofline=~/\\usepackage(.*)/)
   {
      $line = $_;

      $startline=$.;

      $restofline = $1;

      $options = '';

      if ($restofline=~/^\s*\[/)
      {
         ($options,$restofline,$done) = &getnextoptionalgroup($restofline);

         while (!$done)
         {
            if ($nextline = <FILENAME>)
            {
                $line = $line . $nextline;

                $restofline = $restofline . $nextline;

                ($options,$restofline,$done) = &getnextoptionalgroup($restofline);
            }
            else
            {
               die "EOF found whilst scanning optional argument to \\usepackage on line $startline\n";
            }
         }
      }


      ($package,$restofline,$done) = &getnextgroup($restofline);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

             $restofline = $restofline . $nextline;

             ($package,$restofline,$done) = &getnextgroup($restofline);
         }
         else
         {
            die "EOF found whilst scanning argument to \\usepackage on line $startline\n";
         }
      }

      if ($package eq 'csvsort' and $options)
      {
         %hash = &getKeyValues($options,%keyvaldefaults);

         foreach $key (keys(%hash))
         {
            $globalsort{$key}=$hash{$key};
         }
      }

      $_ = $line;
   }

   $restofline = $_;

   if (/^(.*)\\CSVtotabular(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $line = $2;
      $lineno = $.;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS filename on line $lineno\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line = $restofline;

      ($alignment,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($alignment,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS column alignment on line $lineno\n";
         }
      }

      $line = $restofline;

      ($FIRST,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($FIRST,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular third argument on line $lineno\n";
         }
      }

      $line = $restofline;

      ($MIDDLE,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($MIDDLE,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular fourth argument on line $lineno\n";
         }
      }

      $line = $restofline;

      ($LAST,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($LAST,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtotabular fifth argument on line $lineno\n";
         }
      }

      &csvtotabular($csvname, $alignment, $FIRST, $MIDDLE, $LAST,
                    "tabular");

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\CSVtolongtable(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $line = $2;
      $lineno = $.;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $line . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS filename on line $lineno\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line = $restofline;

      ($alignment,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($alignment,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for CVS column alignment on line $.\n";
         }
      }

      $line = $restofline;

      ($FIRST,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($FIRST,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable third argument on line $.\n";
         }
      }

      $line = $restofline;

      ($MIDDLE,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($MIDDLE,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable fourth argument on line $.\n";
         }
      }

      $line = $restofline;

      ($LAST,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline = <FILENAME>)
         {
             $line = $restofline . $nextline;

            ($LAST,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\CSVtolongtable fifth argument on line $.\n";
         }
      }

      &csvtotabular($csvname, $alignment, $FIRST, $MIDDLE, $LAST,
                    "longtable");

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\applyCSVfile(\*)?(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $starred = defined($2);

      $restofline = $3;
      $lineno = $.;

      $restofline = &eatinitialspaces($restofline);
      $restofline = &eatcomments($restofline);

      $startrow = $starred ? 1 : 2;

      if (/^\[([0-9]+)\](.*)/)
      {
         $startrow = $1;

         $restofline = $2;
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile*[$startrow] first argument on line $lineno\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line=$restofline;

      ($body,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($body,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\applyCSVfile*[$startrow] second argument on line $lineno\n";
         }
      }

      if ($starred)
      {
         &applyCSVfilestar($csvname, $body, $startrow);
      }
      else
      {
         &applyCSVfile($csvname, $body, $startrow);
      }

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\sortapplyCSVfile(\*)?(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $starred = defined($2);

      $restofline = $3;
      $lineno = $.;

      $restofline = &eatinitialspaces($restofline);
      $restofline = &eatcomments($restofline);

      $line=$restofline;

      $options = '';

      if ($restofline=~/^\s*\[/)
      {
         ($options,$restofline,$done) = &getnextoptionalgroup($line);

         while (!$done)
         {
            if ($nextline= <FILENAME>)
            {
               $line = $restofline . $nextline;

               ($options,$restofline,$done) = &getnextoptionalgroup($line);
            }
            else
            {
               die "EOF found whilst scanning for \\sortapplyCSVfile optional argument on line $lineno\n";
            }
         }
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortapplyCSVfile argument on line $lineno\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line=$restofline;

      ($body,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($body,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortapplyCSVfile argument on line $lineno\n";
         }
      }

      if ($starred)
      {
         &sortapplyCSVfilestar($csvname, $body, $options);
      }
      else
      {
         &sortapplyCSVfile($csvname, $body, $options);
      }

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\sortCSVto(tabular|longtable)(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $envname = $2;
      $restofline = $3;
      $lineno = $.;

      $restofline = &eatinitialspaces($restofline);
      $restofline = &eatcomments($restofline);

      $line=$restofline;

      $options = '';

      if ($restofline=~/^\s*\[/)
      {
         ($options,$restofline,$done) = &getnextoptionalgroup($line);

         while (!$done)
         {
            if ($nextline= <FILENAME>)
            {
               $line = $restofline . $nextline;

               ($options,$restofline,$done) = &getnextoptionalgroup($line);
            }
            else
            {
               die "EOF found whilst scanning for \\sortCSVto$envname first argument on line $lineno\n";
            }
         }
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortCSVto$envname argument on line $lineno\n";
         }
      }

      $csvname=~s/\\csname (.*)\\roman{(.*)}\\endcsname/$data{$1}[$counter{$2}]/;

      $line=$restofline;

      ($align,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($align,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortCSVto$envname argument on line $lineno\n";
         }
      }

      $line=$restofline;

      ($headrow,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($headrow,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortCSVto$envname argument on line $lineno\n";
         }
      }

      $line=$restofline;

      ($midrows,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($midrows,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortCSVto$envname argument on line $lineno\n";
         }
      }

      $line=$restofline;

      ($lastrow,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($lastrow,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\sortCSVto$envname argument on line $lineno\n";
         }
      }

      &sortCSVtotabular($csvname,$align,$headrow,$midrows,$lastrow,
         $envname,$options);

      print OUTPUT "$restofline\n";
   }
   elsif (/^(.*)\\csvpiechart(\*)?(.*)$/)
   {
      print OUTPUT "$1$comment";
      $comment="";

      $lineno = $.;

      $starred = defined($2);

      $restofline = $3;

      $restofline = &eatinitialspaces($restofline);
      $restofline = &eatcomments($restofline);

      $startrow= $starred ? 1 : 2;

      $line=$restofline;

      ($options,$restofline,$done) = &getnextoptionalgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($options,$restofline,$done) = &getnextoptionalgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\csvpiechart optional argument on line $lineno\n";
         }
      }

      $line=$restofline;

      ($label,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($label,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\csvpiechart second argument on line $lineno\n";
         }
      }

      $line=$restofline;

      ($csvname,$restofline,$done) = &getnextgroup($line);

      while (!$done)
      {
         if ($nextline= <FILENAME>)
         {
            $line = $restofline . $nextline;

            ($csvname,$restofline,$done) = &getnextgroup($line);
         }
         else
         {
            die "EOF found whilst scanning for \\csvpiechart second argument on line $lineno\n";
         }
      }

      print OUTPUT "\\csvpiesetkeys{$options}%\n";

      if ($options=~m/firstrow\s*=\s*(\d+)/)
      {
         $startrow=$1;
      }

      if ($starred)
      {
         &csvpiechartstar($label, $csvname, $startrow);
      }
      else
      {
         &csvpiechart($label, $csvname, $startrow);
      }

      print OUTPUT "$restofline\n";
   }
   else
   {
      print OUTPUT "$_$comment";
   }
}

sub doapplyCSV{
   local($csvrow, $body, @idx, %HoH) = @_;
   local($row, $entry);

   $counter{'csvrownumber'} = 0;
   print OUTPUT "\\setcounter{csvrownumber}{0}\\relax\n";

   foreach ($row=1; $row <= $csvrow; $row++)
   {
      print ".";

      local($THISROW) = $body;

      $THISROW = &csvSaveEntry($THISROW,$row,%HoH);

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $THISROW =~ s/\\insertbyname{$entry}/$replacementval/g;

          $THISROW =~ s/\\insert$entry/$replacementval/g;

          local($column) = $idx{$entry}+1;

          $THISROW =~ s/\\field{(\d+)}/$1eq$column?$replacementval:$&/emg;

          $THISROW = &ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      print OUTPUT "\\refstepcounter{csvrownumber}\\relax\n$THISROW";
      $counter{'csvrownumber'}++;
   }
}

sub doapplyCSVstar{
   local($body, $csvrow, $numcols, @entry) = @_;
   local($row,$column);

   print OUTPUT "\\setcounter{csvrownumber}{0}\\relax\n";
   $counter{'csvrownumber'} = 0;

   for ($row=0; $row < $csvrow; $row++)
   {
      print ".";

      $THISROW = $body;

      for ($column=0; $column < $numcols; $column++)
      {
          $replacementval=$entry[$row][$column];

          $THISROW =~ s/\\field{(\d+)}/$1eq$column+1?$replacementval:$&/emg;

          $THISROW = &ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      print OUTPUT "\\refstepcounter{csvrownumber}$THISROW";
      $counter{'csvrownumber'}++;
   }

}

sub getCSVvalues{
   local($CSVFILE, $startrow) = @_;
   local(@idx, %HoH, @fields);

   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   local ($csvrow)=0;

   while (<FH>)
   {
      if ($. == 1)
      {
         # get header row

         chop;

         @fields = &getEntries($_);

         local($numcols) = 0;

         foreach $field (@fields)
         {
            $idx{$field} = $numcols;

            $numcols++;
         }
      }
      elsif ($. >= $startrow)
      {
         next unless /$separator/;

         chop;

         $csvrow++;
         $rec = {};

         $HoH[$csvrow] = $rec;

         local(@entries) = &getEntries($_);

         local ($i) = 0;

         foreach $e (@entries)
         {
            $rec->{$fields[$i]} = $e;
            $i++;
         }
      }
   }

   close FH;

   return ($csvrow, @idx, %HoH);
}

sub getCSVvaluesstar
{
   local($CSVFILE, $startrow) = @_;
   open FH, $CSVFILE or die "Can't open file '$CSVFILE'\n";

   $csvrow=0;

   while (<FH>)
   {
      if ($. >= $startrow)
      {
         chop;

         @fields = &getEntries($_);

         $numcols = 0;

         foreach $field (@fields)
         {
            $entry[$csvrow][$numcols] = $field;

            $numcols++;
         }

         $csvrow++;
      }
   }

   close FH;

   return ($csvrow, $numcols, @entry);
}

sub csvpiechart
{
   local($value, $CSVFILE, $startrow) = @_;

   print "Converting \\csvpiechart[$options]{$label}{$csvname}\n";
   print OUTPUT "% \\csvpiechart[$options]{$label}{$csvname} converted using csvtools.pl\n";
   print OUTPUT "%>> START INSERT\n";

   # read in data from CSV file
   local($csvrow, @idx, %HoH) = &getCSVvalues($CSVFILE,$startrow);

   # first compute parameters

   print OUTPUT "\\edef\\csvstartang{\\thecsvstartangle}\\relax\n";
   $body = "\\csvsetsegmentparams{\\thecsvrownumber}{$value}\\relax\n";

   &doapplyCSV($csvrow, $body, @idx, %HoH);

   print OUTPUT "\\setcounter{csvstartangle}{\\csvstartang}\\relax\n";

   # compute offsets for cutaway segments

   print OUTPUT "\\ifthenelse{\\equal{\\csvpiecutaways}{}}{}{\\csvcomputeoffsets}\\relax\n";

   # now do the pie chart

   print OUTPUT "\\begin{tikzpicture}\n";

   $body = "\\renewcommand*{\\csvpieinnerlabel}{$csvpieinnerlabel}\n";
   $body .= "\\renewcommand*{\\csvpieouterlabel}{$csvpieouterlabel}\n";
   $body .= "\\csvpiesegment{$value}\n";

   &doapplyCSV($csvrow, $body, %HoH);

   print OUTPUT "\\end{tikzpicture}";
   print OUTPUT "%<< END INSERT\n";
   print "\n";
}

sub csvpiechartstar
{
   local($value, $CSVFILE, $startrow) = @_;

   print "Converting \\csvpiechart*[$options]{$label}{$csvname}\n";
   print OUTPUT "% \\csvpiechart*[$options]{$label}{$csvname} converted using csvtools.pl\n";
   print OUTPUT "%>> START INSERT\n";

   local($csvrow, $numcols, @entry) = &getCSVvaluesstar($CSVFILE, $startrow);

   # first compute parameters

   print OUTPUT "\\edef\\csvstartang{\\thecsvstartangle}\\relax\n";
   $body = "\\csvsetsegmentparams{\\thecsvrownumber}{$value}\\relax\n";

   &doapplyCSVstar($body, $csvrow, $numcols, @entry);

   print OUTPUT "\\setcounter{csvstartangle}{\\csvstartang}\\relax\n";

   # compute offsets for cutaway segments

   print OUTPUT "\\ifthenelse{\\equal{\\csvpiecutaways}{}}{}{\\csvcomputeoffsets}\\relax\n";

   $body = "\\renewcommand*{\\csvpieinnerlabel}{$csvpieinnerlabel}\\relax\n";
   $body .= "\\renewcommand*{\\csvpieouterlabel}{$csvpieouterlabel}\\relax\n";
   $body .= "\\csvpiesegment{$value}\\relax\n";

   print OUTPUT "\\begin{tikzpicture}\n";

   &doapplyCSVstar($body, $csvrow, $numcols, @entry);

   print OUTPUT "\\end{tikzpicture}";
   print OUTPUT "%<< END INSERT\n";
   print "\n";
}

sub applyCSVfile
{
   my ($CSVFILE,$body,$startrow) = @_;

   print "Converting \\applyCSVfile{$CSVFILE}";
   print OUTPUT "\% \\applyCSVfile{$CSVFILE}... converted using csvtools.pl\n";
   print OUTPUT "\%>> START INSERT\n";

   # read in data from CSV file
   local($csvrow, @idx, %HoH) = &getCSVvalues($CSVFILE,$startrow);

   # apply data

   &doapplyCSV($csvrow, $body, @idx, %HoH);

   print "\n";

   print OUTPUT "\%<< END INSERT\n";
}

sub sortapplyCSVfile
{
   my ($CSVFILE,$body,$options) = @_;

   print "Converting \\sortapplyCSVfile{$CSVFILE}";
   print OUTPUT "\% \\sortapplyCSVfile{$CSVFILE}... converted using csvtools.pl\n";
   print OUTPUT "\%>> START INSERT\n";

   # get options
   %hash = &getKeyValues($options) if $options;

   local($startrow) = defined($hash{'firstdataline'})
                    ? $hash{'firstdataline'}
                    : $globalsort{'firstdataline'};

   local ($sortorder) = $hash{'sort'};

   # read in data from CSV file
   local($csvrow, @idx, %HoH) = &getCSVvalues($CSVFILE,$startrow);

   # get sort variable

   local($variable) = defined($hash{'variable'})
                    ? $hash{'variable'}
                    : $globalsort{'variable'};

   local(@rowdata)=();

   print OUTPUT "\\bgroup\n";

   # set field labels

   foreach $entry (keys %{$HoH[1]})
   {
      print OUTPUT "\\setcsvfieldlabel{";
      print OUTPUT ($idx{$entry}+1).'}{';
      print OUTPUT "$entry}%\n";
   }

   print OUTPUT "\\def\\csvsortlist{%\n";

   # construct list

   foreach ($row=1; $row <= $csvrow; $row++)
   {
      print ".";

      local($sortkey) = $variable;

      $sortkey = &csvSaveEntry($sortkey,$row,%HoH);

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $sortkey =~ s/\\insertbyname{$entry}/$replacementval/g;

          $sortkey =~ s/\\insert$entry/$replacementval/g;

          local($column) = $idx{$entry}+1;

          $rowdata[$idx{$entry}] = $replacementval;

          $sortkey =~ s/\\field{(\d+)}/$1eq$column?$replacementval:$&/emg;
      }

      print OUTPUT "{$sortkey}{".join($separator,@rowdata)."}";

      print OUTPUT ($row == $csvrow ? "%\n" : ",%\n");
   }

   print OUTPUT "}%\n";

   print OUTPUT "\\sortapplyCSVdata";

   if (defined($sortorder))
   {
      print OUTPUT "[$sortorder]";
   }

   print OUTPUT "{\\csvsortlist}{%\n";

   print OUTPUT "$body}%\n";

   print OUTPUT "\\egroup\n";
   print OUTPUT "\%<< END INSERT\n";

   print "\n";
}

sub sortapplyCSVfilestar
{
   my ($CSVFILE,$body,$options) = @_;

   print "Converting \\sortapplyCSVfile*{$CSVFILE}";
   print OUTPUT "\% \\sortapplyCSVfile*{$CSVFILE}... converted using csvtools.pl\n";
   print OUTPUT "\%>> START INSERT\n";

   # get options
   %hash = &getKeyValues($options) if $options;

   local($startrow) = defined($hash{'sfirstdataline'})
                    ? $hash{'sfirstdataline'}
                    : $globalsort{'sfirstdataline'};

   local ($sortorder) = $hash{'sort'};

   local($csvrow, $numcols, @entry) = &getCSVvaluesstar($CSVFILE, $startrow);

   # get sort variable

   local($variable) = defined($hash{'variable'})
                    ? $hash{'variable'}
                    : $globalsort{'variable'};

   local(@rowdata)=();

   print OUTPUT "\\bgroup\n";

   print OUTPUT "\\def\\csvsortlist{%\n";

   # construct list

   for ($row=0; $row < $csvrow; $row++)
   {
      print ".";

      local($sortkey) = $variable;

      for ($column=0; $column < $numcols; $column++)
      {
          $replacementval=$entry[$row][$column];

          $sortkey =~ s/\\field{(\d+)}/$1eq$column+1?$replacementval:$&/emg;
          $rowdata[$column] = $replacementval;
      }

      print OUTPUT "{$sortkey}{".join($separator,@rowdata)."}";

      print OUTPUT ($row == $csvrow-1 ? "%\n" : ",%\n");
   }

   print OUTPUT "}%\n";

   print OUTPUT "\\sortapplyCSVdata*";

   if (defined($sortorder))
   {
      print OUTPUT "[$sortorder]";
   }

   print OUTPUT "{\\csvsortlist}{%\n";

   print OUTPUT "$body}%\n";

   print OUTPUT "\\egroup\n";

   print "\n";

   print OUTPUT "\%<< END INSERT\n";
}

sub sortCSVtotabular
{
   my ($CSVFILE,$align,$headrow,$midrows,$lastrow,$envname,$options) = @_;

   print "Converting \\sortCSVto${envname}{$CSVFILE}";
   print OUTPUT "\% \\sortCSVto${envname}{$CSVFILE}... converted using csvtools.pl\n";
   print OUTPUT "\%>> START INSERT\n";

   # get options
   %hash = &getKeyValues($options) if $options;

   local($startrow) = defined($hash{'firstdataline'})
                    ? $hash{'firstdataline'}
                    : $globalsort{'firstdataline'};

   local ($sortorder) = $hash{'sort'};

   # read in data from CSV file
   local($csvrow, @idx, %HoH) = &getCSVvalues($CSVFILE,$startrow);

   # get sort variable

   local($variable) = defined($hash{'variable'})
                    ? $hash{'variable'}
                    : $globalsort{'variable'};

   local(@rowdata)=();

   print OUTPUT "\\bgroup\n";

   # set field labels

   foreach $entry (keys %{$HoH[1]})
   {
      print OUTPUT "\\setcsvfieldlabel{";
      print OUTPUT ($idx{$entry}+1).'}{';
      print OUTPUT "$entry}%\n";
   }

   print OUTPUT "\\def\\csvsortlist{%\n";

   # construct list

   foreach ($row=1; $row <= $csvrow; $row++)
   {
      print ".";

      local($sortkey) = $variable;

      $sortkey = &csvSaveEntry($sortkey,$row,%HoH);

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $sortkey =~ s/\\insertbyname{$entry}/$replacementval/g;

          $sortkey =~ s/\\insert$entry/$replacementval/g;

          local($column) = $idx{$entry}+1;

          $rowdata[$idx{$entry}] = $replacementval;

          $sortkey =~ s/\\field{(\d+)}/$1eq$column?$replacementval:$&/emg;
      }

      print OUTPUT "{$sortkey}{".join($separator,@rowdata)."}";

      print OUTPUT ($row == $csvrow ? "%\n" : ",%\n");
   }

   print OUTPUT "}%\n";

   if (defined($sortorder))
   {
      print OUTPUT "\\setkeys{csvsort.sty}{sort=$sortorder}";
   }

   print OUTPUT "\\sortCSVdatatotabular";

   print OUTPUT "{$csvrow}{\\csvsortlist}{$align}%\n";
   print OUTPUT "{$headrow}%\n";
   print OUTPUT "{$midrows}%\n";
   print OUTPUT "{$lastrow}%\n";
   print OUTPUT "{$envname}%\n";

   print OUTPUT "\\egroup\n";
   print OUTPUT "\%<< END INSERT\n";

   print "\n";
}

sub csvSaveEntry
{
   my ($STR,$row,%HoH) = @_;

   $rowctr = "csvrownumber";

   while (($pos = index($STR, "\\csvSaveEntry")) > -1)
   {
      $start      = substr($STR,0,$pos);
      $restofline = substr($STR,$pos+13);

      $restofline = &eatcomments($restofline);
      $restofline = &eatinitialspaces($restofline);

      if (substr($restofline,0,1) eq "[")
      {
         if (($i = index($restofline,"]")) > -1)
         {
            $rowctr = substr($restofline, 1, $i-1);

            $restofline = substr($restofline, $i+1);
         }
         else
         {
            die "unmatched [ in \\csvSaveEntry\n";
         }

         $row = $counter{$rowctr};
      }

      ($group,$restofline,$done) = &getnextgroup($restofline);

      if (!$done)
      {
         die "argument to \\csvSaveEntry[$rowctr] not found in >>$restofline<<\n";
      }

      $val = $HoH[$row]{$group};

      $STR = $start . "\\expandafter\\gdef\\csname $group\\roman{$rowctr}\\endcsname{$val}" . $restofline;

      $data{$group}[$row] = $HoH[$row]{$group};
   }

   return $STR;
}

sub applyCSVfilestar
{
   my ($CSVFILE,$body,$startrow) = @_;

   print "Converting \\applyCSVfile*{$CSVFILE}";

   $counter{'csvrownumber'} = 0;
   local($csvrow, $numcols, @entry) = &getCSVvaluesstar($CSVFILE, $startrow);

   &doapplyCSVstar($body, $csvrow, $numcols, @entry);

   print "\n";

   print OUTPUT "\%<< END INSERT\n";
}

sub csvtotabular
{
   my ($CSVFILE,$ALIGN,$START,$MID,$END,$environment) = @_;

   print "Converting \\CSVtotabular{$CSVFILE}";

   print OUTPUT "\% \\CSVtotabular{$CSVFILE}... converted using $0\n";
   print OUTPUT "\%>> START INSERT\n";

   # read in data from CSV file
   local($csvrow, @idx, %HoH) = &getCSVvalues($CSVFILE,2);

   print OUTPUT "\\setcounter{csvrownumber}{0}%\n";
   $counter{csvrownumber} = 0;
   print OUTPUT "\\begin{$environment}{$ALIGN}\n";

   print OUTPUT "$START";

   foreach ($row=1; $row <= $csvrow; $row++ )
   {
      print ".";

      if ($row == $csvrow)
      {
         $THISROW=$END;
      }
      else
      {
         $THISROW=$MID;
      }

      foreach $entry ( keys %{$HoH[$row]})
      {
          $replacementval=$HoH[$row]{$entry};

          $THISROW =~ s/\\insertbyname{$entry}/$replacementval/g;

          $THISROW =~ s/\\insert$entry/$replacementval/g;

          $column = $idx{$entry};

          $THISROW =~ s/\\field{$column}/$replacementval/g;

          $THISROW = &ifnextrowlast($THISROW, ($row==$csvrow-1));
      }

      print OUTPUT "\\refstepcounter{csvrownumber}$THISROW";
      $counter{csvrownumber}++;
   }

   print OUTPUT "\\end{$environment}";
   print OUTPUT "\%<< END INSERT\n";

   print "\n";
}

sub ifnextrowlast
{
   my ($STR, $nextislast) = @_;

   if (($pos = index($STR, "\\ifnextrowlast")) > -1)
   {
      $strbegin = substr($STR,0,$pos);
      $strend   = substr($STR,$pos+14);

      ($firstarg,$strend,$done) = &getnextgroup($strend);

      if (!$done)
      {
         die "Can't find first argument to \\ifnextrowlast\n";
      }

      ($secondarg,$strend,$done) = &getnextgroup($strend);

      if (!$done)
      {
         die "Can't find second argument to \\ifnextrowlast\n";
      }

      if ($nextislast)
      {
         $STR = $strbegin . $firstarg . $strend;
      }
      else
      {
         $STR = $strbegin . $secondarg . $strend;
      }
   }

   return $STR;
}

sub eatcomments
{
   local ($_) = @_;

   if (m/(\\{1}%[^%]*)*(\\*)%/)
   {
      $pre = $`;
      $pre .= $1 if defined($1);
      $pre .= $2;

      local($n) = length($2);

      if ($n%2 == 0)
      {
         $_ = $pre;
      }
   }

   return $_;
}

sub eatinitialspaces
{
   local ($_) = @_;

#   while (substr($STR,0,1) eq " " or substr($STR,0,1) eq "\n"
#         or substr($STR,0,1) eq "\r" or substr($STR,0,1) eq "\t")
#   {
#      $STR = substr($STR,1);
#   }

   s/^(\s)*//sg;

   return $_;
}

sub getnextgroup
{
   my($curline) = @_;

   $curline = &eatcomments($curline);

   $curline = &eatinitialspaces($curline);

   # check to see if current string is blank

   if ($curline!~/[^\s]+/m)
   {
      return ("",$curline,0);
   }

   if (($group = substr($curline,0,1)) ne "{")
   {
       # next group hasn't been delimited with braces
       # return first non-whitespace character

       $curline = substr($curline,1);

       # unless it's a backslash, in which case get command name

       if ($group eq "\\")
       {
          if ($curline=~/([a-zA-Z]+)(^[a-zA-Z].*)/m)
          {
             $group = $1;

             $curline = $2;
          }
          else
          {
             # command is made up of backslash followed by symbol

             $curline=~/([\W_0-9\s\\])(.*)/m;

             $group = $1;

             $curline = $2;
          }
       }

       return ($group,$curline,1);
   }

   my $pos=index($curline, "{");
   my $startpos=$pos;
   my $posopen=0;
   my $posclose=0;

   my $bracelevel = 1;

   my $done=0;

   while (!$done)
   {
      $pos++;

      $posopen = index($curline, "{", $pos);

      # check to make sure it's not a \{

      while ((substr($curline, $posopen-1,1) eq "\\") and ($posopen > 0))
      {
         # count how many backlashes come before it.

         $i = $posopen-1;

         $numbs = 1;

         while ((substr($curline, $i-1,1) eq "\\") and ($i > 0))
         {
            $numbs++;
            $i--;
         }

         # is $numbs is odd, we have a \{, otherwise we have \\{

         if ($numbs%2 == 0)
         {
            last;
         }
         else
         {
            $posopen = index($curline, "{", $posopen+1);
         }
      }

      $posclose= index($curline, "}", $pos);

      # check to make sure it's not a \}

      while ((substr($curline, $posclose-1,1) eq "\\") and ($posclose > 0))
      {
         # count how many backlashes come before it.

         $i = $posclose-1;

         $numbs = 1;

         while ((substr($curline, $i-1,1) eq "\\") and ($i > 0))
         {
            $numbs++;
            $i--;
         }

         # is $numbs is odd, we have a \}, otherwise we have \\}

         if ($numbs%2 == 0)
         {
            last;
         }
         else
         {
            $posclose = index($curline, "}", $posclose+1);
         }
      }

      if (($posopen==-1) and ($posclose==-1))
      {
         $done=1;
      }
      elsif ($posopen==-1)
      {
         $pos=$posclose;

         $bracelevel--;

         if ($bracelevel==0)
         {
            $group = substr($curline, $startpos+1, $pos-$startpos-1);

            $curline = substr($curline, $pos+1);

            return ($group,$curline,1);
         }
      }
      elsif ($posclose==-1)
      {
         $pos=$posopen;

         $bracelevel++;
      }
      elsif ($posopen<$posclose)
      {
         $pos=$posopen;

         $bracelevel++;
      }
      elsif ($posclose<$posopen)
      {
         $pos=$posclose;

         $bracelevel--;

         if ($bracelevel==0)
         {
            $group = substr($curline, $startpos+1, $pos-$startpos-1);

            $curline = substr($curline, $pos+1);

            return ($group,$curline,1);
         }
      }
   }

   # closing brace must be on another line

   return ("", $curline, 0);
}

sub getnextoptionalgroup
{
   my($curline) = @_;

   $curline = &eatcomments($curline);

   $curline = &eatinitialspaces($curline);

   # check to see if current string is blank

   if ($curline!~/[^\s]+/m)
   {
      return ("","",0);
   }

   if (($group = substr($curline,0,1)) ne "[")
   {
      # no optional argument present
      return ("", $curline, 1);
   }

   $posopen = index($curline, "[");

   # search for first occurance of ]

   $posclose = index($curline, "]");

   if ($posclose < 0)
   {
      # closing bracket not on this line
      return ("", $curline, 0);
   }

   # is there a { in the substring?

   $group = substr($curline, $posopen, $posclose+1);
   $contents = $group;
   $restofline = substr($curline, $posclose+$posopen+1);

   while (($idx=index($group, "{")) > 0)
   {
      $post = substr($group, $idx);

      $subgroup="";
      ($subgroup,$remainder,$done) = &getnextgroup($post);

      if ($done)
      {
         $group = $remainder;
      }
      else
      {
         # ] is in a group, so look for next one
         $posclose = index($restofline, "]");

         if ($posclose < 0)
         {
            # closing bracket not on this line
            return ("", $curline, 0);
         }
         else
         {
            $contents .= substr($restofline,0,$posclose);
            $group = $contents;
         }
      }
   }

   $contents=~s/^\[(.*)\]$/$1/;

   return ($contents, $restofline, 1);
}

# get all entries in a row
# modified version of code given in Perl FAQ

sub getEntries{
   local($row)=@_;

   @entries = ();
   push(@entries, $+) while $row =~ m{
       "([^\"\\]*(?:\\.[^\"\\]*)*)"$separator?  # groups the phrase inside the quotes
     | ([^$separator]+)$separator?
     | $separator
   }gx;
   push(@entries, undef) if substr($row,-1,1) eq $separator;

   @entries;
}

# extract key=value pairs from list

sub getKeyValues{
   local($list,%defaults)=@_;
   local(%hash)=();

   # remove leading commas and spaces

   $list=~s/^[\s,]*//;

   while ($list=~/^\s*([^,=]+)\s*/)
   {
      $key=$1;

      $value = '';

      if ($list=~s/^\s*$key\s*=\s*{/{/)
      {
         ($value,$list,$done) = &getnextgroup($list);
      }
      elsif ($list=~s/^\s*$key\s*=\s*([^,]*)\s*//)
      {
         $value = $1;
      }
      elsif ($list=~s/^\s*$key\s*(,|$)//)
      {
         if (defined($defaults{$key}))
         {
            $value = $defaults{$key};
         }
         else
         {
            print "don't know default value for $key key\n";
         }
      }

      $hash{$key} = $value;

      # remove trailing commas

      $list=~s/^[\s,]*//;
   }

   %hash;
}

1;