       1 #!/usr/bin/perl -w
       2 #
       3 # File: ElementalAnalysisTextFiles.pl
       4 # Author: Manish Sud <msud@san.rr.com>
       5 #
       6 # Copyright (C) 2018 Manish Sud. All rights reserved.
       7 #
       8 # This file is part of MayaChemTools.
       9 #
      10 # MayaChemTools is free software; you can redistribute it and/or modify it under
      11 # the terms of the GNU Lesser General Public License as published by the Free
      12 # Software Foundation; either version 3 of the License, or (at your option) any
      13 # later version.
      14 #
      15 # MayaChemTools is distributed in the hope that it will be useful, but without
      16 # any warranty; without even the implied warranty of merchantability of fitness
      17 # for a particular purpose.  See the GNU Lesser General Public License for more
      18 # details.
      19 #
      20 # You should have received a copy of the GNU Lesser General Public License
      21 # along with MayaChemTools; if not, see <http://www.gnu.org/licenses/> or
      22 # write to the Free Software Foundation Inc., 59 Temple Place, Suite 330,
      23 # Boston, MA, 02111-1307, USA.
      24 #
      25
      26 use strict;
      27 use FindBin; use lib "$FindBin::Bin/../lib";
      28 use Getopt::Long;
      29 use File::Basename;
      30 use Text::ParseWords;
      31 use Benchmark;
      32 use FileUtil;
      33 use TextUtil;
      34 use MolecularFormula;
      35
      36 my($ScriptName, %Options, $StartTime, $EndTime, $TotalTime);
      37
      38 # Autoflush STDOUT
      39 $| = 1;
      40
      41 # Starting message...
      42 $ScriptName = basename($0);
      43 print "\n$ScriptName: Starting...\n\n";
      44 $StartTime = new Benchmark;
      45
      46 # Get the options and setup script...
      47 SetupScriptUsage();
      48 if ($Options{help} || @ARGV < 1) {
      49   die GetUsageFromPod("$FindBin::Bin/$ScriptName");
      50 }
      51
      52 my(@TextFilesList);
      53 @TextFilesList = ExpandFileNames(\@ARGV, "csv tsv");
      54
      55 # Process options...
      56 my(%OptionsInfo);
      57 print "Processing options...\n";
      58 ProcessOptions();
      59
      60 print "Checking input text file(s)...\n";
      61 my(%TextFilesInfo);
      62 RetrieveTextFilesInfo();
      63 RetrieveColumnsAndLabelsInfo();
      64
      65 # Generate output files...
      66 my($FileIndex);
      67 if (@TextFilesList > 1) {
      68   print "\nProcessing text files...\n";
      69 }
      70 for $FileIndex (0 .. $#TextFilesList) {
      71   if ($TextFilesInfo{FileOkay}[$FileIndex]) {
      72     print "\nProcessing file $TextFilesList[$FileIndex]...\n";
      73     PerformElementalAnalysis($FileIndex);
      74   }
      75 }
      76 print "\n$ScriptName:Done...\n\n";
      77
      78 $EndTime = new Benchmark;
      79 $TotalTime = timediff ($EndTime, $StartTime);
      80 print "Total time: ", timestr($TotalTime), "\n";
      81
      82 ###############################################################################
      83
      84 # Perform elemental analysis...
      85 sub PerformElementalAnalysis {
      86   my($Index) = @_;
      87   my($TextFile, $NewTextFile, $FormulaCol, $Line, $NewLine, $FormulaColValue, $InDelim, $ColNum, $Value, $Status, $ErrorMsg, @ColLabels, @LineWords, @ColNumsBeforeNew, @ColNumsAfterNew);
      88
      89   $TextFile = $TextFilesList[$Index];
      90   $InDelim = $TextFilesInfo{InDelim}[$Index];
      91   $NewTextFile = $TextFilesInfo{OutFile}[$Index];
      92   $FormulaCol = $TextFilesInfo{FormulaColNum}[$Index];
      93
      94   @ColNumsBeforeNew = @{$TextFilesInfo{ColNumsBeforeNew}[$Index]};
      95   @ColNumsAfterNew = @{$TextFilesInfo{ColNumsAfterNew}[$Index]};
      96
      97   print "Generating new Text file $NewTextFile...\n";
      98   open NEWTEXTFILE, ">$NewTextFile" or die "Error: Couldn't open $NewTextFile: $! \n";
      99   open TEXTFILE, "$TextFile" or die "Error: Can't open $TextFile: $! \n";
     100
     101   # Skip over column labels from old file...
     102   $Line = GetTextLine(\*TEXTFILE);
     103
     104   # Add column lablels in new file...
     105   @ColLabels = ();
     106   for $ColNum (@ColNumsBeforeNew) {
     107     push @ColLabels, $TextFilesInfo{ColLabels}[$Index][$ColNum];
     108   }
     109   for $Value (@{$OptionsInfo{SpecifiedCalculations}}) {
     110     push @ColLabels, $TextFilesInfo{ValueLabelsMap}[$Index]{$Value};
     111   }
     112   for $ColNum (@ColNumsAfterNew) {
     113     push @ColLabels, $TextFilesInfo{ColLabels}[$Index][$ColNum];
     114   }
     115   $NewLine = '';
     116   $NewLine = JoinWords(\@ColLabels, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
     117   print NEWTEXTFILE "$NewLine\n";
     118
     119   # Go over all rows...
     120   my($LineCount, $ElementsRef, $ElementCompositionRef, $CalculationType, $CalculatedValue, @CalculatedValues);
     121
     122   $LineCount = 1;
     123   TEXTLINE: while ($Line = GetTextLine(\*TEXTFILE)) {
     124     @LineWords = quotewords($InDelim, 0, $Line);
     125     $LineCount++;
     126
     127     @CalculatedValues = ();
     128     for $Value (@{$OptionsInfo{SpecifiedCalculations}}) {
     129       push @CalculatedValues, '';
     130     }
     131     if ($FormulaCol > @LineWords) {
     132       $ErrorMsg = "Ignoring line $LineCount: Formula column $ColLabels[$FormulaCol] not found";
     133       PrintErrorMsg($Line, $ErrorMsg);
     134       ComposeAndWriteNewLine(\*NEWTEXTFILE, \@LineWords, \@ColNumsBeforeNew, \@ColNumsAfterNew, \@CalculatedValues);
     135       next TEXTLINE;
     136     }
     137
     138     # Make sure it's a valid molecular formula...
     139     $FormulaColValue = $LineWords[$FormulaCol];
     140     if ($OptionsInfo{CheckFormula}) {
     141       ($Status, $ErrorMsg) = MolecularFormula::IsMolecularFormula($FormulaColValue);
     142       if (!$Status) {
     143         $ErrorMsg = "Ignoring line $LineCount: Formula column $ColLabels[$FormulaCol] value is not valid: $ErrorMsg";
     144         PrintErrorMsg($Line, $ErrorMsg);
     145         ComposeAndWriteNewLine(\*NEWTEXTFILE, \@LineWords, \@ColNumsBeforeNew, \@ColNumsAfterNew, \@CalculatedValues);
     146         next TEXTLINE;
     147       }
     148     }
     149
     150     # Calculate appropriate values and write 'em out...
     151     @CalculatedValues = ();
     152     for $CalculationType (@{$OptionsInfo{SpecifiedCalculations}}) {
     153       if ($CalculationType =~ /^ElementalAnalysis$/i) {
     154         ($ElementsRef, $ElementCompositionRef) = MolecularFormula::CalculateElementalComposition($FormulaColValue);
     155         $CalculatedValue = (defined($ElementsRef) && defined($ElementCompositionRef)) ? MolecularFormula::FormatCompositionInfomation($ElementsRef, $ElementCompositionRef, $OptionsInfo{Precision}) : '';
     156       }
     157       elsif ($CalculationType =~ /^MolecularWeight$/i) {
     158         $CalculatedValue = MolecularFormula::CalculateMolecularWeight($FormulaColValue);
     159         $CalculatedValue = (defined($CalculatedValue) && length($CalculatedValue)) ? (sprintf("%.$OptionsInfo{Precision}f", $CalculatedValue)) : "";
     160       }
     161       elsif ($CalculationType =~ /^ExactMass$/i) {
     162         $CalculatedValue = MolecularFormula::CalculateExactMass($FormulaColValue);
     163         $CalculatedValue = (defined($CalculatedValue) && length($CalculatedValue)) ? (sprintf("%.$OptionsInfo{Precision}f", $CalculatedValue)) : "";
     164       }
     165       else {
     166         $CalculatedValue = '';
     167       }
     168       push @CalculatedValues, $CalculatedValue;
     169     }
     170
     171     ComposeAndWriteNewLine(\*NEWTEXTFILE, \@LineWords, \@ColNumsBeforeNew, \@ColNumsAfterNew, \@CalculatedValues);
     172   }
     173   close NEWTEXTFILE;
     174   close TEXTFILE;
     175
     176 }
     177
     178 # Write out new line using old and new calculated data...
     179 sub ComposeAndWriteNewLine {
     180   my($NewTextFileRef, $OldLineWordsRef, $ColNumsBeforeNewRef, $ColNumsAfterNewRef, $CalculatedValuesRef) = @_;
     181   my($NewLine, $ColNum, $Value, @NewLineWords);
     182
     183   @NewLineWords = ();
     184   for $ColNum (@{$ColNumsBeforeNewRef}) {
     185     push @NewLineWords, $OldLineWordsRef->[$ColNum];
     186   }
     187   for $Value (@{$CalculatedValuesRef}) {
     188     push @NewLineWords, $Value;
     189   }
     190   for $ColNum (@{$ColNumsAfterNewRef}) {
     191     push @NewLineWords, $OldLineWordsRef->[$ColNum];
     192   }
     193   $NewLine = JoinWords(\@NewLineWords, $OptionsInfo{OutDelim}, $OptionsInfo{OutQuote});
     194   print $NewTextFileRef "$NewLine\n";
     195 }
     196
     197 # Print out error message...
     198 sub PrintErrorMsg {
     199   my($Line, $ErrorMsg) = @_;
     200
     201   if ($OptionsInfo{DetailLevel} >= 2 ) {
     202     print "$ErrorMsg: $Line\n";
     203   }
     204   elsif ($OptionsInfo{DetailLevel} >= 1) {
     205     print "$ErrorMsg\n";
     206   }
     207 }
     208
     209 # Process formula columns and other information...
     210 sub RetrieveColumnsAndLabelsInfo {
     211   RetrieveFormulaColumnsInfo();
     212   RetrieveStartColumnsAndValueLabelsInfo();
     213 }
     214
     215 # Make sure specified formula column are okay...
     216 sub RetrieveFormulaColumnsInfo {
     217   my($Index, $TextFile);
     218
     219   @{$TextFilesInfo{FormulaColNum}} = ();
     220
     221  FILELIST: for $Index (0 .. $#TextFilesList) {
     222     $TextFile = $TextFilesList[$Index];
     223
     224     $TextFilesInfo{FormulaColNum}[$Index] = 0;
     225
     226     if ($TextFilesInfo{FileOkay}[$Index]) {
     227       my($FormulaColNum, $FormulaColValid);
     228
     229       $FormulaColNum = 0;
     230       $FormulaColValid = 0;
     231       if ($OptionsInfo{SpecifiedFormulaCol}) {
     232         if ($OptionsInfo{ColMode} =~ /^colnum$/i) {
     233           if ($OptionsInfo{SpecifiedFormulaCol} <= $TextFilesInfo{ColCount}[$Index]) {
     234             $FormulaColNum = $OptionsInfo{SpecifiedFormulaCol} - 1;
     235             $FormulaColValid = 1;
     236           }
     237         }
     238         else {
     239           if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$OptionsInfo{SpecifiedFormulaCol}})) {
     240             $FormulaColNum =  $TextFilesInfo{ColLabelToNumMap}[$Index]{$OptionsInfo{SpecifiedFormulaCol}};
     241             $FormulaColValid = 1;
     242           }
     243         }
     244       }
     245       else {
     246         # Grab the first column with the word Formula in its label...
     247         my($ColLabel);
     248         LABEL: for $ColLabel (@{$TextFilesInfo{ColLabels}[$Index]}) {
     249           if ($ColLabel =~ /Formula/i) {
     250             $FormulaColNum =  $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel};
     251             $FormulaColValid = 1;
     252             last LABEL;
     253           }
     254         }
     255       }
     256       if ($FormulaColValid) {
     257         $TextFilesInfo{FormulaColNum}[$Index] = $FormulaColNum;
     258       }
     259       else {
     260         if ($OptionsInfo{SpecifiedFormulaCol}) {
     261           warn "Warning: Ignoring file $TextFile: Formula column specified, $OptionsInfo{SpecifiedFormulaCol}, using \"f --formulacol\" option doesn't exist\n";
     262         }
     263         else {
     264           warn "Warning: Ignoring file $TextFile: Column label containing the word Formula doesn't exist\n";
     265         }
     266         $TextFilesInfo{FileOkay}[$Index] = 0;
     267       }
     268     }
     269   }
     270 }
     271
     272 # Setup starting column number for adding calculated values and
     273 # column lables to use for these values...
     274 sub RetrieveStartColumnsAndValueLabelsInfo {
     275   my($Index, $TextFile, $SpecifiedStartColNum, $StartColNum, $Label, $Value, $NewLabel, $Count, $BeforeStartColNum, $AfterStartColNum, $FirstColNum, $LastColNum, $ColNum, $Part1StartColNum, $Part1EndColNum, $Part2StartColNum, $Part2EndColNum, @Part1ColNums, @Part2ColNums);
     276
     277   # Start column number for inserting new values...
     278   $SpecifiedStartColNum = "last";
     279   if (defined($OptionsInfo{StartCol})) {
     280     if (length($OptionsInfo{StartCol})) {
     281       $SpecifiedStartColNum = $OptionsInfo{StartCol}
     282     }
     283   }
     284
     285   # Column labels for for new calculated values...
     286   my(%NewValueLabels) = (ElementalAnalysis => 'ElementalAnalysis', MolecularWeight => 'MolecularWeight', ExactMass => 'ExactMass');
     287   if (@{$OptionsInfo{SpecifiedValueLabels}}) {
     288     for ($Index = 0; $Index < @{$OptionsInfo{SpecifiedValueLabels}}; $Index +=2) {
     289       $Value = $OptionsInfo{SpecifiedValueLabels}[$Index];
     290       $Label = $OptionsInfo{SpecifiedValueLabels}[$Index + 1];
     291       if (exists $NewValueLabels{$Value}) {
     292         $NewValueLabels{$Value} = $Label;
     293       }
     294     }
     295   }
     296
     297   @{$TextFilesInfo{ColNumsBeforeNew}} = ();
     298   @{$TextFilesInfo{ColNumsAfterNew}} = ();
     299   @{$TextFilesInfo{ValueLabelsMap}} = ();
     300
     301   FILELIST: for $Index (0 .. $#TextFilesList) {
     302     $TextFile = $TextFilesList[$Index];
     303
     304     @{$TextFilesInfo{ColNumsBeforeNew}[$Index]} = ();
     305     @{$TextFilesInfo{ColNumsAfterNew}[$Index]} = ();
     306     %{$TextFilesInfo{ValueLabelsMap}[$Index]} = ();
     307
     308     if (!$TextFilesInfo{FileOkay}[$Index]) {
     309       next FILELIST;
     310     }
     311
     312     if ($SpecifiedStartColNum !~ /^last$/i) {
     313       if ($OptionsInfo{ColMode} =~ /^collabel$/i) {
     314         if (exists($TextFilesInfo{ColLabelToNumMap}[$Index]{$SpecifiedStartColNum})) {
     315           $StartColNum = $TextFilesInfo{ColLabelToNumMap}[$Index]{$SpecifiedStartColNum};
     316         }
     317         else {
     318           die "Error: Invalid value $SpecifiedStartColNum specified using \"-s --startcol\" option: column name doesn't exist in  $TextFile  \n";
     319         }
     320       }
     321       else {
     322         if ($SpecifiedStartColNum > 0 && $SpecifiedStartColNum <= $TextFilesInfo{ColCount}[$Index]) {
     323           $StartColNum -= 1;
     324         }
     325         else {
     326           die "Error: Invalid value $SpecifiedStartColNum specified using \"-s --startcol\" option: column number doesn't exist in  $TextFile  \n";
     327         }
     328       }
     329     }
     330     else {
     331       $StartColNum = $TextFilesInfo{ColCount}[$Index] - 1;
     332     }
     333     # Set up columns lists for before and after the addition of calculated column values
     334     # for each text file...
     335     my($BeforeStartColNum, $AfterStartColNum, $FirstColNum, $LastColNum, $ColNum, $Part1StartColNum, $Part1EndColNum, $Part2StartColNum, $Part2EndColNum, @Part1ColNums, @Part2ColNums);
     336
     337     $FirstColNum = 0; $LastColNum = $TextFilesInfo{ColCount}[$Index] - 1;
     338
     339     $BeforeStartColNum = $StartColNum - 1;
     340     $AfterStartColNum = $StartColNum + 1;
     341
     342     if ($OptionsInfo{StartColMode} =~ /^after$/i) {
     343       $Part1StartColNum = $FirstColNum; $Part1EndColNum = $StartColNum;
     344       $Part2StartColNum = $AfterStartColNum; $Part2EndColNum = $LastColNum;
     345     }
     346     else {
     347       $Part1StartColNum = $FirstColNum; $Part1EndColNum = $BeforeStartColNum;
     348       $Part2StartColNum = $StartColNum; $Part2EndColNum = $LastColNum;
     349     }
     350     @Part1ColNums = (); @Part2ColNums = ();
     351     for $ColNum (0 .. $TextFilesInfo{ColCount}[$Index]) {
     352       if ($ColNum >= $Part1StartColNum && $ColNum <= $Part1EndColNum) {
     353         push @Part1ColNums, $ColNum;
     354       }
     355     }
     356     for $ColNum (0 .. $TextFilesInfo{ColCount}[$Index]) {
     357       if ($ColNum >= $Part2StartColNum && $ColNum <= $Part2EndColNum) {
     358         push @Part2ColNums, $ColNum;
     359       }
     360     }
     361     push @{$TextFilesInfo{ColNumsBeforeNew}[$Index]}, @Part1ColNums;
     362     push @{$TextFilesInfo{ColNumsAfterNew}[$Index]}, @Part2ColNums;
     363
     364     # Setup column labels for calculated values...
     365     for $Value (keys %NewValueLabels) {
     366       $Label = $NewValueLabels{$Value};
     367
     368       # Make sure it doesn't already exists...
     369       $Count = 1;
     370       $NewLabel = $Label;
     371       while (exists $TextFilesInfo{ColLabelToNumMap}[$Index]{$NewLabel}) {
     372         $Count++;
     373         $NewLabel = $Label . $Count;
     374       }
     375       $TextFilesInfo{ValueLabelsMap}[$Index]{$Value} = $NewLabel;
     376     }
     377   }
     378 }
     379
     380 # Retrieve information about input text files...
     381 sub RetrieveTextFilesInfo {
     382   my($Index, $TextFile, $FileDir, $FileName, $FileExt, $InDelim, $Line, @ColLabels, $OutFileRoot,  $OutFile, $ColNum, $ColLabel);
     383
     384   %TextFilesInfo = ();
     385
     386   @{$TextFilesInfo{FileOkay}} = ();
     387   @{$TextFilesInfo{ColCount}} = ();
     388   @{$TextFilesInfo{ColLabels}} = ();
     389   @{$TextFilesInfo{ColLabelToNumMap}} = ();
     390   @{$TextFilesInfo{InDelim}} = ();
     391   @{$TextFilesInfo{OutFile}} = ();
     392
     393   FILELIST: for $Index (0 .. $#TextFilesList) {
     394     $TextFile = $TextFilesList[$Index];
     395
     396     $TextFilesInfo{FileOkay}[$Index] = 0;
     397     $TextFilesInfo{ColCount}[$Index] = 0;
     398     $TextFilesInfo{InDelim}[$Index] = "";
     399     $TextFilesInfo{OutFile}[$Index] = "";
     400
     401     @{$TextFilesInfo{ColLabels}[$Index]} = ();
     402     %{$TextFilesInfo{ColLabelToNumMap}[$Index]} = ();
     403
     404     if (!(-e $TextFile)) {
     405       warn "Warning: Ignoring file $TextFile: It doesn't exist\n";
     406       next FILELIST;
     407     }
     408     if (!CheckFileType($TextFile, "csv tsv")) {
     409       warn "Warning: Ignoring file $TextFile: It's not a csv or tsv file\n";
     410       next FILELIST;
     411     }
     412     ($FileDir, $FileName, $FileExt) = ParseFileName($TextFile);
     413     if ($FileExt =~ /^tsv$/i) {
     414       $InDelim = "\t";
     415     }
     416     else {
     417       $InDelim = "\,";
     418       if ($Options{indelim} !~ /^(comma|semicolon)$/i) {
     419         warn "Warning: Ignoring file $TextFile: The value specified, $Options{indelim}, for option \"--indelim\" is not valid for csv files\n";
     420         next FILELIST;
     421       }
     422       if ($Options{indelim} =~ /^semicolon$/i) {
     423         $InDelim = "\;";
     424       }
     425     }
     426
     427     if (!open TEXTFILE, "$TextFile") {
     428       warn "Warning: Ignoring file $TextFile: Couldn't open it: $! \n";
     429       next FILELIST;
     430     }
     431
     432     $Line = GetTextLine(\*TEXTFILE);
     433     @ColLabels = quotewords($InDelim, 0, $Line);
     434     close TEXTFILE;
     435
     436     $FileDir = ""; $FileName = ""; $FileExt = "";
     437     ($FileDir, $FileName, $FileExt) = ParseFileName($TextFile);
     438     $FileExt = "csv";
     439     if ($Options{outdelim} =~ /^tab$/i) {
     440       $FileExt = "tsv";
     441     }
     442     if ($Options{root} && (@TextFilesList == 1)) {
     443       my ($RootFileDir, $RootFileName, $RootFileExt) = ParseFileName($Options{root});
     444       if ($RootFileName && $RootFileExt) {
     445         $FileName = $RootFileName;
     446       }
     447       else {
     448         $FileName = $Options{root};
     449       }
     450       $OutFileRoot = $FileName;
     451     }
     452     else {
     453       $OutFileRoot = $FileName . "ElementalAnalysis";
     454     }
     455
     456     $OutFile = $OutFileRoot . ".$FileExt";
     457     if (lc($OutFile) eq lc($TextFile)) {
     458       warn "Warning: Ignoring file $TextFile:Output file name, $OutFile, is same as input text file name, $TextFile\n";
     459       next FILELIST;
     460     }
     461     if (!$Options{overwrite}) {
     462       if (-e $OutFile) {
     463         warn "Warning: Ignoring file $TextFile: The file $OutFile already exists\n";
     464         next FILELIST;
     465       }
     466     }
     467
     468     $TextFilesInfo{FileOkay}[$Index] = 1;
     469     $TextFilesInfo{InDelim}[$Index] = $InDelim;
     470     $TextFilesInfo{OutFile}[$Index] = "$OutFile";
     471
     472     $TextFilesInfo{ColCount}[$Index] = @ColLabels;
     473     push @{$TextFilesInfo{ColLabels}[$Index]}, @ColLabels;
     474     for $ColNum (0 .. $#ColLabels) {
     475       $ColLabel = $ColLabels[$ColNum];
     476       $TextFilesInfo{ColLabelToNumMap}[$Index]{$ColLabel} = $ColNum;
     477     }
     478   }
     479
     480 }
     481
     482 # Process option values...
     483 sub ProcessOptions {
     484   %OptionsInfo = ();
     485
     486   $OptionsInfo{Mode} = $Options{mode};
     487
     488   $OptionsInfo{ColMode} = $Options{colmode};
     489   $OptionsInfo{StartColMode} = $Options{startcolmode};
     490
     491   $OptionsInfo{Fast} = defined $Options{fast} ? $Options{fast} : undef;
     492
     493   $OptionsInfo{DetailLevel} = $Options{detail};
     494   $OptionsInfo{CheckFormula} = $Options{fast} ? 0 : 1;
     495   $OptionsInfo{Precision} = $Options{precision};
     496
     497   $OptionsInfo{InDelim} = $Options{indelim};
     498
     499   $OptionsInfo{OutDelim} = ($Options{outdelim} =~ /^tab$/i ) ? "\t" : (($Options{outdelim} =~ /^semicolon$/i) ? "\;" : "\,");
     500   $OptionsInfo{OutQuote} = ($Options{quote} =~ /^yes$/i) ? 1 : 0;
     501
     502   $OptionsInfo{Overwrite} = defined $Options{overwrite} ? $Options{overwrite} : undef;
     503   $OptionsInfo{OutFileRoot} = defined $Options{root} ? $Options{root} : undef;
     504
     505   $OptionsInfo{StartCol} = defined $Options{startcol} ? $Options{startcol} : undef;
     506
     507   $OptionsInfo{FormulaCol} = defined $Options{formulacol} ? $Options{formulacol} : undef;
     508   $OptionsInfo{SpecifiedFormulaCol} = "";
     509
     510   if (defined $Options{formulacol}) {
     511     $OptionsInfo{SpecifiedFormulaCol} = $Options{formulacol};
     512     if ($Options{colmode} =~ /^colnum$/i) {
     513       if (!IsPositiveInteger($OptionsInfo{SpecifiedFormulaCol})) {
     514         die "Error: Invalid value $Options{formulacol} specified using \"-f -formulacol\" option: Allowed values: > 0\n";
     515       }
     516     }
     517   }
     518
     519   # Setup what to calculate...
     520   @{$OptionsInfo{SpecifiedCalculations}} = ();
     521   if ($Options{mode} =~ /^All$/i) {
     522     @{$OptionsInfo{SpecifiedCalculations}} = qw(ElementalAnalysis MolecularWeight ExactMass);
     523   }
     524   else {
     525     my($Mode, $ModeValue, @SpecifiedModeValues);
     526     $Mode = $Options{mode};
     527     $Mode =~ s/ //g;
     528     @SpecifiedModeValues = split /\,/, $Mode;
     529     for $ModeValue (@SpecifiedModeValues) {
     530       if ($ModeValue !~ /^(ElementalAnalysis|MolecularWeight|ExactMass)$/i) {
     531         if ($ModeValue =~ /^All$/i) {
     532           die "Error: All value for option \"-m --mode\" is not allowed with other valid values.\n";
     533         }
     534         else {
     535           die "Error: The value specified, $ModeValue, for option \"-m --mode\" is not valid. Allowed values: ElementalAnalysis, MolecularWeight, or ExactMass\n";
     536         }
     537       }
     538       push @{$OptionsInfo{SpecifiedCalculations}}, $ModeValue;
     539     }
     540   }
     541
     542   $OptionsInfo{ValueColLabels} = defined $Options{valuecollabels} ? $Options{valuecollabels} : undef;
     543   @{$OptionsInfo{SpecifiedValueLabels}} = ();
     544
     545   if ($Options{valuecollabels}) {
     546     my($Value, $Label, @ValueLabels);
     547     @ValueLabels = split /\,/, $Options{valuecollabels};
     548     if (@ValueLabels % 2) {
     549       die "Error: The value specified, $Options{valuecollabels}, for option \"-v --valuecollabels\" is not valid: It must contain even number of comma delimited values\n";
     550     }
     551     my($Index);
     552     for ($Index = 0; $Index < @ValueLabels; $Index +=2) {
     553       $Value = $ValueLabels[$Index];
     554       $Value =~ s/ //g;
     555       $Label = $ValueLabels[$Index + 1];
     556       if ($Value !~ /^(ElementalAnalysis|MolecularWeight|ExactMass)$/i) {
     557         die "Error: The value specified, $Value, using option \"-v --valuecollabels\" is not valid. Allowed values: ElementalAnalysis, MolecularWeight, or ExactMass\n";
     558       }
     559       push @{$OptionsInfo{SpecifiedValueLabels}}, ($Value, $Label);
     560     }
     561   }
     562 }
     563
     564 # Setup script usage  and retrieve command line arguments specified using various options...
     565 sub SetupScriptUsage {
     566
     567   # Retrieve all the options...
     568   %Options = ();
     569   $Options{colmode} = "colnum";
     570   $Options{detail} = 1;
     571   $Options{mode} = "All";
     572   $Options{indelim} = "comma";
     573   $Options{outdelim} = "comma";
     574   $Options{precision} = 2;
     575   $Options{quote} = "yes";
     576   $Options{startcolmode} = "after";
     577
     578   if (!GetOptions(\%Options, "colmode|c=s", "detail|d=i", "fast", "formulacol|f=s", "help|h", "indelim=s", "mode|m=s", "outdelim=s", "overwrite|o", "precision|p=i", "quote|q=s", "root|r=s", "startcol|s=s", "startcolmode=s", "valuecollabels|v=s", "workingdir|w=s")) {
     579     die "\nTo get a list of valid options and their values, use \"$ScriptName -h\" or\n\"perl -S $ScriptName -h\" command and try again...\n";
     580   }
     581   if ($Options{workingdir}) {
     582     if (! -d $Options{workingdir}) {
     583       die "Error: The value specified, $Options{workingdir}, for option \"-w --workingdir\" is not a directory name.\n";
     584     }
     585     chdir $Options{workingdir} or die "Error: Couldn't chdir $Options{workingdir}: $! \n";
     586   }
     587   if ($Options{colmode} !~ /^(colnum|collabel)$/i) {
     588     die "Error: The value specified, $Options{colmode}, for option \"-c --colmode\" is not valid. Allowed values: colnum or collabel\n";
     589   }
     590   if ($Options{indelim} !~ /^(comma|semicolon)$/i) {
     591     die "Error: The value specified, $Options{indelim}, for option \"--indelim\" is not valid. Allowed values: comma or semicolon\n";
     592   }
     593   if ($Options{outdelim} !~ /^(comma|semicolon|tab)$/i) {
     594     die "Error: The value specified, $Options{outdelim}, for option \"--outdelim\" is not valid. Allowed values: comma, tab, or semicolon\n";
     595   }
     596   if (!IsPositiveInteger($Options{precision})) {
     597     die "Error: The value specified, $Options{precision}, for option \"-p --precision\" is not valid. Allowed values: > 0 \n";
     598   }
     599   if ($Options{quote} !~ /^(yes|no)$/i) {
     600     die "Error: The value specified, $Options{quote}, for option \"-q --quote\" is not valid. Allowed values: yes or no\n";
     601   }
     602   if ($Options{startcolmode} !~ /^(before|after)$/i) {
     603     die "Error: The value specified, $Options{quote}, for option \"--startcolmode\" is not valid. Allowed values: before or after\n";
     604   }
     605   if (!IsPositiveInteger($Options{detail})) {
     606     die "Error: The value specified, $Options{detail}, for option \"-d --detail\" is not valid. Allowed values: > 0\n";
     607   }
     608 }
     609