#!/usr/bin/perl
#-----------------------------------------------------------------
#
# File: qar.pl
#
# Copyright 2009 Programming Research Ltd
#
# Version: 2.1
#
# Written: C Waddingham, 10-August-2009
#
# Purpose: Wrapper to standard.prdf.generator.exe and others
#          and the xslt processor Xalan.
#
#          Reads command line options and derives the -cmaf option
#          specified.  Note this option is of special interest.
#
#          Having found the -cmaf option, the associated path is
#          stored as the path to the directory where .prdf.xml and
#          other output files are written (including the final 
#          report unless the user has specified -po qar::op=).
#
#          Therefore steps are:
#          1. Find output directory path
#          2. Execute generator(s)
#          3. Abort if generator(s) failed
#          4. Execute xsl transform template_xml.xsl to generate the
#             overall xml input file for the final xsl 
#             transformation
#          5. Abort if template_xml.xsl transform failed
#          6. Execute prdf2xhtml.xsl transform to generate final
#             xhtml report.  Or execute prdf2html.xsl to generate
#             .html report.
#          7. Abort if prdf2[x]html.xsl transform failed
#          8. Render the generated xhtml|html file in browser
#
# Usage: perl qar.pl %Q %L+ %P+ [ <po options> ]
#
#        This program is designed to run as an ActiveState compiled
#        program qar.exe on Windows and Cygwin.  On Unix platforms it is 
#        designed to be run by the native Perl interpreter.
#
#        Where the po options are:
#             -po qar::title=<document title for front page>
#             -po qar::project_name=<project name for front page>
#             -po qar::date_type=UK
#             -po qar::date_type=US
#             -po qar::logo_file=<path of file>
#             -po qar::no_appendix
#             -po qar::no_version
#             -po qar::output_path=<path for result files>
#             -po qar::op=<path for result files>
#             -po qar::report_format=<html|xhtml>
#             -po qar::report_type=<Code Review Report/Warning Listing/..>
#             -po "qar::report_type=Code Review Report" 
#             -po "qar::report_type=Compliance Report"  
#             -po "qar::report_type=General Report"
#             -po "qar::report_type=Quality Report" 
#             -po "qar::report_type=Suppression Report"
#             -po "qar::report_type=Template Usage Report"  
#             -po qar::user_reports_dir=<path of folder containing report programs>
#             -po qar::user_reports_name=<Users name for user reports folder>
#             -po qar::ud=<path of folder>
#             -po qar::viewing_program=<path of program>
#             -po qar::prdf2target_xsl_file=<path of file>
#
#        Note: When qar::viewing_program=none no rendering takes place.
#
#        Note: Particularly on Windows, the browser_program option
#        must be specified as the full name including .exe
#
#        The following output files are written at the output
#        directory path location (qar::output_path):
#
#             code review report.xhtml | .html | .xml
#             compliance report.xhtml | .html | .xml
#             quality report.xhtml | .html | .xml
#             suppression report.xhtml | .html | .xml
#             template usage report.xhtml | .html | .xml
#             general report.xhtml | .html | .xml
#
#-----------------------------------------------------------------

   BEGIN
   {
      our $VERSION = "2.1";

      #  Keep flushing the output buffer.
      #
      $| = 1;

      use integer;
      use strict;
   
      our $qar_dir_name = "QAR-$VERSION";
      our $qar = "qar";
      our $Xalan = "Xalan";
      our $xerces = "xerces";

      our $hr_standard_reports =
         { "Code Review Report"     => "any",
           "Compliance Report"      => "any",
           "Quality Report"         => "any",
           "Suppression Report"     => "any",
### Suppressed for QAR-2.1           "Template Usage Report"  => "any",
         };

      our $die_config_error = 10;
      our $die_program_termination = 19;
   
      # NB Potentially could be running on Cygwin, but as we use 
      # ActiveState perlapp to create the .exe, then we will always
      # be using MSWin on WIndows.
      #
      our $platform_is_windows = $^O =~ /^MSWin/o;
   
      our $config_separator;
      $config_separator = ";" if ( $platform_is_windows );
      $config_separator = ":" if ( ! $platform_is_windows );

      our $path_separator;
      $path_separator = "\\" if ( $platform_is_windows );
      $path_separator = "/"  if ( ! $platform_is_windows );
   }

   #-- Check for -version option ---------------------------------

   if ( grep( /^-(v|ver|version)$/oi, @ARGV ) > 0 )
   {
      print "Version $VERSION\n";

      exit;
   }

   #-- Get packages ---------------------------------------------------------
   #

   # Use WHICH( $0 ) to find the full path of the $0 file.
   #

   my $this_qar_program_full_path = WHICH( $0 );

   if ( $this_qar_program_full_path eq "" )
   {
      die "Program error deriving full program name for \"$0\".\n";
   }

   @INC = ( DIR_NAME( $this_qar_program_full_path ), @INC );

   require QAR_CMAF_DIR;
   require QAR_LOAD_TEMPLATE_XML_FILE;
   require QAR_OPT;
   require QAR_PATH;
   require QAR_UTIL;
   require QAR_XML_IO;
   require QAR_POST;

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

   our $executable_name = QAR_UTIL::STEM_NAME( $0 );

   QAR_UTIL::OPEN_TRACE( $executable_name );

   QAR_UTIL::TRACE( "Supplied arguments\n" );
   QAR_UTIL::TRACE( "$executable_name\n" );
   foreach my $arg ( @ARGV )
   {
      QAR_UTIL::TRACE( "$arg\n" );
   }
   QAR_UTIL::TRACE( "--------------------------------------------\n" );
   
   # During PARSE_COMMAND_LINE, DIEX can be called and for this to work
   # correctly it is necessary to know whether qar::viewing_program=none
   # has been supplied.  NB qar::viewing_program=none may not be read on the 
   # command line, or in -via files etc, until after the error has occured.
   #
   # Hence switch off DIEX while reading command line, and if on completion of
   # reading the command line any errors were found, then display the errors and terminate.
   # 
   # To accommodate this:
   # 1. Before calling QAR_OPT::PARSE_COMMAND_LINE() set $QAR_UTIL::diex_buffer_messages = true;
   #    And set $QAR_UTIL::diex_message_buffer = "";
   # 2. Execute PARSE_COMMAND_LINE() => all DIEX output is buffered.
   # 3. On completion of PARSE_COMMAND_LINE(), if ( $QAR_UTIL::diex_message_buffer ne "" ) then print and EXIT
   # 4. Set $QAR_UTIL::diex_buffer_messages = false
   #
   QAR_UTIL::SET_DIEX_BUFFERING_ON();

   if ( $#ARGV < 1 )
   {
      #-- Show usage -------------------------------------------------------
      #   
      QAR_UTIL::DIEX
          ( ": Usage:\n\n$executable_name " .
            "<product> -cmaf <file> [options...] f1...fn" .
            "\n$executable_name " .
            "<product> -cmaf <file> [options...] -list filelist" .
            "\n\ne.g.\n$executable_name \%Q \%L+ \%P+\n",
            $die_config_error );
   }

   QAR_CMAF_DIR::SET_QAR_HOME_DIR
      ( QAR_UTIL::APPLY_FINAL_SEP
          ( QAR_UTIL::DIR_NAME( QAR_UTIL::WHICH( $0 ) )
          ) . 
          QAR_UTIL::APPLY_FINAL_SEP( ".." )
      );

   QAR_UTIL::DIEX( "$qar_dir_name directory not found at " . 
                   QAR_CMAF_DIR::QAR_HOME_DIR() . "\n",
                   $die_config_error
       ) if ( ! QAR_UTIL::DIR_EXISTS( QAR_CMAF_DIR::QAR_HOME_DIR() ) );

   #-- Parse command line options -------------------------------------------
   #

   # Do actual parse.
   #
   QAR_OPT::PARSE_COMMAND_LINE( @ARGV );

   # Following nonsense block included to prevent message:
   # Name "QAR_UTIL::diex_message_buffer" used only once: possible typo at qar.pl ...
   if ( 0 )
   {
      $QAR_UTIL::diex_message_buffer = "";
   }

   my $buffered_messages = $QAR_UTIL::diex_message_buffer;

   QAR_UTIL::SET_DIEX_BUFFERING_OFF();

   if ( $buffered_messages ne "" )
   {
      QAR_UTIL::DIEX( $buffered_messages, $die_config_error );
   }

   #
   #-- Parse command line options complete ----------------------------------

   my $product = $ARGV[ 0 ];

   if ( ( lc( $product ) ne "qac" ) &&
        ( lc( $product ) ne "qacpp" ) &&
        ( lc( $product ) ne "qac++" )
      )
   {
      QAR_UTIL::DIEX( "Invalid product name $product specified.\n" .
            "Use QAC or QACPP.\n",
            $die_config_error );
   }

   QAR_UTIL::DIEX( "Environment variable " . uc( $product ) .
                   "BIN must be specified",
                   $die_config_error
       ) if ( ! exists( $ENV{ uc( $product ) . "BIN" } ) );

   QAR_UTIL::DIEX( "-po $qar\:\:report_type= must be specified",
         $die_config_error
       ) if ( QAR_OPT::REPORT_TYPE() eq "" );

   QAR_UTIL::DIEX( "-cmaf not specified.\n",
         $die_config_error
       ) if ( QAR_CMAF_DIR::CMAF_DIR() eq "" );

   # -cmaf parameter must be an absolute path
   #
   QAR_CMAF_DIR::SET_CMAF_DIR
      ( QAR_UTIL::GET_FILE_PATH_ABSOLUTE
         ( QAR_CMAF_DIR::CMAF_DIR() )
      );

   QAR_CMAF_DIR::SET_CMAF_DIR
      ( QAR_UTIL::REMOVE_FINAL_SEP
         ( QAR_UTIL::DIR_NAME
            ( QAR_UTIL::REMOVE_FINAL_SEP( QAR_CMAF_DIR::CMAF_DIR() )
            )
         )
      );
  
   if ( QAR_OPT::OUTPUT_PATH() eq "" )
   {
      QAR_OPT::SET_OUTPUT_PATH( QAR_CMAF_DIR::CMAF_DIR() );
   }

   QAR_UTIL::MK_PATH( QAR_OPT::OUTPUT_PATH() );

   # Ensure report_format is valid
   #
   QAR_UTIL::DIEX( "Illegal -po $qar\:\:report_format=" .
         QAR_OPT::REPORT_FORMAT(),
         $die_config_error
       ) if ( QAR_OPT::REPORT_FORMAT() !~ /^(html|xhtml|)$/oi );

   if ( QAR_OPT::VIEWING_PROGRAM() eq "" )
   {   
      if ( $platform_is_windows )
      {
         my $html_browser = LOOKUP_IN_WINDOWS_INI_FILE( $product );

         if ( $html_browser ne "" )
         {       
            QAR_OPT::SET_VIEWING_PROGRAM( $html_browser );
         }
         else
         {
            my $browser_command = QAR_UTIL::LOOKUP_DEFAULT_BROWSER_IN_REGISTRY();
   
            if ( $browser_command ne "" )
            {
               # Now have browser program and any arguments.
               # Get the first term i.e. the browser program.
               #
               my @args = QAR_UTIL::SPLIT_STRING_ALLOWING_SPECIFIC_QUOTED_TERMS
                             ( " \t", $browser_command, "\"" );
         
               if ( $#args > -1 )
               {
                  QAR_OPT::SET_VIEWING_PROGRAM( $args[ 0 ] );
               }
            }
         }
      }
      else
      {
         my $html_browser = LOOKUP_IN_UNIX_XQACRC_FILE( $product );
       
         QAR_OPT::SET_VIEWING_PROGRAM( $html_browser );
      }
   }   

   if ( ( QAR_OPT::VIEWING_PROGRAM() ne "" ) &&
        ( lc( QAR_OPT::VIEWING_PROGRAM() ) ne "none" ) &&
        ( QAR_UTIL::WHICH( QAR_OPT::VIEWING_PROGRAM() ) eq "" )
      )
   {
      print "Viewer \"" . 
            QAR_OPT::VIEWING_PROGRAM() .
            "\" not available, result will not be rendered.\n";
      QAR_OPT::SET_VIEWING_PROGRAM( "" );
   }

   if ( ( QAR_OPT::VIEWING_PROGRAM() ne "" ) &&
        ( lc( QAR_OPT::VIEWING_PROGRAM() ) ne "none" )
      )
   {
      # Give warning if viewing program is not compatible with report_format
      #
      if ( $platform_is_windows )
      {
   	 my $brow_prog = QAR_UTIL::BASE_NAME( QAR_OPT::VIEWING_PROGRAM() );

   	 if ( ( lc( $brow_prog ) eq "iexplore.exe" ) ||
   	      ( lc( $brow_prog ) eq "iexplore" )
   	    )
   	 {
   	    # Here with IE browser
            # This browser requires graphics to be in VML.
   	    #
            if ( QAR_OPT::REPORT_FORMAT() eq "" ) 
            {
               QAR_OPT::SET_REPORT_FORMAT( "html" );
            }
            elsif ( lc( QAR_OPT::REPORT_FORMAT() ) ne "html" ) 
            {
               print "++Warning++" .
                     " $qar\:\:report_format=" . 
                     QAR_OPT::REPORT_FORMAT() . 
                     " is not compatible with browser " .
                     QAR_OPT::VIEWING_PROGRAM() . 
                     ", graphics will not display.\n";

               my $ignore = QAR_UTIL::INPUT( "Press return to continue: " );
            }
   	 }
         else
         {
            # Here with any browser except IE, e.g. firefox
            # These browsers require graphics to be in SVG.
            #
            if ( QAR_OPT::REPORT_FORMAT() eq "" ) 
            {
               QAR_OPT::SET_REPORT_FORMAT( "xhtml" );
            }
            elsif ( lc( QAR_OPT::REPORT_FORMAT() ) ne "xhtml" ) 
            {
               print "++Warning++" .
                     " $qar\:\:report_format=" .
                     QAR_OPT::REPORT_FORMAT() . 
                     " is not compatible with browser " .
                     QAR_OPT::VIEWING_PROGRAM() . 
                     ", graphics will not display.\n";

               my $ignore = QAR_UTIL::INPUT( "Press return to continue: " );
            }
   	 }
      }
   }

   QAR_UTIL::DIEX( "-po $qar\:\:report_format= not specified\n",
         $die_config_error 
       ) if ( QAR_OPT::REPORT_FORMAT() eq "" );

   if ( ( uc( QAR_OPT::DATE_TYPE() ) ne "UK" ) &&
        ( uc( QAR_OPT::DATE_TYPE() ) ne "US" )
      )
   {
      QAR_UTIL::DIEX( "Invalid \"$qar\:\:date_type=" . QAR_OPT::DATE_TYPE() . "\".\n",
            $die_config_error );
   }

   my ( $day, $month, $year ) = QAR_UTIL::DATE_DD_MONTH_YYYY();

   my $date;
   if ( uc( QAR_OPT::DATE_TYPE() ) eq "UK" )
   {
      # Here when using UK date format
      #
      $date = "$day $month $year";
   }
   else
   {
      # Here when using US date format
      #
      $date = "$month $day $year";
   }

   $date = XSL_QUOTE( $date );

   $title = XSL_QUOTE( QAR_OPT::TITLE() );

   $project_name = XSL_QUOTE( QAR_OPT::PROJECT_NAME() );

   $logo_file = XSL_QUOTE( QAR_OPT::LOGO_FILE() );

   if ( exists( $$hr_standard_reports{ QAR_OPT::REPORT_TYPE() } ) )
   {
      if ( QAR_OPT::USER_REPORTS_DIR() eq "" )
      {
         QAR_OPT::SET_USER_REPORTS_DIR( QAR_PATH::STANDARD_REPORTS_DIR() );
      }
   }
         
   QAR_UTIL::DIEX( "-po $qar\:\:user_reports_dir= must be specified",
         $die_config_error
       ) if ( QAR_OPT::USER_REPORTS_DIR() eq "" );

   QAR_UTIL::DIEX( "Cannot find directory " . QAR_OPT::USER_REPORTS_DIR() . 
         " in -po \"$qar\:\:user_reports_dir=" . 
         QAR_UTIL::REMOVE_FINAL_SEP( QAR_OPT::USER_REPORTS_DIR() ) . "\"",
         $die_config_error
       ) if ( ! -d QAR_OPT::USER_REPORTS_DIR() );

   #-- Get $xalan_dir --------------------------------------------

   QAR_UTIL::DIEX( "$Xalan not found.\n" .
         "Expect $Xalan at " . EXPECTED_XALAN_LOCATION() . "\n",
         $die_config_error
       ) if ( ! QAR_UTIL::DIR_EXISTS( EXPECTED_XALAN_LOCATION() ) );

   my $xalan_dir = QAR_UTIL::APPLY_FINAL_SEP( EXPECTED_XALAN_LOCATION() );

   #-- Get $xerces_dir -------------------------------------------

   QAR_UTIL::DIEX( "$xerces not found.\n" .
         "Expect $xerces at " . EXPECTED_XERCES_LOCATION() . "\n",
         $die_config_error
       ) if ( ! QAR_UTIL::DIR_EXISTS( EXPECTED_XERCES_LOCATION() ) );

   my $xerces_dir = QAR_UTIL::APPLY_FINAL_SEP( EXPECTED_XERCES_LOCATION() );

   #-- Set PATH and LD_LIBRARY_PATH etc --------------------------

   $ENV{ PATH } = $xalan_dir . $config_separator . $xerces_dir . $config_separator .
                  $ENV{ PATH };

   if ( ! $platform_is_windows )
   {
      if ( exists( $ENV{ LD_LIBRARY_PATH } ) )
      {
         $ENV{ LD_LIBRARY_PATH } = $xalan_dir . "../lib" . $config_separator .
                                   $xerces_dir . "../lib" . $config_separator .
                                   $ENV{ LD_LIBRARY_PATH };
      }
      else
      {
         $ENV{ LD_LIBRARY_PATH } = $xalan_dir . "../lib" . $config_separator .
                                   $xerces_dir . "../lib" . $config_separator;
      }

      # This is for HP11i
      #
      if ( exists( $ENV{ SHLIB_PATH } ) )
      {
         $ENV{ SHLIB_PATH } = $xalan_dir . "../lib" . $config_separator .
                              $xerces_dir . "../lib" . $config_separator .
                              $ENV{ SHLIB_PATH };
      }
      else
      {
         $ENV{ SHLIB_PATH } = $xalan_dir . "../lib" . $config_separator .
                              $xerces_dir . "../lib";
      }
   }

   #-- Run the generators ----------------------------------------
   #
   print "Generating - Please wait...\n";

   RUN_GENERATORS();

   #-- Execute template_xml.xsl ---------------------------------
   #
   # e.g. 
   # Xalan -p op "'C:/Program Files/PRQA/QACPP-2.5/mcpp/projects/mcpp_examples/output/'" \
   #       -p title "'Code Review Document'" \
   #       -p project_name "'MCPP_Examples'" \
   #       -p date "'August 12 2009'" \
   #       -p no_version "false()" \
   #       -p no_appendix "false()" \
   #       -o "'C:/Program Files/PRQA/QACPP-2.5/mcpp/doc/Code Review Report.xml'" \
   #       "C:\Program files\prqa\QAR-1.0\standard_reports\Code Review Report.template.xml" \
   #       "C:\Program files\prqa\QAR-1.0\prdf\transforms\template_xml.xsl" 
   #

   my $program = "$xalan_dir$Xalan";

   # Workaround the fact that xsltproc creates a file with reduced set 
   # of access rights.  Whereas if file already exists, the access rights 
   # unchanged.
   #
   unlink PHASE_ONE_RESULT_FILE();
   open O, ">" . PHASE_ONE_RESULT_FILE();
   close O;

   my $cmaf_dir_uri = QAR_CMAF_DIR::CMAF_DIR();
   $cmaf_dir_uri = $cmaf_dir_uri . "/";
   $cmaf_dir_uri =~ s/ /\%20/go;
   $cmaf_dir_uri =~ s/\\/\//go;
   $cmaf_dir_uri = XSL_QUOTE( $cmaf_dir_uri );

   print "Writing " . PHASE_ONE_RESULT_FILE() . "\n";

   QAR_UTIL::TRACE
        ( "EXECUTING:\n$program -p op $cmaf_dir_uri -p title $title " .
          "-p project_name $project_name -p date $date -p " .
          "no_version " . QAR_OPT::NO_VERSION() . " -p no_appendix " . QAR_OPT::NO_APPENDIX() . 
          " -o " . QAR_UTIL::QUOTE_ARG( PHASE_ONE_RESULT_FILE() ) . " " . 
          QAR_UTIL::QUOTE_ARG( QAR_OPT::USER_REPORTS_DIR() . QAR_OPT::REPORT_TYPE() . ".template.xml" ) . " " .
          QAR_UTIL::QUOTE_ARG( QAR_CMAF_DIR::TRANSFORMS_DIR() . "template_xml.xsl" ) . "\n"
        );

   my $template_file = QAR_OPT::USER_REPORTS_DIR() . QAR_OPT::REPORT_TYPE() . ".template.xml";

   QAR_UTIL::DIEX( "Error $template_file not found.\n",
         $QAR_UTIL::die_config_error
       ) if ( ! -e $template_file );

   my $template_xml_xsl_file = QAR_CMAF_DIR::TRANSFORMS_DIR() . "template_xml.xsl";

   QAR_UTIL::DIEX( "Error $template_xml_xsl_file not found.\n",
         $QAR_UTIL::die_config_error
       ) if ( ! -e $template_xml_xsl_file );

   my $return_code = QAR_UTIL::EXECUTE_ARGS_WITH_RESULT
      ( $program,
        "-p", 
        "op",
        $cmaf_dir_uri,
        "-p",
        "title",
        $title,
        "-p",
        "project_name",
        $project_name,
        "-p",
        "date",
        $date,
        "-p",
        "no_version",
        QAR_OPT::NO_VERSION(),
        "-p",
        "no_appendix",
        QAR_OPT::NO_APPENDIX(),
        "-o",
        PHASE_ONE_RESULT_FILE(),
        $template_file,
        $template_xml_xsl_file
      );

   QAR_UTIL::DIEX( "$program failed. return code = $return_code\n",
         $return_code
       ) if ( $return_code != 0 );

   QAR_UTIL::DIEX( "$program failed to write:\n" .
         PHASE_ONE_RESULT_FILE() . "\n",
         $die_program_termination
       ) if ( ! -e PHASE_ONE_RESULT_FILE() );

   #-- Execute master_(x)html.xsl --------------------------------
   #
   # e.g. 
   # Xalan -o "QAR_OUTPUT_PATH/Code Review Report.xhtml" \
   #       -p logo_file "'logo.png'" \
   #       "QAR_OUTPUT_PATH/QAR_OPT::REPORT_TYPE.prdf.xml \
   #       "PRDF2TARGET_XSL_FILE" 
   #

   $program = "$xalan_dir$Xalan";

   # Workaround the fact that xsltproc creates a file with reduced set 
   # of access rights.  Whereas if file already exists, the access rights 
   # do not change.
   #
   unlink PHASE_TWO_RESULT_FILE();
   open O, ">" . PHASE_TWO_RESULT_FILE();
   close O;

   print "Writing " . PHASE_TWO_RESULT_FILE() . "\n";

   QAR_UTIL::TRACE
        ( "EXECUTING:\n$program -o " . QAR_UTIL::QUOTE_ARG( PHASE_TWO_RESULT_FILE() ) . 
          " -p logo_file $logo_file " .
          QAR_UTIL::QUOTE_ARG( PHASE_ONE_RESULT_FILE() ) . " " .
          QAR_UTIL::QUOTE_ARG( QAR_OPT::PRDF2TARGET_XSL_FILE() ) . "\n"
        );

   $return_code = QAR_UTIL::EXECUTE_ARGS_WITH_RESULT
      ( $program,
        "-o",
        PHASE_TWO_RESULT_FILE(),
        "-p", 
        "logo_file",
        $logo_file,
        PHASE_ONE_RESULT_FILE(),
        QAR_OPT::PRDF2TARGET_XSL_FILE()
      );

   QAR_UTIL::DIEX( "$program failed. return code = $return_code\n",
         $return_code
       ) if ( $return_code != 0 );

   QAR_UTIL::DIEX( "$program failed to write:\n" .
         PHASE_TWO_RESULT_FILE() . "\n",
         $die_program_termination
       ) if ( ! -e PHASE_TWO_RESULT_FILE() );

   chmod 0777, PHASE_TWO_RESULT_FILE();

   #-- Post process monolithic xhtml/html file into sections ----
   #

   my $rendered_file_name;

   if ( ( QAR_OPT::REPORT_FORMAT() eq "html" ) ||
        ( QAR_OPT::REPORT_FORMAT() eq "xhtml" )
      )
   {
     # DIVIDE_REPORT chops up the monolithic report (e.g. "Quality Report.xhtml")
     # into sections, and structures the report for a browser display with index
     # on the lhs, and the report sections rendered on the rhs.
     # DIVIDE_REPORT relies on intrinsic structure of the xml report being in
     # prdf sections optionally with dividers.  It reads the output of the XSLT
     # phase and performs manipulation of the xhtml|html in the report.xhtml,
     # or report.html file, using simple Perl processing only.
     # This entire 3rd phase of processing is performed in QAR_POST.
     # Can only do this for xhtml and html reports, and not pdf etc.
     #
     $rendered_file_name = QAR_POST::DIVIDE_REPORT( PHASE_TWO_RESULT_FILE() );
  
     if ( $rendered_file_name eq "" )
     {
        $rendered_file_name = PHASE_TWO_RESULT_FILE();
     }
   }
   else
   {
      # Here with non html/xhtml report, e.g. pdf or doc
      # Cannot do phase 3 processing of this.
      #
      $rendered_file_name = PHASE_TWO_RESULT_FILE();
   }

   # Ensure rendered_file_name is absolute (for browser(s))
   #
   $rendered_file_name = QAR_UTIL::GET_FILE_PATH_ABSOLUTE( $rendered_file_name  );

   #-- Execute viewer -------------------------------------------
   #
   if ( ( QAR_OPT::VIEWING_PROGRAM() ne "" ) &&
        ( lc( QAR_OPT::VIEWING_PROGRAM() ) ne "none" )
      )
   {
      if ( $platform_is_windows )
      {
         QAR_OPT::SET_VIEWING_PROGRAM( QAR_UTIL::SHORT_PATH_NAME( QAR_OPT::VIEWING_PROGRAM() ) );

         # For some unknown reason, the following won't work:
         # $return_code = QAR_UTIL::EXECUTE_ARGS_WITH_RESULT
         #    ( "start",
         #      QAR_OPT::VIEWING_PROGRAM(),
         #      $rendered_file_name
         #    );
         # So use following instead:

         # 9-Feb-2012: CR-14575: Create QAR-2.0.
         # TSR10940 (QAR fails to open browser) is repeated for the following
         # circumstance:
         #
         # Browser does not open:
         # C:\>start "dummy title" \
         #   C:\progra~1\google\chrome\application\chrome.exe \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         # Browser does not open:
         # C:\>start "dummy title" \
         #   "C:\progra~1\google\chrome\application\chrome.exe" \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         # Browser does not open:
         # C:\>start \
         #   C:\progra~1\google\chrome\application\chrome.exe \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         # Browser does open:
         # C:\>start "dummy title" \
         #   "C:\program files\google\chrome\application\chrome.exe" \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         # Browser does open:
         # C:\>start \
         #   "C:\progra~1\google\chrome\application\chrome.exe" \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         # Browser does open:
         # C:\>start \
         #   "C:\program files\google\chrome\application\chrome.exe" \
         #   "C:\program files\prqa\qacpp-3.0-r-RC3\mcppx\projects\mcppx_examples\output\Code Review Report-Main.xhtml"
         #
         #
         # Therefore use, no dummy title, and quote program regardless of whether needed.
         #

         my $cmd = "start \"" . 
                   QAR_OPT::VIEWING_PROGRAM() . 
                   "\" " . 
                   QAR_UTIL::QUOTE_ARG( $rendered_file_name );

         QAR_UTIL::TRACE( "EXECUTING:\n$cmd\n" );

         `$cmd`;
      }
      else
      {
         QAR_UTIL::TRACE
              ( "EXECUTING:\n" . QAR_OPT::VIEWING_PROGRAM() . " " . 
                QAR_UTIL::QUOTE_ARG( $rendered_file_name ) . "\n" );

         $return_code = QAR_UTIL::EXECUTE_ARGS_WITH_RESULT
            ( QAR_OPT::VIEWING_PROGRAM(),
              $rendered_file_name
            );
      }

      QAR_UTIL::DIEX( QAR_OPT::VIEWING_PROGRAM() . " failed. return code = $return_code\n",
            $return_code
          ) if ( $return_code != 0 );
   }

   # Finished
   #
   QAR_UTIL::EXIT( 0 );

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

sub PHASE_ONE_RESULT_FILE
{
   # Usage: $result_file = PHASE_ONE_RESULT_FILE();
   #
   return QAR_UTIL::APPLY_FINAL_SEP( QAR_OPT::OUTPUT_PATH() ) . QAR_OPT::REPORT_TYPE() . ".xml";
}

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

sub PHASE_TWO_RESULT_FILE
{
   # Usage: $result_file = PHASE_TWO_RESULT_FILE();
   #
   return QAR_UTIL::APPLY_FINAL_SEP( QAR_OPT::OUTPUT_PATH() ) . QAR_OPT::REPORT_TYPE() . "." . QAR_OPT::REPORT_FORMAT();
}

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

sub EXPECTED_XALAN_LOCATION
{
   # Usage: $expected_xalan_location = EXPECTED_XALAN_LOCATION();
   #
   return QAR_CMAF_DIR::QAR_HOME_DIR() .
          QAR_UTIL::APPLY_FINAL_SEP( $Xalan ) .
          QAR_UTIL::APPLY_FINAL_SEP( "bin" );
}

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

sub EXPECTED_XERCES_LOCATION
{
   # Usage: $expected_xerces_location = EXPECTED_XERCES_LOCATION();
   #
   return QAR_CMAF_DIR::QAR_HOME_DIR() .
          QAR_UTIL::APPLY_FINAL_SEP( $xerces ) .
          QAR_UTIL::APPLY_FINAL_SEP( "bin" );
}

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

sub RUN_GENERATORS
{
   # Usage: RUN_GENERATORS();
   #

   my ( $ar_spec_file_names, $spec_files_msgs ) =
      QAR_UTIL::GET_GLOB_FILE_LIST( QAR_PATH::QAR_PRDF_BIN_DIR() . "*.prdf.generator.spec" );

   my $hr_sections = QAR_LOAD_TEMPLATE_XML_FILE::LOAD_REPORT_TEMPLATE_XML_FILE();
   my @list_of_sections = ();

   foreach my $section ( "prdf:front_page", "prdf:main", "prdf:appendices", "prdf:version" )
   {
      my $ar_sections = $$hr_sections{ $section };
      if ( defined( $ar_sections ) )
      {
         push @list_of_sections, @$ar_sections;
      }
   }

   # At this point @ARGV supplied is still current.
   # No significant args have been updated

   my @argv = GET_ARGV_SETTINGS( @ARGV );

   foreach my $prdf_spec_name ( @$ar_spec_file_names )
   {
      my $match = 0;  # false
      my @spec_file = QAR_UTIL::LOAD_FILE( $prdf_spec_name );
      my $ar_param = [];

      LINE:
      foreach my $line ( @spec_file )
      {
         next LINE if ( $line =~ /^\s*$/o );
         next LINE if ( $line =~ /^\s*\#/o );

         $line =~ s/[\n\r]+$//o;

         my ( $section, $params ) = split( ":", $line, 2 );

         $section = lc( $section ) . ".prdf.xml";

         next LINE if ( grep( /^\Q$section\E$/, @list_of_sections ) == 0 );

         $match = 1;  # true

         next LINE if ( ! defined( $params ) );

         push @$ar_param, 
              QAR_UTIL::SPLIT_STRING_ALLOWING_SPECIFIC_QUOTED_TERMS
                 ( " \t", $params, "\"" );
      }
 
      if ( $match )
      {
         ( my $program = $prdf_spec_name ) =~ s/\.spec$//oi;

         my @args = GET_PO_FILTERED_ARGS( $ar_param, @ARGV );
 
         my @trace_args = ();
         foreach my $a ( @args )
         {
            push @trace_args, QAR_UTIL::QUOTE_ARG( $a );
         }
         QAR_UTIL::TRACE
              ( "EXECUTING:\n$program " . 
                join( " ", @trace_args, "-po", 
                      QAR_UTIL::QUOTE_ARG( "qar::report_type=" . 
                                           QAR_OPT::REPORT_TYPE()
                                         )
                    ) .
                "\n" );

         my $return_code = QAR_UTIL::EXECUTE_ARGS_WITH_RESULT
            ( $program,
              @args,
              "-po",
              "qar::report_type=" .
              QAR_OPT::REPORT_TYPE()
            );

         QAR_UTIL::DIEX( "$program failed. return code = $return_code\n",
               $return_code
             ) if ( $return_code != 0 );
      }
   }
}

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

sub GET_PO_FILTERED_ARGS
{
   # Usage: @args = GET_PO_FILTERED_ARGS( $ar_po_params, @argv );
   #
   my $ar_po_params = shift;
   my @argv = @_;

   my @args = ();
   my $i_arg = 0;

   ARG:
   while ( $i_arg <= $#argv )
   {
      my $arg = $argv[ $i_arg ];

      if ( $arg =~ /^-po/i )
      {
         my $opt;
 
         if ( lc( $arg ) eq "-po" )
         {
            last ARG if ( $i_arg >= $#argv );

            $opt = $argv[ $i_arg + 1 ];
            ++$i_arg;
         }
         else
         {
            $opt = substr( $arg, 3 );
         }

         foreach my $po_param ( @$ar_po_params )
         {
            if ( $opt =~ /^\Q$po_param\E.*$/i )
            {
               push @args, $arg, $opt;

               next ARG;
            }
         }
      }
      else
      {
         push @args, $arg;
      }
   }
   continue
   {
      ++$i_arg;
   }

   return @args;
}

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

sub GET_ARGV_SETTINGS
{
   # Usage: $bat_data = GET_ARGV_SETTINGS( @ARGV );
   #
   my @argv = @_;

   my $sep = "";
   my $bat_data = "";
   my $qac_pp_found = 0;  # false

   foreach my $arg ( @argv )
   {
      $qac_pp_found |= $arg =~ /^(qac|qacpp|qac\+\+)$/oi;

      if ( $qac_pp_found )
      {
        $bat_data .= $sep . QAR_UTIL::QUOTE_ARG( $arg ); 
        $sep = " ";
      }
   }

   return $bat_data;
}

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

sub LOOKUP_IN_UNIX_XQACRC_FILE
{
   # Usage: $html_browser = LOOKUP_IN_UNIX_XQACRC_FILE( $product );
   #
   # Reads the file $HOME/.xqac[pp]rc
   # If unsuccessful, reads the file $QAC[PP]PATH/.xqac[pp]rc
   #
   # and parses each file for 
   # 
   # * HTML Browser Settings
   # HTMLBrowser=firefox
   # 
   # etc.
   #
   my $product = shift;

   my $dot_file = ".x" . lc( $product ) . "rc";

   my $html_browser = "";

   if ( exists( $ENV{ 'HOME' } ) )
   {   
      my $full_dot_file = QAR_UTIL::APPLY_FINAL_SEP( $ENV{ 'HOME' } ) . $dot_file;
   
      if ( -e $full_dot_file )
      {
         $html_browser = PARSE_UNIX_CONFIG_FILE( $full_dot_file );
   
         if ( $html_browser ne "" )
         {
            return $html_browser;
         }
      }
   }

   # Here when not found in home file, so search product file

   if ( exists( $ENV{ uc( $product ) . "PATH" } ) )
   {
      my $full_dot_file = QAR_UTIL::APPLY_FINAL_SEP( $ENV{ uc( $product ) . "PATH" } ) . $dot_file;

      if ( -e $full_dot_file )
      {
        $html_browser = PARSE_UNIX_CONFIG_FILE( $full_dot_file );
   
         if ( $html_browser ne "" )
         {
            return $html_browser;
         }
      }
   }

   return $html_browser;
}

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

sub PARSE_UNIX_CONFIG_FILE
{
   # Usage: $html_browser = PARSE_UNIX_CONFIG_FILE( $filepath );
   #
   # Parses $filepath file for 
   # * HTML Browser Settings
   # HTMLBrowser=firefox
   # 
   # or whatever brwoser setting is
   #
   my $filepath = shift;

   my @file = QAR_UTIL::LOAD_FILE( $filepath );

   my $index = 0;

   while ( $index <= $#file )
   {
      my $line = $file[ $index ];

      if ( $line =~ /^\* HTML Browser Settings/o )
      {
         # Here when found section.
         # Get next line
         #
         if ( $index < $#file )
         {
            $line = $file[ ++$index ];

            if ( $line =~ /^HTMLBrowser=(.*)$/o )
            {
               return $1;
            }
         }

         # Here when section found but not followed by a valid setting
         #
         return "";
      }

      ++$index;
   }
      
   return "";
}

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

sub LOOKUP_IN_WINDOWS_INI_FILE
{
   # Usage: $html_browser = LOOKUP_IN_WINDOWS_INI_FILE( $product );
   #
   # Reads the file $QAC[PP]PATH/bin/qac[pp].ini
   # and parses for 
   # 
   # [EditorSetup]
   # InternalViewer=0
   # EditorPreference=C:\WINDOWS\system32\notepad.exe
   # EditorParameters=
   # HTMLBrowser=C:\Program Files\Mozilla Firefox\firefox.exe
   # UseDDECommunication=0
   # 
   my $product = shift;

   my $ini_file = lc( $product ) . ".ini";

   my $html_browser = "";

   if ( ! exists( $ENV{ uc( $product ) . "BIN" } ) )
   {
      my $full_ini_file = QAR_UTIL::APPLY_FINAL_SEP( $ENV{ uc( $product ) . "BIN" } ) . $ini_file;
   
      if ( -e $full_ini_file )
      {
         $html_browser = PARSE_WINDOWS_CONFIG_FILE( $full_ini_file );
      }
   }

   return $html_browser;
}

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

sub PARSE_WINDOWS_CONFIG_FILE
{
   # Usage: $html_browser = PARSE_UNIX_WINDOWS_FILE( $filepath );
   #
   # Reads the file $filepath and parses for 
   # 
   # [EditorSetup]
   # InternalViewer=0
   # EditorPreference=C:\WINDOWS\system32\notepad.exe
   # EditorParameters=
   # HTMLBrowser=C:\Program Files\Mozilla Firefox\firefox.exe
   # UseDDECommunication=0
   # 
   my $filepath = shift;

   my @file = QAR_UTIL::LOAD_FILE( $filepath );

   my $index = 0;

   while ( $index <= $#file )
   {
      my $line = $file[ $index ];

      if ( $line =~ /^\[EditorSetup\]/o )
      {
         # Here when found section.
         # Get section lines
         #
         while ( $index < $#file )
         {
            ++$index;

            $line = $file[ $index ];

            if ( $line =~ /^HTMLBrowser=(.*)$/o )
            {
               return $1;
            }
            elsif ( $line eq "" )
            {
               # Here when whole section read and not found
               #
               return "";
            }
         }

         # Here when section found but not followed by a valid setting
         #
         return "";
      }

      ++$index;
   }
      
   return "";
}

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

sub XSL_QUOTE
{
   # Usage: $xsl_param = XSL_QUOTE( $param );
   #
   my $param = shift;

   $param = "'$param'";

   if ( $platform_is_windows )
   {
      $param = "\"$param\"";
   }

   return $param;
}

#
# Following functions provided here purely and only to provide STEM_NAME
# and WHICH capability needed to derive $executable_name and path.
#

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

sub PLATFORM_IS_CYGWIN
{
   return $^O eq "cygwin";
}

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

sub BASE_NAME
{
   # Usage: $stem_and_extension = BASE_NAME( $path );
   #
   my $path = shift;

   my $separator = $path_separator;

   if ( ( $path eq "" ) or ( $path =~ /$separator$/ ) )
   {
      return "";
   }

   # Remove any leading DOS drive name
   #
   $path =~ s/^[a-z]+://oi;

   # Get all path elements
   #
   my @path_elements = split( /[\\\/]/o, $path );

   if ( $#path_elements >= 0 )
   {
      return $path_elements[ $#path_elements ];
   }
   else
   {
      return "";
   }
}

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

sub STEM_NAME
{
   # Usage: $stem = STEM_NAME( $path );
   #
   my $base_name = BASE_NAME( shift );

   my $dot_pos = rindex( $base_name, "." );

   if ( $dot_pos == -1 )
   {
      return $base_name;
   }
   elsif ( $dot_pos == 0 )
   {
      return "";
   }
   else
   {
      return substr( $base_name, 0, $dot_pos );
   }
}

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

sub DIR_NAME
{
   # Usage: $directory_of_file_path = DIR_NAME( $path );
   #
   my $path = shift;
   
   if ( $path eq "" )
   {
      return "";
   }

   my $n_pos = -1;
   my $keep_value;
   my $separator = $path_separator;
  
   # Allow both Unix and DOS separators
   #
   $path = NORMALISE_PATH_SEPARATORS( $path );

   LOOP:
   for (;;)
   {
      $keep_value = $n_pos;
      $n_pos      = index( $path, $separator, $n_pos + 1 );
      last LOOP if ( $n_pos == -1 );
   }
    
   return substr( $path, 0, $keep_value + 1 );
}

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

sub NORMALISE_PATH_SEPARATORS
{
   # Usage: $path_normalised = NORMALISE_PATH_SEPARATORS( $path );
   #
   # Swaps all occurences of \ or / for true path separator.
   #
   my $path = shift;
   
   my $separator = $path_separator;

   $path =~ s/\\/$separator/g;
   $path =~ s/\//$separator/g;

   return $path;
}

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

sub PWD
{
   require Cwd;

   return Cwd::getcwd();
}

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

sub WHICH
{
   # Usage: $full_filename = WHICH( $program_name );
   #
   # Returns absolute path to $program_name or "" if not found.
   #
   # NB this WHICH() is identical to GEN::WHICH() and has the limitation that
   # for Windows, if the supplied file is not an executable file, e.g. fred.txt,
   # then WHICH() will return the first full path of fred.txt in
   # PATH, whether it is executable or not.  It is the responsibility
   # of the user of WHICH() to ensure that a truely executable
   # filename is supplied.
   #
   # For Unix simply uses the 'which' utility.
   # For Windows:
   #    Gets all paths on PATH and for each path:
   #       Looks for $program_name . ( '.exe' | '.com' | '' ).
   #
   # NB For Windows, this sub will return the first file path in PATH
   # that matches $program_name . ( '.exe' | '.com' | '' )
   # This may be a non-executable file.
 
   my $program_name = shift;

   if ( $program_name eq "" )
   {
      return "";
   }

   # No need to lookup if $program_name is absolute.
   #
   elsif ( IS_FILE_PATH_ABSOLUTE( $program_name ) )
   {
      return EXECUTABLE_FULL_NAME( $program_name );
   }
   elsif ( $program_name ne BASE_NAME( $program_name ) )
   {
      # Here when path is relative
      # Just add to current dir
      #
      my $path = GET_FILE_PATH_ABSOLUTE( APPLY_FINAL_SEP( PWD() ) . $program_name );

      return EXECUTABLE_FULL_NAME( $path );
   }
   elsif ( ! $platform_is_windows )
   {
      my $prog      = BASE_NAME( $program_name );
      my $prog_stem = STEM_NAME( $program_name );

      my $exe_name  = $prog;

      my $path_delimiter = ":";
      my $path_list      = $ENV{ 'PATH' };

      # Append . to ensure that $program_name specified as ./name
      # can be found.
      #
      $path_list .= $path_delimiter . ".";

      my @paths = split( $path_delimiter, $path_list );
  
      foreach my $path ( @paths )
      {
         $path = APPLY_FINAL_SEP( $path );

         my $try_name = $path . $exe_name;

         if ( -f $try_name )
         {
            if ( PLATFORM_IS_CYGWIN() )
            {
               return $try_name;
            }
            elsif ( IS_FILE_EXECUTABLE( $try_name ) )
            {
               return $try_name;
            }
         }
      }
   }

   # Here when on Windows.
   #
   else
   {
      my $prog       = BASE_NAME( $program_name );
      my $prog_stem  = STEM_NAME( $program_name );

      my @exe_names = ( "$prog.exe", "$prog.com", $prog );

      my $path_delimiter = ";";
      my $path_list      = $ENV{ 'PATH' };
      $path_list         = "." . $path_delimiter . $path_list;

      my @paths = split( $path_delimiter, $path_list );
  
      foreach my $path ( @paths )
      {
         $path = APPLY_FINAL_SEP( $path );

         foreach my $exe_name ( @exe_names )
         {
            my $try_name = $path . $exe_name;

            if ( -f $try_name )
            {
               return EXECUTABLE_FULL_NAME( $try_name );
            }
         }
      }
   }

   return "";
}

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

sub IS_FILE_PATH_ABSOLUTE
{
   # Usage: $is_absolute_bool = IS_FILE_PATH_ABSOLUTE( $path );
   #
   # Returns true if path starts with \ or / or c:\
   #
   my $path = shift;

   if ( $platform_is_windows )
   {
      # Does $path start with '\' ?
      #
      if ( substr( $path, 0, 1 ) =~ /^[\\\/]$/o )
      {
         # Have absolute path
         #
         return 1;   # true
      }
      elsif ( substr( $path, 1, 2 ) =~ /^:[\\\/]$/o )
      {
         # Have absolute path
         #
	 return 1;   # true
      }
      else
      {
         # Here when path is relative.
         #
         return 0;  # false
      }
   }
   else
   {
      if ( PLATFORM_IS_CYGWIN() )
      {
   	 my $ch2_3 = substr( $path, 1, 2 );
   
   	 if ( ( $ch2_3 eq ":\\" ) ||
   	      ( $ch2_3 eq ":/" )
   	    )
   	 {
   	    # Have absolute path
   	    #
   	    return 1;
   	 }
      }

      # Platform is Unix
      #
      if ( index( "~/", substr( $path, 0, 1 ) ) != -1 )
      {
         # Have absolute path
         #
         return 1;   # true
      }
      else
      {
         # Here when path is relative.
         #
         return 0;  # false
      }
   }
}

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

sub EXECUTABLE_FULL_NAME
{
   # Usage: $full_program_name = EXECUTABLE_FULL_NAME( $program_name );
   #
   my $program_name = shift;

   my $executable_full_name = "";

   if ( ! $platform_is_windows )
   {
      $executable_full_name = $program_name;
   }
   else
   {
      if ( -e "$program_name.exe" )
      {
         $executable_full_name = "$program_name.exe";
      }
      if ( -e "$program_name.com" )
      {
         $executable_full_name = "$program_name.com";
      }
      elsif ( -e $program_name )
      {
         $executable_full_name = $program_name;
      }
   }

   if ( $executable_full_name eq "" )
   {
      return "";
   }

   ### $executable_full_name = GET_FILE_PATH_ABSOLUTE( $executable_full_name );
   ### $executable_full_name = EVALUATE_RELATIVE_PATHS( $executable_full_name );

   return $executable_full_name;
}

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

sub GET_FILE_PATH_ABSOLUTE
{
   # Usage: $path_adjusted = GET_FILE_PATH_ABSOLUTE( $path );
   #
   # If $path is relative, returns as absolute. 
   # If absolute returns unchanged.
   #
   # NB Cannot call trace() or entry() in this routine as TRACE will not
   # be initialised at this routine is called at startup.
   #
   my $path_param = shift;

   # Make sure uniform separators are used.
   #
   my $path = NORMALISE_PATH_SEPARATORS( $path_param );

   # Test path is legal
   #
   if ( substr( $path, 0, 1 ) eq ":" )
   {
      print "GET_FILE_PATH_ABSOLUTE: Illegal path \"$path_param\".\n";
      exit  $die_program_termination;
   }
  
   # Does $path start with '\' or '/'?
   # Allow DOS and Unix path separator of / or \
   #
   my $ch1 = substr( $path, 0, 1 );
   my $first_char_indicates_absolute = ( index( "\\\/" , $ch1 ) >= 0 );

   if ( $first_char_indicates_absolute )
   {
      # Have absolute path
      #
      return $path;
   }

   if ( $platform_is_windows )
   {
      my $ch2_3 = substr( $path, 1, 2 );

      if ( ( $ch2_3 eq ":\\" ) ||
           ( $ch2_3 eq ":/" )
         )
      {
         # Have absolute path
         #
         return $path;
      }
      else
      {
         # Here when path is relative.
         # Calc absolute path.
         #

         # First check case like  A:fred
         # Check that drive is the drive of the current dir.
         # If same then make relative to current dir
         # Otherwise don't know what current dir is for specified
         # drive, so fail program
         #

         my $current_dir_path = PWD();
         $current_dir_path = TRIM( $current_dir_path );

         my $abs_path;
         
         if ( substr( $path, 1, 1 ) eq ":" )
         {
            # Here with drive spec in supplied parameter ( $path_param ).
            #
         
            # Get that drive spec.
            #
            my ( $drive_specified, $path_specified, $junk ) = split( ":", $path, 3 );
         
            # Get the currently logged drive.
            #
            my ( $current_drive, $rest ) = split( ":", $current_dir_path, 2 );
         
            # Can only resolve a relative path of a drive which is the current drive.
            # e.g. can do:
            #   current drive is C:
            #   path is          C:fred
            # Cannot do
            #   current drive is C:
            #   path is          D:fred
            # as don't know the current directory on D:
            #
         
            if ( lc( $drive_specified ) eq lc( $current_drive ) )
            {
               $abs_path = APPLY_FINAL_SEP( $current_dir_path ) . $path_specified;
            }
            else
            {
               print "GET_FILE_PATH_ABSOLUTE: Cannot derive absolute " .
                     "path for \"$path_param\".\n";
               exit $die_program_termination;
            }
         }
         else
         {
            # Here with no drive spec in $path, i.e. $path_param.
            #
            $abs_path = APPLY_FINAL_SEP( $current_dir_path ) . $path;
         }
         
         # Remove any leading .\ pairs
         #
         ( $abs_path = $abs_path ) =~ s/^(\.\\)*//o;

         return $abs_path;
      }
   }
   else
   {
      if ( PLATFORM_IS_CYGWIN() )
      {
   	 my $ch2_3 = substr( $path, 1, 2 );
   
   	 if ( ( $ch2_3 eq ":\\" ) ||
   	      ( $ch2_3 eq ":/" )
   	    )
   	 {
   	    # Have absolute path
   	    #
   	    return $path;
   	 }
      }

      # Platform is Unix ( possibly Cygwin )
      #

      # Here when path is relative.
      # Calc absolute path.
      #

      # Test for path relative to login directory,
      # i.e. starts with '~'
      #
      if ( $ch1 eq "~" )
      {
         # Here with path relative to login directory
         # Get username e.g. ~fred/dir... or ~/dir... etc
         #
         my $home;
         ( $home = $path ) =~ s/^(~.*?)\/.*/$1/o;

         my $remainder = substr( $path, length( $home ) );

         # Evaluate the $home path
         #
         my $expanded_home_path = `ls -d $home`;
         $expanded_home_path = TRIM( $expanded_home_path );

         # Build the absolute path
         #
         return REMOVE_FINAL_SEP( $expanded_home_path ) . $remainder;
      }
      else
      {
   	 my $current_dir_path = PWD();
   	 $current_dir_path = TRIM( $current_dir_path );
   
   	 # Remove any leading ./ pairs
   	 #
   	 ( $path = $path ) =~ s/^(\.\/)*//o;
   
   	 return APPLY_FINAL_SEP( $current_dir_path ) . $path;
      }
   }
}

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

sub TRIM
{
   # Usage: $trimmed_string = TRIM( string );
   # Removes leading and trailing whitespace from string
   #
   my $str = shift;

   ( $str = $str ) =~ s/^\s*//o;
   ( $str = $str ) =~ s/\s*$//o;

   return $str;
}

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

sub APPLY_FINAL_SEP
{
   # e.g. $qabin = APPLY_FINAL_SEP( $QACBIN );
   #
   my $path = shift;

   my $separator = $path_separator;

   if ( $path !~ /\Q$separator\E$/ ) 
   {
      return $path . $separator;
   }
   else
   {
      return $path;
   }
}

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

sub REMOVE_FINAL_SEP
{
   # Usage: $path = REMOVE_FINAL_SEP( $file_path_str );
   #
   my $file_path_str = shift;
   
   if ( $file_path_str eq "" ) 
   {
      return "";
   }

   my $separator = $path_separator;
  
   my $last    = length( $file_path_str ) - 1;
   my $last_ch = substr( $file_path_str, $last, 1 );

   while ( $last_ch eq $separator )
   {
      --$last;

      if ( $last eq -1 )
      {
         return "";
      }

      $last_ch = substr( $file_path_str, $last, 1 );
   }

   return substr( $file_path_str, 0, $last + 1 );
}

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

sub IS_FILE_EXECUTABLE
{
   # Usage: $boolean_executable= IS_FILE_EXECUTABLE( $filename );
   #
   my $filename = shift;

   return ( -x $filename ) || ( -X $filename );
}

#-- eof qar.pl --- ---------------------------------------------
