#!/usr/local/bin/perl -w

# =============================================================================
# csvdiff.pl - compare two csv files, Copyrigth 2006 & 2007  - Roland Schmitz
# =============================================================================
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation version 2.1 of the License.
#
# This program is distributed because i hope that it will be usefull, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version
# 2.1 for more details.
# =============================================================================
# csvdiff is a Perl script to diff/compare two csv files with the possibility
# to select the separator. Differences will be shown like: "Column XYZ in
# record 999" is different. After this, the actual and the expected result for
# this column will be shown.
# =============================================================================
# Contact: r-sch AT users DOT sourceforge DOT net
# =============================================================================
# Version 1.7  - created: 2009-02-XX by Roland Schmitz, based on the feedback
#                                    of Pierre Besnard from France
#                changed: YYYY-MM-DD by xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
#            last change: YYYY-MM-DD by xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# =============================================================================

#perl csvdiff.pl -a act.csv -e exp.csv -s ";" -c col_names.csv -k "2" |more
#perl -d:ptkdb csvdiff.pl -a act.csv -e exp.csv -s ";" -c col_names.csv -k "2"

use strict;
use warnings;

use Getopt::Std;
use Scalar::Util qw[ looks_like_number ];
use Data::Dumper;

# ----------------------
# --- used variables ---
# ----------------------
our ($opt_s, $opt_S, $opt_a, $opt_e, $opt_c, $opt_k, $opt_t, $opt_i, $opt_f);
our ($opt_g, $opt_d, $opt_D, $opt_h, $opt_I, $opt_L, $opt_n, $opt_v);
our ($opt_V, $opt_T, $opt_C, $opt_l, $opt_r, $debug);

my ($s_exp_file, $s_act_file, $s_col_file);

my $s_version = "1.7";    # Version von csvdiff
my $s_use_key = 1;        # Vergleich mit (=1) oder ohne (=0) Key
my $s_key_num = 1;        # Key numerisch (=1) oder String/Feldname (=0)
my $s_key_cols = 0;       # Welche Spalte enthaelt den "unique key", Zaehlung
                          #  beginnt bei 0
my $s_fadeout_cols = "";  # Welche Spate(n) sollen nicht mit verglichen werden
my $s_ratio_cols = "";    # Fuer welche Spate(n) sollen ein Verhaeltnis und
                          #  Differenz berechnet werden
my $s_num_cols = "";      # Welche Spalten sollen numerisch verglichen werden
my $s_num_cols_invers = 0;# Sollen die uebergebenen Spalten numerisch verglichen
                          #  werden (0), oder alle anderen Spalten ausser den in
                          #  $s_num_cols abgelegten Spalten (1).
my @a_num_cols = ();      # Aufteilen der in $s_num_cols uebergebene(n) Felder
                          #  in einzelne Werte
my $s_joined_key = "";    # Alle Keys zu einem String zusammenfassen, um diese
                          #  dann als Schluessel der Hashs zu verwenden
my $s_separator = ',';    # Feldtrenner
my $s_d_separator = '.';  # Dezimaltrennzeichen
my $s_t_separator = "";   # Tausendertrennzeichen
my $s_verbose = 0;        # csvdiff "etwas" geschwaetziger machen
my $s_sort_data = 0;      # Dateien sortieren vor vergleich?
my $s_ignore = 0;         # Gross - Kleinschreibung beim Vergleich beachten(=0),
                          #  oder nicht (=1)
my $s_ignore_space = 0;   # Doppelte Leerzeichen ignorieren
my %h_key_act;            # Hash der aktuellen Daten
                          #  Schluessel = Keyspalte aus
                          #  csv Datei, Wert = Zeilennummer
my %h_key_exp;            # Hash der erwarteten Daten
                          #  Schluessel = Keyspalte aus csv Datei
                          #  Wert = Zeilennummer
my %h_key_all;            # Hash das alle vorkommenden Key's enthaelt
                          #  Wert = wie oft kommt der jeweilige Key in beiden
                          #  Dateien vor
my @a_records=();         # Array das mehrfach verwendet wird, um einen
                          #  Record am Trenner in einzelne Felder aufzuspalten
my @a_key_cols=();        # Array in das die uebergebenen Keys aufgespalten
                          #  werden
my @a_fadeout_cols=();    # Array in das die uebergebenen "Fadeout" Spalten
                          #   aufgespalten werden
my @a_ratio_cols=();      # Array in das die uebergebenen "Ratio" Spalten
                          #   aufgespalten werden
my @a_lines_act=();       # Array das alle Zeilen aus dem ACT-File aufnimmt
my @a_lines_exp=();       # Array das alle Zeilen aus dem EXP-File aufnimmt
my @a_lines_col=();       # Array das alle Zeilen aus dem COL-File aufnimmt
                          #  eigentlich unnoetig, da COL nur eine Zeile
                          #  enthalten darf, so sieht's aber "symetrischer" aus
my @a_col_names=();       # Array das die Spaltennamen aufnimmt
my $s_diff_counter=0;     # Variable die die Anzahl der unterschiedlichen Zeilen
                          #  zaehlt
my $coloured_output=0;    # Soll der output diff aehnlich sein, aber mit Farbe?
my $show_pos_length=15;   # min. Laenge einer Zeichenkette, ab der die Position
                          #  des ersten Unterschieds angezeigt wird
my $s_no_diff_per_record=0; #Der wievielte Unterschied wurde pro Zeile gefunden?
                            #  um Leerzeile einzuf�gen zwischen 2 Unterschieden
my $s_compare_message=""; # Hierin wird die Warnung aus cmp_data aufgenommen,
                           #  wenn ein Feld das numerisch verglichen werden soll
                           #  nicht numerische Zeichen enthaelt


# Einige temporaere Variablen, die z.B. als Counter verwendet werden
my $s_line_counter;      # Zeilenzaehler, wird mehrfach verwendet
my $s_field_counter;     # Feldzaehler, wird mehrfach verwendet
my ($s_trim, $s_zeile, $s_zeile_join, $s_i, $s_tmp_counter, $s_key, $s_diff);
my ($s_col, $s_tmp_col, $s_tmp_var);
my (@a_trim_act, @a_trim_exp);
my (@a_tmp_lines, @a_tmp_records, $s_tmp_empty_lines);

# === Funktion ================================================================
# Name:           buggy
# Beschreibung:   Ausgabe von Debuginformationen auf STDERR
# Beispiel:       &buggy(__PACKAGE__, __LINE__, __FILE__, "Kommentar");
# Parameter:      Paketname
#                 Zeilennummer
#                 Dateiname
#                 zusaetzlicher Kommentar
# Rueckgabe Wert: -
# =============================================================================
sub buggy {
  if($debug){
    my $index=0;
    print STDERR "\n-------------------------\n";
    foreach(@_){
      $index++;
      if(!$_)
        { $_ = "" }
      if($index==1)
        { print STDERR "Package: " . $_ ."\n" }
      if($index==2)
        { print STDERR "Line:    " . $_ ."\n" }
      if($index==3)
        { print STDERR "File:    " . $_  ."\n" }
      if($index==4)
        { print STDERR "Comment: " . $_  ."\n" }
    }
    print "-------------------------\n";
  }
}
# === Ende von Funktion buggy =================================================

# === Funktion ================================================================
# Name:           usage
# Beschreibung:   Ausgabe der "Onlinehilfe"
# Parameter:      -
# Rueckgabe Wert: -
# =============================================================================
sub usage {
#  print "@_\n";
  print "\nUsage:\n";
  print "======\n";
  print "  Parameters:\n";
  print "    -e <File1>             Expected Result\n";
  print "    -a <File2>             Actual Result\n\n";
  print "All folowing Parameters ar optional!\n";
  print "    -c <File3>             Columnames in csv format (in only one Line!)\n";
  print "    -C                     Columnames in first line of both result files\n";
  print "    -s <Separator>         Fieldseparator (default=,)\n";
  print "    -S <Decimalseparator>  Decimalseparator (default=.)\n";
  print "                           it can be . or ,\n";
  print "    -T <1000 Separator>    thousands separator (default=empty)\n";
  print "                           it can be empty (no separator) or . or ,\n";
  print "    -k <KeyCol(s)>         Keycolumn(s)\n";
  print "    -f <Col(s)>            Fade-out column(s)\n";
  print "    -n <Col(s)>            This column(s) should be numerical compared,\n";
  print "                           if the columnlist starts with - all columns EXPECT theese\n";
  print "                           are numerical compared, e.g. \"-2;3;5\"!\n";
  print "    -N                     All numeric columns are compared numerical, the rest will\n";
  print "                           be compared caracter by caracter. -n will be ignored if -N is set!\n";
  print "    -r <Col(s)>            A ratio and difference of these columns will be computed\n";
  print "                           NOT with option -d\n";
  print "                           Ratio = Expected/Actual   Difference = Expected-Actual\n";
  print "    -L <Length>            Min. length to show exact position of first difference,\n";
  print "                           optional (default=15)\n";
  print "    -h                     print online help\n";
  print "    -D                     Debug mode\n";
  print "    -d                     coloured Output like diff, ONLY for Linux\n";
  print "    -t                     Trim space's\n";
  print "    -i                     Ignore-case\n";
  print "    -I                     Ignore multiple space's\n";
  print "    -g                     Grade/sort data before comparision, this has only\n";
  print "                           effect when there are no key columns\n";
  print "    -v                     Print csvdiff version and quit\n";
  print "    -V                     Verbose mode\n";
  print "    -l                     Show complete lines which are compared, otherwise show\n";
  print "                            only linenumbers\n";
  print "\n";
  print "If your key consists of multiple fields/columns use for example\n";
  print "  -k \"1;4;2\", column one four and two are the unique key.\n";
  print "\n";
  print "Multiple keys has to be separated by the fieldseparator defind with Option \"-s\".\n";
  print "\n";
  print "If a column is min. $show_pos_length (default) characters long, the exact position of the\n";
  print "  first difference will be indicated by a ^ symbol, you can specify this by the\n";
  print "  \"-L\" option. If you don't want to use this feature, use \"-L 0\"!\n";
  print "\n";
  print "If TAB is your column serarator use -s \"\\t\" as separator.\n";
  print "Caution: empty lines/fields are displayed like: \">  <\" (note the two blanks)!\n";
  print "\nColumn and record count starts with 1!\n";
  print "\n";
  print "\nExample: $0 -e exp.csv -a act.csv -s \";\" -c col_names.csv -k 2 -f \"11;12\"\n";
}
# === Ende von Funktion usage =================================================

# === Funktion ================================================================
# Name:           trim
# Beschreibung:   Fuehrende nund nachlaufende Leerzeichen aus Strings oder
#                 Arrays entfernen
# Beispiel:       $string = trim($string);
#                 @many   = trim(@many);
# Parameter:      Zeichenkette oder Array die/das "getrimmt" werden soll
# Rueckgabe Wert: Zeichenkette oder Array je nach Aufruf, ohne fuehrende/
#                 nachlaufende Leerzeichen
# =============================================================================
sub trim {
  &buggy(__PACKAGE__, __LINE__, __FILE__, "Subroutine \"trim\" Anfang") if($debug);
  my @out = @_;
  for (@out) {
    #s/^\s*(.*?)\s*$/$1/g; #schlechte Loesung, dauert ca. 3 mal so lange!
    #s/^\s+//;
    #s/\s+$//;
    s/^ +//;
    s/ +$//;
  }
  return wantarray ? @out : $out[0];
}
# === Ende von Funktion trim ==================================================

# === Funktion ================================================================
# Name:           trim_delete_space
# Beschreibung:   Hier werden je nach angegebener Option fuehrende und nach-
#                 laufende Leerzeichen, sowie doppelte/mehrfache leerzeichen
#                 innerhalb der Felder entfernt, bzw. duch ein Leerzeichen
#                 ersetzt
# Beispiel:       @a_trimed_array = trim_delete_space(@a_array2trim);
# Parameter:      @a_array2trim   = Array, das die Daten (Records) enthaelt die
#                                   "getrimmt" werden sollen
# Rueckgabe Wert: @a_trimed_array = um "unnoetige" Leerzeichen bereinigtes
#                                   Array
# =============================================================================
sub trim_delete_space {
  my @tmp_array  = @_;
  my @a_records  = ();
  my @a_tmp_trim = ();

  foreach my $s_zeile (@tmp_array) {

    #Ein oder mehrmaliges Leerzeichen durch ein Leerzeichen ersetzen
    if($s_ignore_space) {
      $s_zeile =~ s/ +/ /g;
    }

    #Fuehrende und nachlaufende Leerzeichen abschneiden
    if($s_trim) {
      $s_zeile =~ s/^\s*(.*?)/$1/g; #Leerzeichen vor dem ersten Feld
      $s_zeile =~ s/\s*(\Q$s_separator\E)\s*/$1/g; #Leerzeichen um den Feldtrenner herum ersetzen
      $s_zeile =~ s/(.*?)\s*$/$1/g; #Leerzeichen nach dem letzen Feld
    }

    #Bearbeitete Zeilen/Records wieder in ein Array schreiben
    push(@a_tmp_trim, $s_zeile);
  }

  return @a_tmp_trim;
}
# === Ende von Funktion trim_delete_space =====================================

# === Funktion ================================================================
# Name:           cmp_data
# Beschreibung:   Vergleichen von zwei Feldern
# Beispiel:       $s_diff = cmp_data($rec1, $rec2, $akt_feld);
# Parameter:      $rec1 = Zeichenkette die den Datenrecord aus der ersten
#                         Datei enthaelt
#                 $rec2 = Zeichenkette die den Datenrecord aus der zweiten
#                         Datei enthaelt
#                 $akt_feld = Aktuelle Feldnummer, wird benoetigt um pruefen zu
#                             koennen, ob numerisch verglichen werden soll
# Rueckgabe Wert: 0 wenn's gleich ist, 1 wenn die Felder unterschiedlich sind
# =============================================================================
sub cmp_data {
  my ($s_tmp_data1, $s_tmp_data2, $s_tmp_field_no) = @_;
  &buggy(__PACKAGE__, __LINE__, __FILE__, "Subroutine \"cmp_data\" \ns_tmp_data1=$s_tmp_data1 \ns_tmp_data2=$s_tmp_data2") if($debug);
  my $s_tmp_cmp_res = undef;

  #Verschoben nach read_csv
  #if($s_ignore) {
  #  # Gross - Kleinschreibung ignorieren
  #  $s_tmp_data1 = lc($s_tmp_data1);
  #  $s_tmp_data2 = lc($s_tmp_data2);
  #}

  #Erstmal wird grundsaetzlich ein String Vergleich gemacht!
  #siehe prelre:
    #\A = Match only at beginning of string
    #\Z = Match only at Ende von string, or before newline at the end
    #\z = Match only at Ende von string
    #\E = end case modification (think vi)
    #\Q = quote (disable) pattern metacharacters till \E
  if($s_tmp_data1 =~ /\A\Q$s_tmp_data2\E\z/) {
    $s_tmp_cmp_res = 0;
  }
  else {
    $s_tmp_cmp_res = 1;
  }

  #hier wird der komplette Record miteinander verglichen
  if($s_tmp_data1 =~ /\Q$s_separator\E/ or $s_tmp_data2 =~ /\Q$s_separator\E/) {
    #der komplette Record wird ausschliesslich per String Vergleich verglichen
    #der String Vergleich wurde bereits durchgef�hrt!
    ;
  }

  #hier werden einzelne Felder miteinander verglichen
  else {
    #ACHTUNG, $s_tmp_cmp_res wurde auf jeden Fall schon durch den String
    #Vergleich mit einem Ergebnis belegt!!!

    #hier wird mal wieder 1 addiert, da in csvdiff die erste Spalte, Zeile, ...
    #die 1 ist und nicht die 0!
    $s_tmp_field_no += 1;

    #gibt es ueberhaupt Spalten die numerisch verglichen werden sollen?
    #wenn nein, ok dann wurde der Stringvergleich bereits gemacht
    if($s_num_cols){

      my $s_cmp_mode = "";
      #Alle Spalten sollen numerisch verglichen werden AUSSER die in
      #@a_num_cols enthaltenen
      if($s_num_cols_invers) {
        #pr�fen ob die aktuelle Spalte NICHT im Array der Spalten ist die
        #numerisch verglichen werden sollen
        if(!grep{ $a_num_cols[$_] eq $s_tmp_field_no } 0..$#a_num_cols) {
          $s_cmp_mode = "numeric";
        }
        else {
          $s_cmp_mode = "text";
        }
      }
      #Die in @a_num_cols enthaltenen Spalten sollen numerisch verglichen
      #werden
      else {
        #pr�fen ob die aktuelle Spalte im Array der Spalten ist die numerisch
        #verglichen werden sollen
        if(grep{ $a_num_cols[$_] eq $s_tmp_field_no } 0..$#a_num_cols) {
          $s_cmp_mode = "numeric";
        }
        else {
          $s_cmp_mode = "text";
        }
      }

      #Ueberpruefen ob die aktuelle Spalte numerisch verglichen werden soll
      if( $s_cmp_mode eq "numeric" ) {

        my $regex1 = '[0-9' . quotemeta($s_t_separator) . quotemeta($s_d_separator) . '\+-]+';
        my $regex2 = qr{ ^ $regex1 $ }x;

        #Pruefen ob nicht erlaubte Zeichen enthalten sind
        if( $s_tmp_data1 !~ $regex2 or $s_tmp_data2 !~ $regex2) {
          #wenigstens einer der beiden Werte ist nicht numerisch, sollte es aber sein

          $s_tmp_cmp_res = 1;
          $s_compare_message  = sprintf ("  Warning: column $s_tmp_field_no should be compared numerical, but the values aren't numerical!\n");
          $s_compare_message .= sprintf ("    It could help to provide a decimal separator.\n") if (!$s_d_separator);
          $s_compare_message .= sprintf ("    It could help to provide a 1000 separator.\n") if (!$s_t_separator);

          #Ungueltige(s) Zeichen ausgeben
          my ($s_not_valid_char1, $s_not_valid_char2, $s_not_valid_char);
          ($s_not_valid_char1 = $s_tmp_data1) =~ s/$regex1//g;
          ($s_not_valid_char2 = $s_tmp_data2) =~ s/$regex1//g;
          $s_not_valid_char = $s_not_valid_char1 . $s_not_valid_char2;
          my %seen;
          $s_not_valid_char =~ s/(.)/$seen{$1}++ ? "" : $1/gse;
          $s_compare_message .= sprintf ("    Following character(s) occured: \"$s_not_valid_char\", valid ones are \"+-0123456789$s_t_separator$s_d_separator\"!\n");

        }

        else {
          #Tausendertrennzeichen "entsorgen" (temporaer)
          s/(\d)\Q$s_t_separator\E(?=\d{3})/$1/g for ($s_tmp_data1, $s_tmp_data2);
          #Dezimaltrenner auf Perl internes Format umbiegen (temporaer)
          s/\Q$s_d_separator\E/\./ for ($s_tmp_data1, $s_tmp_data2);
          #Sollte das Vorzeichen hinter der Zahl stehen, wird es hier nach
          #vorne geholt (temporaer)
          s/(.*)([-\+])$/$2$1/ for ($s_tmp_data1, $s_tmp_data2);

          #beide Werte sind numerisch
          if(looks_like_number($s_tmp_data1) and looks_like_number($s_tmp_data2)) {
            #Vergleiche Spalte(n) numerisch
            if(($s_tmp_data1 - $s_tmp_data2) == 0) {
              #Werte sind numerisch identisch
              $s_tmp_cmp_res = 0;
            }
            else {
              #Werte sind numerisch nicht identisch
              $s_tmp_cmp_res = 1;
            }
          }
          #beide Felder sind leer
          elsif($s_tmp_data1 eq "" and $s_tmp_data2 eq "") {
            #Werte sind numerisch identisch
            $s_tmp_cmp_res = 0;
          }
          else {
            print STDERR "This should never occour, something ist wrong with > $s_tmp_data1 <\n";
            print STDERR "or > $s_tmp_data2 <! => exit 1\n";
            exit 1;
          }
        }
      }
      #die aktuelle Spalte soll nicht numerisch verglichen werden!
      else {
        ; # String Vergleich ist schon gemacht, siehe oben
      }
    }
    #es gibt keine Spalten die numerisch verglichen werden sollen!
    else {
      ; #der String Vergleich ist schon gemacht, siehe oben
    }

  }

  return $s_tmp_cmp_res;
}
# === Ende von Funktion cmp_data ==============================================

# === Funktion ================================================================
# Name:           read_csv
# Beschreibung:   CSV Datei in Array einlesen, und Anzahl der Leerzeilen
#                 ermitteln, ggf. auch Felder ausblenden
# Beispiel:       ($leerzeilen, @datenzeilen) = read_csv($csv_datei);
# Parameter:      $csv_datei   = Name der einzulesenden CSV Datei
# Rueckgabe Wert: $leerzeilen  = Anzahl der Leerzeilen in der Datei
#                 $erste_zeile = Wenn opt_C gesetzt ist, enth�lt die erste
#                                Zeile die Spaltennamen
#                 @datenzeilen = Alle Zeilen die nicht leer sind
# =============================================================================
sub read_csv {
  my ($tmp_filename) = $_[0];
  #&buggy(__PACKAGE__, __LINE__, __FILE__, "Subroutine \"read_csv\" Anfang") if($debug);

  #Rueckgabewerte (-variablen) definieren
  my $s_tmp_empty_lines = 0; #wieviele Leerzeilen enthaelt die Datei
  my $s_tmp_first_line = ""; #wird fuer Spaltennamen verwendet, wenn vorhanden
  my @a_tmp_lines = ();      #hierin werden die Daten an das Hauptprogramm
                             #zurueckgegeben

  my $s_tmp_line_counter = 0;

  open( CSV_FILE, "<$tmp_filename" ) or die "File $tmp_filename could not be opened: $!";
    my $re_compiled = qr/ *(\Q$s_separator\E) */; #Vorkomplierter regulaerer
                                                  #Ausdruck, macht's schneller.
                                                  #siehe: perldoc perlop
    while (<CSV_FILE>) {
      $s_tmp_line_counter++;
      chomp $_;

      #Wenn die erste Zeile die Spaltennamen enthaelt, wird die komplette Zeile
      #der R�ckgabevariablen $s_tmp_first_line zugewiesen
      if($s_tmp_line_counter==1 && $opt_C) {
        $s_tmp_first_line = $_;
        next;
      }
      #Wenn die Zeile leer ist, wird sie gez�hlt, und die Verarbeitung geht mit
      #der naechsten Zeile weiter
      if(!$_) {
        $s_tmp_empty_lines++;
        next;
      }
  #    next unless $_; #Leerzeilen werden ignoriert

      #Trim, Vor- und Nachlaufende Leerzeichen entfernen
      if($opt_t) {
        #Leerzeichen um den Feldtrenner herum loeschen
        #s/ *(\Q$s_separator\E) */$1/g;
        s/$re_compiled/$1/g;

        #Leerzeichen vor dem ersten und nach dem letzten Feld loeschen
        #s/^ *(.*?) *$/$1/;
        s/^ +//;
        s/ +$//;

        #Wenn ganz allgemein Whitespace's geloescht werden sollen sind folgende
        #zwei Zeilen zu aktivieren
        #s/\s*(\Q$s_separator\E)\s*/$1/g;
        #s/^\s*(.*?)\s*$/$1/;
      }

      #Doppelte Leerzeichen zu einem machen
      if($opt_I) {
        s/ +/ /g;
      }

      # Gross - Kleinschreibung ignorieren
      if($opt_i) {
        $_ = lc($_);
      }

      #Spalte(n) ausblenden
      if($opt_f) {
        #Zeile am Trenner aufspalten
        @a_tmp_records = split(/\Q$s_separator\E/, $_);
        foreach my $s_col (@a_fadeout_cols) {
          my $s_tmp_col = $s_col-1;
          # Pruefen ob die angegebene Spalte existiert
          if(exists $a_tmp_records[$s_tmp_col]) {
            # Spalte existiert, Inhalt durch festen String ersetzen
            $a_tmp_records[$s_tmp_col] = "Fade out for comparing";
          }
          else {
            print "Error: The fadeout column: $s_col did not exist => exit 1\n";
            exit 1;
          }
        }
        #print Dumper(@a_tmp_records);
        #Array wieder zum String machen
        my $s_tmp_zeile_join = join("$s_separator", @a_tmp_records);
        #aktuelle Zeile ans Ende des Arrays haengen
        push(@a_tmp_lines, $s_tmp_zeile_join);
      }
      #Nichts wird ausgeblendet
      else {
        #aktuelle Zeile einfach ans Ende des Arrays haengen
        push(@a_tmp_lines, $_);
      }
    }
  close( CSV_FILE );
  #print Dumper(@a_tmp_lines);

  return ($s_tmp_empty_lines, $s_tmp_first_line, @a_tmp_lines);
}
# === Ende von Funktion read_csv ==============================================

# === Funktion ================================================================
# Name:           compare_for_sort
# Beschreibung:   Daten fuer das sortieren (mit sort) vergleichen
# Beispiel:
# Parameter:
# Rueckgabe Wert: Die beiden uebergebenen Variablen werden sortiert
#                 zurueckgegeben
# =============================================================================
sub compare_for_sort {
    return $a <=> $b if(looks_like_number($a) and looks_like_number($b));
    return lc $a cmp lc $b;
}
# === Ende von Funktion compare_for_sort ======================================

# === Funktion ================================================================
# Name:           diff_pos
# Beschreibung:   Position ermitteln, an der der erste Unterschied auftritt,
#                 diese Funktion ruft sich selber solange selber auf, bis der
#                 erste Unterschied gefunden ist.
# Beispiel:       $stelle = diff_pos($var1,$var2,$von,$bis)
# Parameter:      $var1 = erste Zeichenkette der "untersucht" werden soll
#                 $var2 = zweite Zeichenkette der "untersucht" werden soll
#                 $von  = ab dieser Position wird nach Unterschieden gesucht
#                 $bis  = bis zu dieser Position wird nach Unterschieden gesucht
# Rueckgabe Wert: Die Position an der der erste Unterschied aufgetreten ist,
#                 die Zaehlung beginnt auch hier bei 1!
# =============================================================================
#Ermitteln ab welcher Stelle sich zwei Strings unterscheiden.
#Das ganze funktioniert rekursiv, zuerst wird die "erste Haelfte" der Strings
#untersucht. Sind diese Teilstrings gleich, wird die zweite Haelfte miteinander
#verglichen, sind sie nicht gleich wird die "erste Haelfte" geteilt, und
#wiederum die erste haelfte hiervon untersucht, ...
#Da diese Funktion nur(!) aufgerufen wird, wenn zuvor festgestellt wurde, das
#sich die Zeichenketten voneinander unterscheiden, muss also die Position
#gefundenwerden.
sub diff_pos {
  my($ist, $soll, $von, $bis) = @_;

  if($s_ignore) {
    # Gross - Kleinschreibung ignorieren
    $ist  = lc($ist);
    $soll = lc($soll);
  }

  #zu untersuchenden Teil aus den Zeichenketten extrahieren
  my $anz_zeichen = $bis - $von + 1;
  my $ist_teil  = substr($ist ,$von,$anz_zeichen);
  my $soll_teil = substr($soll,$von,$anz_zeichen);

  my $neu_von = 0;
  my $neu_bis = 0;

  # Die Zeichenketten sind nicht gleich
  if ($ist_teil ne $soll_teil){
    #print "ne fuer Bereich";
    $neu_von = $von;
    $neu_bis = $von + int(($anz_zeichen)/2) - 1;
  }
  # Die Zeichenketten sind gleich
  else {
    #print "eq fuer Bereich ";
    $neu_von = $bis+1;
    $neu_bis = int($bis + 1 + ($bis/2));
    #aufpassen das die "bis" Position nicht hinter dem Ende der Zeichenkette liegt
    if ($neu_bis gt length($ist)) {
      $neu_bis = length($ist);
    }
    #was soll das ?????
    if ($von eq $bis) {
      $von++;
    }
  }

  #print " von=$von bis=$bis  anz_zeichen=$anz_zeichen Bereich im soll = $soll_teil\n";
  # rueckgabe der Stelle
  if(($bis - $von) eq 0) {
    return ($von + 1);
  }
  # erneute Rekursion
  else {
    diff_pos($ist,$soll,$neu_von,$neu_bis);
  }
}
# === Ende von Funktion diff_pos ==============================================

# === Funktion ================================================================
# Name:           call_diff_pos
# Beschreibung:   Wraper Funktion fuer "diff_pos" um die Position des ersten
#                 Unterschieds zu ermitteln, und anzuzeigen
# Beispiel:       call_diff_pos($var1, $var2);
# Parameter:      $var1 = erste Zeichenkette die verglichen werden soll
#                 $var2 = zweite Zeichenkette die verglichen werden soll
# Rueckgabe Wert: -
# =============================================================================
sub call_diff_pos {
  my ($tmp_ist, $tmp_soll) = @_;

  my $ist_laenge  = length($tmp_ist);
  my $soll_laenge = length($tmp_soll);

  #die Position des ersten Unterschieds wird nur ermittelt wenn wenigstens eine
  #der beiden Zeichenketten mindestens $show_pos_length Zeichen lang ist, und
  #$show_pos_length selber groesser als 0 ist
  if (($ist_laenge >= $show_pos_length or $soll_laenge >= $show_pos_length) and $show_pos_length > 0) {
    my $bis = 0;
    my $von = 0;
    #ermitteln bis zu welchem Zeichen dier Haelfte der laengeren Zeichenkette
    #geht
    if ($ist_laenge gt $soll_laenge) {
      $bis = int($ist_laenge/2);
    }
    else {
      $bis = int($soll_laenge/2);
    }

    my $stelle = diff_pos($tmp_ist,$tmp_soll,$von,$bis); #Ermitteln an wo der erste Unterschied besteht
    my $first_difference = "  Position = ";
    for (my $ind = 1; $ind < $stelle; $ind++) {
      $first_difference = $first_difference . " ";
    }

    if($coloured_output){
      #$show_pos_length=1;  #da hier die ganze Zeile betrachtet wird, wird die
        #Position immer angezeigt
      $first_difference = $first_difference . " " x 7; # Da der Aufbau des
        # Outputs hier andes ist, muessen noch 7 Leerzeichen eingefuegt werden
    }

    $first_difference = $first_difference . "^";
    print "$first_difference\n";
  }
  #Ausgeben/Anzeigen an welcher Stelle der erste Unterschied ist -Ende-
}
# === Ende von Funktion call_diff_pos =========================================

# === Funktion ================================================================
# Name:           chk_input_file
# Beschreibung:   Pruefen ob die angegebene Datei existiert und lesbar ist
# Beispiel:       $s_act_file = chk_input_file($file, $required, $comment);
# Parameter:      $file     = Datei die eingelesen werden soll
#                 $required = Muss diese Datei existieren, wenn ja dann 1,
#                             wenn nein dann 0
#                 $comment  = Kommentar fuer die Ausgabe
#                             z.B. "Actual Result File"
# Rueckgabe Wert: $checked_file = Hier wird der Dateiname nach der Pruefung
#                                 fuer die weitere Verwendung abgelegt
# =============================================================================
sub chk_input_file {
  my ($tmp_file, $tmp_required, $tmp_comment) = @_;
  my $checked_file = "";

  if (defined $tmp_file) {
    if (-f $tmp_file) {
      if (-r $tmp_file) {
        $checked_file = $tmp_file;
      }
      else {
        print STDERR "Error:\n";
        print STDERR "  $tmp_comment is unreadable\n";
        exit 1;
      }
    }
    else {
      print STDERR "Error:\n";
      print STDERR "  $tmp_comment doesn't exist\n";
      exit 1;
    }
  }
  else {
    if ($tmp_required) {
      print STDERR "Error:\n";
      print STDERR "  $tmp_comment is needed! ->Exit\n";
      usage;
      exit 1;
    }
  }

  return $checked_file;
}
# === Ende von Funktion chk_input_file ========================================

# === Funktion ================================================================
# Name:           create_hash
# Beschreibung:   Hash aus den in das jeweilige Array (@a_lines_act oder
#                 @a_lines_exp) eingelesenen Daten erzeugen, in dem dem Key die
#                 jeweilige Zeilennummer zugeordnet wird
# Beispiel:       %h_key = create_hash($file, @a_daten);
# Parameter:      $file    = "act" oder "exp", wird fuer die Fehlermeldung
#                            benoetigt
#                 @a_daten = Array, das die Daten (Records) aus der
#                            eingelesenen Datei enthaelt
# Rueckgabe Wert: %h_key = Hash der alle Key's (mittels der golbalen Variable
#                          $s_key_cols ermittelt) als Schluessel enthaelt, und
#                          die Zeilennummer als Wert
# =============================================================================
sub create_hash {

  my ($s_file, @a_daten) = @_;

  my $s_line_counter = 0;
  my @a_records = (); # Array leer machen
  my @a_key_cols = ();
  my %h_tmp_key_data;

  #Hash erzeugen in dem dem Key die Zeilennummer zugeordnet wird
  foreach my $s_zeile (@a_daten) {

    #ganze Array Zeile am Trenner aufspalten
    @a_records  = split(/\Q$s_separator\E/, $s_zeile);

    #die uebergebenen Keys (Keyspalten) am Trenner aufspalten
    @a_key_cols = split(/\Q$s_separator\E/, $s_key_cols);

    #In dieser Variable wird der Unique Key zusammen gesetzt, wenn er aus
    #mehreren Spalten besteht
    my $s_joined_key = "";
    #Diese Variable wird auf 1 gesetzt wenn der Schluessel leer ist, dann wird
    #eine Warnung ausgegeben
    my $s_empty_key  = 0;

    my $s_tmp_line_no = 0;
    my $s_tmp_line_no_already_found = 0;

    #--------------------------------------------------------------------------
    #Schluessel fuer Hash aufbauen
    #--------------------------------------------------------------------------
    #Die -1 in den folgenden Zeilen ist noetig, da das erste Feld im Array die
    #Nr. 0 ist, uebergeben wird aber eine 1, was auch so beabsichtigt ist!

    #Der Key besteht nur aus einem Feld
    if(scalar @a_key_cols == 1) {
      #Pruefen ob der angegebene Key existiert
      if(exists $a_records[($a_key_cols[0]-1)]) {
        if(defined $a_records[($a_key_cols[0]-1)]) {
          #Key existiert und ist definiert
          if($a_records[($a_key_cols[0]-1)] eq "") {
            $s_empty_key = 1;
          }
        }
        else {
          $s_tmp_line_no = $s_line_counter + 1;
          print STDERR "Error: The keycolumn: $a_key_cols[0] contains an undefined value in $s_file file  => exit 1\n";
          print STDERR "Complete Line $s_tmp_line_no in $s_file file: > $s_zeile <\n\n" if($opt_l);
          exit 1;
        }
      }
      else {
        $s_tmp_line_no = $s_line_counter + 1;
        print STDERR "Error: The keycolumn: $a_key_cols[0] did not exist in $s_file file  => exit 1\n";
        print STDERR "Complete Line $s_tmp_line_no in $s_file file: > $s_zeile <\n\n" if($opt_l);
        exit 1;
      }
      $s_joined_key = $a_records[($a_key_cols[0]-1)]
    }

    #Der Key besteht aus mehreren Feldern
    else {
      my $s_tmp_counter = 0;
      foreach (@a_key_cols) {
        #Pruefen ob der angegebene Key existiert
        if(exists $a_records[($_-1)]) {
          if(defined $a_records[($_-1)]) {
            #Key existiert und ist definiert
            if($a_records[($_-1)] eq "") {
              #$s_empty_key = 1; #Bei zusammengesetzten Keys kann ein einzelnes Feld durchaus leer sein
            }
          }
          else {
            print STDERR "Error: The keycolumn: $_ contains an undefined value in $s_file file => exit 1\n";
            print STDERR "Complete Line in $s_file file: > $s_zeile <\n\n" if($opt_l);
            exit 1;
          }
        }
        else {
          print STDERR "Error: The keycolumn: $_ did not exist in $s_file file => exit 1\n";
          print STDERR "Complete Line in $s_file file: > $s_zeile <\n\n" if($opt_l);
          exit 1;
        }

        #Die einzelnen Keys mit dem Spaltentrenner zu einer Variable verbinden
        if($s_tmp_counter == 0) {
          #Sonderbehandlung fuer ersten Key, damit der Trenner nur zwischen den
          #  Keys steht, und nicht vor dem ersten
          $s_joined_key = $a_records[($_-1)];
        }
        else {
          $s_joined_key = $s_joined_key . $s_separator . $a_records[($_-1)];
        }

        $s_tmp_counter++;
      }
      my $s_is_key_empty;
      ($s_is_key_empty = $s_joined_key ) =~ s/\Q$s_separator\E//g;
      #Bei zusammengesetzten Keys kann ein einzelnes Feld durchaus leer sein
      if($s_is_key_empty eq "") {
        $s_empty_key = 1;
      }
      else {
        $s_empty_key = 0;
      }
    }

    #Der Schluessel der eingefuegt werden soll existiert bereits im Hash
    if(defined $h_tmp_key_data{$s_joined_key}) {
      $s_tmp_line_no = $s_line_counter + 1;
      $s_tmp_line_no_already_found = $h_tmp_key_data{$s_joined_key} + 1;
      print STDERR "ERROR: Key \"$s_joined_key\" is not unique in $s_file file => exit 1!\n";
      print STDERR "This Key occurs in line $s_tmp_line_no_already_found and in line $s_tmp_line_no.\n";
      print STDERR "Complete Line $s_tmp_line_no_already_found: > $a_daten[$h_tmp_key_data{$s_joined_key}] <\n" if($opt_l);
      print STDERR "Complete Line $s_tmp_line_no: > $s_zeile <\n\n" if($opt_l);
      exit 1;
    }
    #Der Schluessel ist neu und wird eingefuegt
    else {
      $h_tmp_key_data{$s_joined_key} = $s_line_counter; #Hash belegen
      $s_line_counter++; # Zeilennummer inkrementieren
      warn "Do you think a key which contains NULL (is empty) is very clever?\n" if($s_empty_key);
    }
  }

  #Hash der alle Keys (aus beiden Dateien) enthaelt, mit Anzahl des vorkommens
  #als Wert. Ist der Wert 2, existiert der Key in beiden Dateien, ist er 1 nur
  #in einer der Dateien.
  #Werte ungleich 1 oder 2 duerfen nicht auftreten, da ein 0 maliges Vorkommen
  #nicht moeglich ist, und sollte der Wert groesser 2 werden, wuerde der Key in
  #mindestens einer der beiden Dateien mehrfach vorkommen, was weiter oben
  #bereits gefiltert wird. Das koennte heissen, das ggf. eine Spalte des Keys
  #vergessen wurde, wenn er aus mehreren besteht.
  foreach my $s_key ( sort keys %h_tmp_key_data){
    if(defined $h_key_all{$s_key}) {
      $h_key_all{$s_key} += 1;
      #zusaetzlicher Check, sollte eigentlich nie greifen!
      if($h_key_all{$s_key} > 2){
        print STDERR "ERROR: Key \"$s_key\" is not unique in $s_file file => exit 1!\n";
        print STDERR "Complete Line in $s_file file: $s_zeile\n\n" if($opt_l);
        exit 1;
      }
    }
    else {
      $h_key_all{$s_key}=1;
    }
  }

  return %h_tmp_key_data;
}
# === Ende von Funktion create_hash ===========================================

# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#                                  M A I N
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

#getopts('hdsae') or die "Option not valid!\n";
getopts('hdDtiIvVgClf:s:S:a:e:c:k:L:n:T:r:') or die "One Option is not valid!\n";
# siehe http://www.interaktiv.net/shop-system-support/tutorials/perlgetopt.shtml
# siehe http://www.cis.uni-muenchen.de/~hbosk/perl2_ws03/einlesen/optionen_einlesen.html

if (defined $opt_h) {
  #"Online Help" ausgeben
  print "\nOnline Help\n";
  usage;
  $opt_v = 1;
  exit 1;
}

if (defined $opt_v) {
  #Version ausgeben
  print "\nThis is version $s_version of csvdiff\n\n";
  print "csvdiff homepage: http://csvdiff.sourceforge.net/\n";
  print "         contact: r-sch\@users.sourceforge.net\n\n";
  print "This software is licenced unter the terms of GPL version 2.1, feel free\n";
  print "to use, customise, ... it.\n";
  print "If you implement new features, it will be fine if you contact me.\n";
  print "If you think there are missing features, or a bug please contact me too.\n";
  print "In a later version there will be more help, sorry.\n";
  exit 1;
}

if (defined $opt_T) {
  #Tausendertrenner
  if (defined $opt_n) {
    if($opt_T eq "" or $opt_T eq "." or $opt_T eq ","){
      $s_t_separator = $opt_T;
      if(defined $opt_S) {
        if($opt_T eq $opt_S) {
          print STDERR "The decimalseparator and the 1000 separator you provided \"$opt_S\"\n";
          print STDERR "can not be equal! => exit 1\n";
          exit 1;
        }
      }
    }
    else {
      print STDERR "The 1000 separator \"$opt_T\" you provided is not valid!\n";
      print STDERR "Only , or . or empty (no separator) are valid!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    print STDERR "You provided a 1000 separator, but no column to be compared numerical!\n";
    print STDERR "=> exit 1\n";
    exit 1;
  }
}
else {
  $s_t_separator = "";
}

if (defined $opt_S) {
  #Dezimaltrenner
  if (defined $opt_n) {
    if($opt_S eq "." or $opt_S eq ","){
      $s_d_separator = $opt_S;
      if(defined $opt_T) {
        if($opt_T eq $opt_S) {
          print STDERR "The decimalseparator and the 1000 separator you provided \"$opt_S\"\n";
          print STDERR "can not be equal! => exit 1\n";
          exit 1;
        }
      }
    }
    else {
      print STDERR "The Decimalseparator \"$opt_S\" you provided is not valid!\n";
      print STDERR "Only , or . are valid Decimalseparators!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    print STDERR "You provided a decimalseparator, but no column to be compared numerical!\n";
    print STDERR "=> exit 1\n";
    exit 1;
  }
}
else {
  $s_d_separator = ".";
}

if (defined $opt_s) {
  #Spaltentrenner
  $s_separator = $opt_s;
}
else {
  $s_separator = ",";
}

if (defined $opt_k) {
  #Keyspalte(n)
  $s_key_cols = $opt_k;
  $s_use_key = 1;

  my $tmp_check_key_col = $s_key_cols;
  #pruefen ob ein nichtnumerisches Zeichen (Spaltentrenner) enthalten ist
  #Achtung: hier wird ausschliesslich "formal" geprueft, ob der richtige
  #Spaltentrenner verwendet wurde

  if ($tmp_check_key_col =~ /\D+/) {
    #das auftreten des gewuenschten Spaltentrenners durch nichts ersetzen
    $tmp_check_key_col =~ s/\Q$s_separator\E//g;
    #nochmals pruefen ob ein nichtnumerisches Zeichen enthalten ist, wenn jetzt
    #noch eins da ist wurde ein andererer Trenner verwendet
    if ($tmp_check_key_col =~ /\D+/) {
      print STDERR "The Columseparator you provided for keycolumns is not the one you used with the \"-s\" option!\n";
      print STDERR "You provided \"$s_key_cols\" as key column(s) and \"$s_separator\" as separator!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    ; #es wurde ein Integer uebergeben, hier muss "nur" geprueft werden ob
      #diese Spalte existiert, das passiert weiter unten
  }
}
else {
  $s_key_cols = 0;
  $s_use_key  = 0;
}

if (defined $opt_f) {
  #Spalte(n) ausblenden
  $s_fadeout_cols = $opt_f;
  my $tmp_check_fadeout_col = $s_fadeout_cols;
  #pruefen ob ein nichtnumerisches Zeichen enthalten ist
  #Achtung: hier wird ausschliesslich "formal" geprueft, ob der richtige
  #Spaltentrenner verwendet wurde
  if ($tmp_check_fadeout_col =~ /\D+/) {
    #das auftreten des gew nschten Spaltentrenners durch nichts ersetzen
    $tmp_check_fadeout_col =~ s/\Q$s_separator\E//g;
    #nochmals pruefen ob ein nichtnumerisches Zeichen enthalten ist, wenn jetzt
    #noch eins da ist wurde ein andererer Trenner verwendet
    if ($tmp_check_fadeout_col =~ /\D+/) {
      print STDERR "The Columseparator you provided for \"fadeout\" columns is not the one you used with the \"-s\" option!\n";
      print STDERR "You provided \"$s_fadeout_cols\" as fadeout column(s) and \"$s_separator\" as separator!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    ; #es wurde ein Integer uebergeben, hier muss "nur" geprueft werden ob
      #diese Spalte existiert, das passiert weiter unten
  }
}

if (defined $opt_n) {
  #Spalte(n) numerisch vergleichen
  $s_num_cols = $opt_n;

  #Ist das erste Zeichen das �bergeben wurde ein Minus?
  #Wenn ja sollen alle Spalten ausser der/den angegebenen numerisch verglichen
  #werden!
  $s_num_cols_invers = ($s_num_cols =~ tr /-//d); #Wertebereich: 0 und 1
  #$s_num_cols_invers = 1 if($s_num_cols =~ /-.*/);
  #$s_num_cols =~ tr /-//d;

  #Sonderbehandlung wenn alle Spalten numerisch verglichen werden sollen
  $s_num_cols = 0 if($s_num_cols eq "");

  my $tmp_check_num_cols = $s_num_cols;
  #pruefen ob ein nichtnumerisches Zeichen enthalten ist
  #Achtung: hier wird ausschliesslich "formal" geprueft, ob der richtige
  #Spaltentrenner verwendet wurde
  if ($tmp_check_num_cols =~ /\D+/) {
    #das auftreten des gew nschten Spaltentrenners durch nichts ersetzen
    $tmp_check_num_cols =~ s/\Q$s_separator\E//g;
    #nochmals pruefen ob ein nichtnumerisches Zeichen enthalten ist, wenn jetzt
    #noch eins da ist wurde ein andererer Trenner verwendet
    if ($tmp_check_num_cols =~ /\D+/) {
      print STDERR "The Columseparator you provided for \"numeric comparision\" columns is not the one you used with the \"-s\" option!\n";
      print STDERR "You provided \"$s_num_cols\" as column(s) to be numeric compared and \"$s_separator\" as separator!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    ; #es wurde ein Integer uebergeben, hier muss "nur" geprueft werden ob
      #diese Spalte existiert, das passiert weiter unten
  }
  #dieses Array enthaelt die Felder die numerisch verglichen werden sollen
  @a_num_cols = split(/\Q$s_separator\E/, $s_num_cols);
}

if (defined $opt_r) {
  if(defined $opt_d) {
    print STDERR "\nWARNING: Option -r will be IGNORED, because you used option -d too\n";
    $opt_r = undef;
  }
  #Spalte(n) fuer die ein Verhaeltnis und Unterschied wird gerechnet
  $s_ratio_cols = $opt_r;
  my $tmp_check_ratio_col = $s_ratio_cols;
  #pruefen ob ein nichtnumerisches Zeichen enthalten ist
  #Achtung: hier wird ausschliesslich "formal" geprueft, ob der richtige
  #Spaltentrenner verwendet wurde
  if ($tmp_check_ratio_col =~ /\D+/) {
    #das auftreten des gewuenschten Spaltentrenners durch nichts ersetzen
    $tmp_check_ratio_col =~ s/\Q$s_separator\E//g;
    #nochmals pruefen ob ein nichtnumerisches Zeichen enthalten ist, wenn jetzt
    #noch eins da ist wurde ein andererer Trenner verwendet
    if ($tmp_check_ratio_col =~ /\D+/) {
      print STDERR "The Columseparator you provided for \"ratio\" columns is not the one you used with the \"-s\" option!\n";
      print STDERR "You provided \"$s_ratio_cols\" as ratio column(s) and \"$s_separator\" as separator!\n";
      print STDERR "=> exit 1\n";
      exit 1;
    }
  }
  else {
    ; #es wurde ein Integer uebergeben, hier muss "nur" geprueft werden ob
      #diese Spalte existiert, das passiert weiter unten
  }
  @a_ratio_cols = split(/\Q$s_separator\E/, $s_ratio_cols);
}

if (defined $opt_g) {
  #Daten vor vergleich sortieren?
  $s_sort_data = $opt_g;
}
else {
  $s_sort_data = 0;
}

if (defined $opt_D) {
  #Debug Modus
  $debug = 1;
}
else {
  $debug = 0;
}

if (defined $opt_V) {
  #Etwas mehr Output erzeugen
  $s_verbose = 1;
}
else {
  $s_verbose = 0;
}

if (defined $opt_d) {
  #diff aehnlicher Output, auf Linux mit Farbe
  $coloured_output = 1;
}
else {
  $coloured_output = 0;
}

if (defined $opt_t) {
  #Vor- und Nachlaufender Leerzeichen ignorieren
  $s_trim = 1;
}
else {
  $s_trim = 0;
}

if (defined $opt_i) {
  #Gross-/Kleinschreibung ignorieren
  $s_ignore = 1;
}
else {
  $s_ignore = 0;
}

if (defined $opt_I) {
  #Doppelte Leerzeichen ignorieren
  $s_ignore_space = 1;
}
else {
  $s_ignore_space = 0;
}

if (defined $opt_L) {
  #Min. laenge ab der die Position des ersten Unterschieds angezeigt wird

  #pruefen ob ein nichtnumerisches Zeichen enthalten ist
  #siehe auch perldoc -q "whether a scalar is a number"
  if($opt_L =~ /\D+/) {
    print STDERR "You provided \"$opt_L\" as min. length to show the first difference, this is not an integer (!).\n";
    print STDERR "=> exit 1\n";
    exit 1;
  }
  else {
    $show_pos_length = $opt_L;
  }
}
else {
  $show_pos_length = 15;
}

# Pruefen ob beide Optionen zur uebergabe der Spaltennamen verwendet wurden
if($opt_c && $opt_C) {
  print STDERR "You can just use \"-c $opt_c\" or \"-C\", but not together!\n";
  print STDERR "=> exit 1\n";
  exit 1;
}

# -----------------------------------------------------------------------------
# Ausgeben aller aktivierten Optionen, da Verbose Modus
# -----------------------------------------------------------------------------
if($s_verbose) {
  print "This is csvdiff version: $s_version\n";
  print "Folowing Options are activated:\n";
  print "  Actual Result                 : $opt_a\n";
  print "  Expected Result               : $opt_e\n";
  print "  Columnamesfile                : $opt_c\n" if($opt_c);
  if($s_separator eq " "){
    print "  Fieldseparator                : \"$s_separator\"\n";
  }
  else{
    print "  Fieldseparator                : $s_separator\n";
  }
  print "  Show complete Line            : yes\n" if($opt_l);
  print "  Fade-out column(s)            : $opt_f\n" if($opt_f);
  print "  Ratio column(s)               : $opt_r\n" if($opt_r);
  if($opt_n) {
    print "  Decimalseparator              : $s_d_separator\n";
    if($s_t_separator) {
      print "  1000 separator                : $s_t_separator\n";
    }
    else {
      print "  1000 separator                : None\n";
    }
  }
  if($s_num_cols_invers) {
    print "  NOT Numeric compared column(s): $s_num_cols\n" if($opt_n);
  }
  else {
    print "  Numeric compared column(s)    : $s_num_cols\n" if($opt_n);
  }
  print "  Indicate first difference pos.: $show_pos_length\n" if($show_pos_length > 0);
  print "  Print online Help             : yes\n" if($opt_h);
  print "  Debug mode                    : yes\n" if($opt_D);
  print "  Diff like coloured output     : yes\n" if($opt_d);
  print "  Trim space's                  : yes\n" if($opt_t);
  print "  Ignore case                   : yes\n" if($opt_i);
  print "  Ignore multiple space's       : yes\n" if($opt_I);
  print "  Grade/sort input data first   : yes\n" if($opt_g);
  print "  Keycolumn(s)                  : $opt_k\n" if($opt_k);
  print "\n" if(!$opt_k); #Leerzeile nur wenn nicht noch die Spaltennamen
                          #ausgegeben werden sollen
}

# -----------------------------------------------------------------------------
# Pruefen ob die Datei $s_act_file existiert und lesbar ist
# -----------------------------------------------------------------------------
$s_act_file = chk_input_file($opt_a, 1, "Actual Result File");
$s_exp_file = chk_input_file($opt_e, 1, "Expected Result File");
$s_col_file = chk_input_file($opt_c, 0, "Columnames File");

# -----------------------------------------------------------------------------
# Dateien in Arrays einlesen, hierbei werden Leerzeilen (Zeilen ohne ein
# Zeichen, Zeilen, bei denen alle Felder NULL enthalten sind damit nicht
# gemeint) ignoriert!
# -----------------------------------------------------------------------------
if($opt_f) {
  # Es sollen eine oder mehrere Spalten nicht zum Vergleichen beruecksichtigt
  # werden, die uebergebenen Spalten am Trenner aufspalten
  @a_fadeout_cols = split(/\Q$s_separator\E/, $s_fadeout_cols);
}

my $s_empty_lines_act = 0;
my $s_first_line_act = "";
#Daten aus ACT Datei einlesen
($s_empty_lines_act, $s_first_line_act, @a_lines_act) = read_csv($s_act_file);

my $s_empty_lines_exp = 0;
my $s_first_line_exp = "";
#Daten aus EXP Datei einlesen
($s_empty_lines_exp, $s_first_line_exp, @a_lines_exp) = read_csv($s_exp_file);

#Wenn die Ergebnisdateien die Spalten�berschriften in der ersten Zeile
#enthalten, stimmen sie �berein? Wenn ja, die Zeile aufspalten und an
#das Spaltennamensarray zuweisen.
if($opt_C) {
  if($s_first_line_act ne $s_first_line_exp) {
    print STDERR "Column names in actual and expected result are not equal => exit 1\n";
    exit 1;
  }
  else {
    if($s_first_line_act eq "") {
      print STDERR "\nThe first line in your Files should contain the Columnames, but it is empty => exit 1\n";
      exit 1;
    }
    @a_col_names = split(/\Q$s_separator\E/, $s_first_line_act);
  }
}

#Warnungen ausgeben wenn Leerzeilen in den Dateien enthalten sind
if($s_empty_lines_act > 0 or $s_empty_lines_exp > 0){
  print STDERR "\nWARNING: Empty lines are IGNORED, they are not counted etc., take care of linenumbers!\n";
  if($s_empty_lines_act > 0){
    print STDERR "  Actual Result contains $s_empty_lines_act empty lines.\n";
  }
  if($s_empty_lines_exp > 0){
    print STDERR "  Expected Result contains $s_empty_lines_exp empty lines.\n";
  }
  print STDERR "\n";
}

if($s_col_file) {
  open( FILE_COL, "<$s_col_file" ) or die "File $s_col_file could not be opend: $!";
    @a_lines_col = <FILE_COL>; # komplette Datei in Array einlesen
  close( FILE_COL );
  if (scalar @a_lines_col > 1) {
    print STDERR "Error: Your columnnames file has more than one line => exit 1\n";
    exit 1;
  }
  chomp(@a_lines_col);
  #ganze Array Zeile am Trenner aufspalten
  @a_col_names = split(/\Q$s_separator\E/, $a_lines_col[0]);
}

chomp(@a_lines_act, @a_lines_exp);

#Hier noch die Namen der Keyspalten ausgeben
if($s_verbose) {
  if($opt_k) {
    my @a_tmp_key_cols = split(/\Q$s_separator\E/, $opt_k);
    print "  Keycolumnname(s)              : ";
    my $s_erster_key = 0;
    foreach my $s_tmp_spalte (@a_tmp_key_cols) {
      print "                                  " if($s_erster_key);
      $s_erster_key++;
      printf ("%-4d = $a_col_names[$s_tmp_spalte-1]\n",$s_tmp_spalte); #Die -1 ist wieder zur Berichtigung der Feldname des ersten Feldes steht in Arrayelement 0!
    }
  print "\n";
  }
}


# -----------------------------------------------------------------------------
# Fuehrende und nachlaufende Leerzeichen entfernen, sowie doppelte Leerzeichen
# -----------------------------------------------------------------------------
#if($s_ignore_space or $s_trim) {
#  @a_lines_act = trim_delete_space(@a_lines_act);
#  @a_lines_exp = trim_delete_space(@a_lines_exp);
#}

# -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# Vergleich mit Key
# -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
if($s_use_key) {
  if($s_sort_data) {
    print "\nTo use a Keycolumn and the sort/grade Option together makes no sense, sort will be ignored!\n\n";
    $s_sort_data = 0;
  }

  # ---------------------------------------------------------------------------
  # Hash mit den Daten aus der "actual" bzw. "expected" Datei fuellen
  # ---------------------------------------------------------------------------
  %h_key_act = create_hash("act", @a_lines_act);
  %h_key_exp = create_hash("exp", @a_lines_exp);

  # ---------------------------------------------------------------------------
  # Jetzt/hier geht das Vergleichen los
  # ---------------------------------------------------------------------------
  $s_line_counter = 0;
  $s_diff_counter = 0;

  # Der Vergleich erfolgt in der sortierten Reihenfolge der Keys!
  foreach $s_key ( sort keys %h_key_all){
    $s_line_counter++; # Zeilennummer inkrementieren

    # -------------------------------------------------------------------------
    # Key ist nur in exp enthalten
    # -------------------------------------------------------------------------
    if(defined $h_key_exp{$s_key} && not defined $h_key_act{$s_key}) {
      if($s_diff_counter > 0) {
        # Trennzeile ausgeben
        print "\n", '-' x 79, "\n\n";
      }
      $s_diff_counter++;
      if($opt_l) {
        print "Record with Key: \"$s_key\" exists only in expected result $s_exp_file\n";
        printf (" Line %03d : > $a_lines_exp[$h_key_exp{$s_key}] <\n",$h_key_exp{$s_key}+1);
      }
      else {
        printf( "Record with Key: \"$s_key\" exists only in expected result $s_exp_file, line %d\n",$h_key_exp{$s_key}+1);
      }
    }

    # -------------------------------------------------------------------------
    # Key ist nur in act enthalten
    # -------------------------------------------------------------------------
    elsif(not defined $h_key_exp{$s_key} && defined $h_key_act{$s_key}) {
      if($s_diff_counter > 0) {
        # Trennzeile ausgeben
        print "\n", '-' x 79, "\n\n";
      }
      $s_diff_counter++;
      if($opt_l) {
        print "Record with Key: \"$s_key\" exists only in actual result $s_act_file\n";
        printf (" Line %03d : > $a_lines_act[$h_key_act{$s_key}] <\n",$h_key_act{$s_key}+1);
      }
      else {
        printf ("Record with Key: \"$s_key\" exists only in actual result $s_act_file, line %d\n",$h_key_act{$s_key}+1);
      }
    }

    # -------------------------------------------------------------------------
    # Key ist in act und exp enthalten
    # -------------------------------------------------------------------------
    elsif(defined $h_key_exp{$s_key} && defined $h_key_act{$s_key}) {
      my $s_act_line = $h_key_act{$s_key};
      my $s_exp_line = $h_key_exp{$s_key};

      $s_diff = 0;
      my $s_record_header = "";
      my $s_record_diff_line = "";
      $s_diff = cmp_data($a_lines_act[$s_act_line], $a_lines_exp[$s_exp_line]);
      # -----------------------------------------------------------------------
      # Die einzelnen Records (als ganzes) sind unterschiedlich
      # -----------------------------------------------------------------------
      if($s_diff) {
        if($s_diff_counter > 0) {
          # Trennzeile ausgeben
          #print "\n", '-' x 79, "\n\n";
          $s_record_header .= "\n" . '-' x 79 . "\n\n";
        }
        $s_diff_counter++;
#        print "Record with key \"$s_key\" is different:\n";
        $s_record_header .= "Record with key \"$s_key\" is different:\n";

        #Zeile in einzelne Felder aufspalten
        my @a_fields_act = split /\Q$s_separator\E/, $a_lines_act[$s_act_line];
        my @a_fields_exp = split /\Q$s_separator\E/, $a_lines_exp[$s_exp_line];

        # ---------------------------------------------------------------------
        # Unterschiede werden diff-maessig angezeigt
        # ---------------------------------------------------------------------
        if($coloured_output) {
          my $tmp_act_line = sprintf ("Actual   line %03d > ",$s_act_line+1);
          my $tmp_exp_line = sprintf ("Expected line %03d > ",$s_exp_line+1);

          $s_field_counter = 0;
          my $b_record_header_already_printed = 0;
          #Lauf ueber alle Felder der Records, so sie existieren
          while(defined $a_fields_act[$s_field_counter] && defined $a_fields_exp[$s_field_counter]) {
            $s_diff = 0;
            $s_diff = cmp_data($a_fields_act[$s_field_counter], $a_fields_exp[$s_field_counter], $s_field_counter);
            #Das aktuelle Feld ist unterschiedlich
            if($s_diff) {
              if($b_record_header_already_printed == 0) {
                print "$s_record_header";
                $b_record_header_already_printed=1;
              }
              #Farbige Ausgabe ist derzeit nur unter Linux moeglich, ggf. auch
              #unter anderen Unix'en, aber nicht unter Windows
              if(lc($^O) =~ "win") {
                $tmp_act_line .= "$a_fields_act[$s_field_counter]$s_separator";
                $tmp_exp_line .= "$a_fields_exp[$s_field_counter]$s_separator";
              }
              else {
                $tmp_act_line .= "\033[31m$a_fields_act[$s_field_counter]\033[39m$s_separator";
                $tmp_exp_line .= "\033[32m$a_fields_exp[$s_field_counter]\033[39m$s_separator";
              }
            }
            #Das aktuelle Feld ist gleich
            else {
              $tmp_act_line .= "$a_fields_act[$s_field_counter]$s_separator";
              $tmp_exp_line .= "$a_fields_exp[$s_field_counter]$s_separator";
            }
            $s_field_counter++;
          }

          $tmp_act_line .= " <\n";
          $tmp_exp_line .= " <\n";
          $s_record_diff_line .= $tmp_act_line;
          $s_record_diff_line .= $tmp_exp_line;

          if($b_record_header_already_printed) {
            print $s_record_diff_line;

            #Ausgeben/Anzeigen an welcher Stelle der erste Unterschied ist
            call_diff_pos($a_lines_act[$s_act_line], $a_lines_exp[$s_exp_line]);
          }
        }

        # ---------------------------------------------------------------------
        # Unterschiede werden im csvdiff "Format" angezeigt
        # ---------------------------------------------------------------------
        else {
          if($opt_l) {
            #Erstmal wird die ganze Zeile ausgegeben
            $s_record_diff_line .= sprintf ("Actual   line %03d > $a_lines_act[$s_act_line] <\n",$s_act_line +1);
            $s_record_diff_line .= sprintf ("Expected line %03d > $a_lines_exp[$s_exp_line] <\n",$s_exp_line +1);
          }
          else {
            #Nur die Zeilennummern ausgeben die verglichen werden
            $s_record_diff_line .= sprintf ("Actual line %d compared with Expected line %d\n",$s_act_line +1,$s_exp_line +1);
          }

          $s_field_counter = 0;
          $s_no_diff_per_record = 0;
          my $b_record_header_already_printed = 0;
          #Lauf ueber alle Felder der Records, so sie existieren
          while(defined $a_fields_act[$s_field_counter] && defined $a_fields_exp[$s_field_counter]) {
            $s_diff = 0;
            $s_compare_message = "";
            $s_diff = cmp_data($a_fields_act[$s_field_counter], $a_fields_exp[$s_field_counter], $s_field_counter);
            #Das aktuelle Feld ist unterschiedlich
            if($s_diff) {
              $s_no_diff_per_record++;
              #Ausgabe erfolgt nur wenn der erste Unterschied pro Zeile gefunden wird
              if($b_record_header_already_printed == 0) {
                print "$s_record_header";
                print "$s_record_diff_line";
                $b_record_header_already_printed=1;
              }
              print STDERR "$s_compare_message";
              print "\n" if($s_no_diff_per_record > 1); #Leerzeile einfuegen
                #zwischen zwei Unterschieden
              printf (" Difference in field no.: %02d",$s_field_counter +1);
              if ($opt_c || $opt_C) {
                print " - field name: $a_col_names[$s_field_counter]";
              }
              print "\n";
              print "  Actual   > $a_fields_act[$s_field_counter] <\n";
              print "  Expected > $a_fields_exp[$s_field_counter] <\n";

              if($opt_r) {
                #Pruefen ob ein Verhaeltnis und Unterschied fuer das aktuelle Feld erstellt werden soll
                if(grep{ $s_field_counter == ($_-1)} @a_ratio_cols) {
                  if(looks_like_number($a_fields_exp[$s_field_counter]) and looks_like_number($a_fields_act[$s_field_counter])) {
                    my $s_tmp_ratio = $a_fields_exp[$s_field_counter] / $a_fields_act[$s_field_counter];
                    my $s_tmp_diff = $a_fields_exp[$s_field_counter] - $a_fields_act[$s_field_counter];
                    print "   Ratio      > $s_tmp_ratio <\n";
                    print "   Difference > $s_tmp_diff <\n";
                  }
                  else {
                    print "   Ratio and difference couldn't be calculated!\n";
                  }

                }
              }

              #Ausgeben/Anzeigen an welcher Stelle der erste Unterschied ist
              call_diff_pos($a_fields_act[$s_field_counter],$a_fields_exp[$s_field_counter]);
            }
            #Das aktuelle Feld ist gleich
            else {
              ; #Hier wird nichts ausgegeben
            }
            $s_field_counter++;
          }

        }
      }

      # -----------------------------------------------------------------------
      # Die einzelnen Records (als ganzes) sind gleich
      # -----------------------------------------------------------------------
      else {
        # Die einzelnen Records (als ganzes) sind gleich
        #if($s_diff_counter > 0) {
        #  # Trennzeile ausgeben
        #  print "\n", '-' x 79, "\n\n";
        #}
        #$s_diff_counter++;
        #print "Record with key \"$s_key\" is idetical\n";
      }
    }

    # -------------------------------------------------------------------------
    # Key ist weder in act noch exp enthalten
    # -------------------------------------------------------------------------
    else {
      # Das duerfte eigentlich nie eintreten, koennte ggf. weggelassen werden,
      # und das letzte "elsif" zum "else" gemacht werden
      print STDERR "ERROR at Key: $s_key occoured\n";
      exit 1;
    }
  }
}
# -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
# Vergleich ohne Key
# -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
else {
  #Kopie des Arrays erstellen um die org. Zeilennummer nach dem Sortieren
  #ermitteln zu koennen
  my @a_lines_act_org = @a_lines_act;
  my @a_lines_exp_org = @a_lines_exp;

  #Daten vor vergleich sortieren
  if($s_sort_data) {
    @a_lines_act = sort compare_for_sort @a_lines_act;
    @a_lines_exp = sort compare_for_sort @a_lines_exp;
  }

  #Lauf ueber alle Zeilen beider Dateien, solange sie definiert sind
  for( $s_line_counter = 0; defined $a_lines_act[$s_line_counter] && defined $a_lines_exp[$s_line_counter]; $s_line_counter++) {

    my $s_record_header = "";
    my $s_record_diff_line = "";
    $s_diff = cmp_data($a_lines_act[$s_line_counter],$a_lines_exp[$s_line_counter], $s_field_counter);
    #In dieser Zeile gibt's mindestens einen Unterschied
    if($s_diff) {
      if($s_diff_counter > 0) {
        #Trennzeile ausgeben
        #print "\n", '-' x 79, "\n\n";
        $s_record_header .= "\n" . '-' x 79 . "\n\n";
      }
      $s_diff_counter++;

      #Zeilen in einzelne Felder aufteilen
      my @a_act_fields = split /\Q$s_separator\E/,$a_lines_act[$s_line_counter];
      my @a_exp_fields = split /\Q$s_separator\E/,$a_lines_exp[$s_line_counter];
      #Variablen die nach und nach aufgebaut werden um den Output zu erzeugen
      my $tmp_act_line = "";
      my $tmp_exp_line = "";
      #Variablen fuer die orginalen Zeilennummern (wenn sortiert wurde)
      my $s_act_line_no = 0;
      my $s_exp_line_no = 0;

      # -----------------------------------------------------------------------
      # Vergleich ohne Key-Spalte, Output: diff-maessig
      # -----------------------------------------------------------------------
      if($coloured_output) {
        if($s_sort_data) {
          #Orginale Zeilennummer ermitteln
          for my $ind (0 .. $#a_lines_act_org) {
            if($a_lines_act_org[$ind] eq $a_lines_act[$s_line_counter]) {
              $s_act_line_no = $ind;
              last;
            }
          }
          for my $ind (0 .. $#a_lines_exp_org) {
            if($a_lines_exp_org[$ind] eq $a_lines_exp[$s_line_counter]) {
              $s_exp_line_no = $ind;
              last;
            }
          }
          #my ($s_act_line_no) = grep{ $a_lines_act_org[$_] eq $a_lines_act[$s_line_counter] } 0..$#a_lines_act_org;
          #my ($s_exp_line_no) = grep{ $a_lines_exp_org[$_] eq $a_lines_exp[$s_line_counter] } 0..$#a_lines_exp_org;

          $tmp_act_line = sprintf ("Actual   line %03d > ",$s_act_line_no+1);
          $tmp_exp_line = sprintf ("Expected line %03d > ",$s_exp_line_no+1);

          #Wenn sortiert wurde mach die Ausgabe der Zeilennummer so keinen Sinn
          #hier muesste wenn die Orginal Zeilennummer ausgegeben werden
          #$tmp_act_line = sprintf ("Actual   line > ");
          #$tmp_exp_line = sprintf ("Expected line > ");
        }
        else {
          $tmp_act_line = sprintf ("Actual   line %03d > ",$s_line_counter+1);
          $tmp_exp_line = sprintf ("Expected line %03d > ",$s_line_counter+1);
        }

        $s_field_counter = 0;
        my $b_record_header_already_printed = 0;
        # Lauf ueber alle Felder der Records, so sie existieren
        while(defined $a_act_fields[$s_field_counter] && defined $a_exp_fields[$s_field_counter]) {
          $s_diff = cmp_data($a_act_fields[$s_field_counter], $a_exp_fields[$s_field_counter], $s_field_counter);
          # Das aktuelle Feld ist unterschiedlich
          if($s_diff) {
            if($b_record_header_already_printed == 0) {
              print "$s_record_header";
              $b_record_header_already_printed=1;
            }
            #Farbige Ausgabe ist derzeit nur unter Linux moeglich, ggf. auch
            #unter anderen Unix'en, aber nicht unter Windows
            if(lc($^O) =~ "win") {
              $tmp_act_line .= "$a_act_fields[$s_field_counter]$s_separator";
              $tmp_exp_line .= "$a_exp_fields[$s_field_counter]$s_separator";
            }
            else {
              $tmp_act_line .= "\033[31m$a_act_fields[$s_field_counter]\033[39m$s_separator";
              $tmp_exp_line .= "\033[32m$a_exp_fields[$s_field_counter]\033[39m$s_separator";
            }
          }
          else {
            $tmp_act_line .= "$a_act_fields[$s_field_counter]$s_separator";
            $tmp_exp_line .= "$a_exp_fields[$s_field_counter]$s_separator";
          }
          $s_field_counter++;
        }

        $tmp_act_line .= " <\n";
        $tmp_exp_line .= " <\n";
        $s_record_diff_line .= $tmp_act_line;
        $s_record_diff_line .= $tmp_exp_line;

        if($b_record_header_already_printed) {
          print $s_record_diff_line;

          #Ausgeben/Anzeigen an welcher Stelle der erste Unterschied ist
          call_diff_pos($a_lines_act[$s_line_counter], $a_lines_exp[$s_line_counter]);
        }
      }

      # -----------------------------------------------------------------------
      # Vergleich ohne Key-Spalte, Output: standard
      # -----------------------------------------------------------------------
      else {
        if($s_sort_data) {
          #Orginale Zeilennummer ermitteln
          for my $ind (0 .. $#a_lines_act_org) {
            if($a_lines_act_org[$ind] eq $a_lines_act[$s_line_counter]) {
              $s_act_line_no = $ind;
              last;
            }
          }
          for my $ind (0 .. $#a_lines_exp_org) {
            if($a_lines_exp_org[$ind] eq $a_lines_exp[$s_line_counter]) {
              $s_exp_line_no = $ind;
              last;
            }
          }
          #my ($s_act_line_no) = grep{ $a_lines_act_org[$_] eq $a_lines_act[$s_line_counter] } 0..$#a_lines_act_org;
          #my ($s_exp_line_no) = grep{ $a_lines_exp_org[$_] eq $a_lines_exp[$s_line_counter] } 0..$#a_lines_exp_org;

          if($opt_l) {
            #Erstmal wird die ganze Zeile ausgegeben
            $s_record_diff_line .= sprintf ("Actual   line %03d > $a_lines_act[$s_line_counter] <\n",$s_act_line_no + 1);
            $s_record_diff_line .= sprintf ("Expected line %03d > $a_lines_exp[$s_line_counter] <\n",$s_exp_line_no + 1);
          }
          else {
            #Nur die Zeilennummern ausgeben die verglichen werden
            $s_record_diff_line .= sprintf ("Actual line %d compared with Expected line %d\n",$s_act_line_no + 1,$s_exp_line_no + 1);
          }

          #Wenn sortiert wurde mach die Ausgabe der Zeilennummer so keinen Sinn
          #hier muesste wenn die Orginal Zeilennummer ausgegeben werden
          #$tmp_act_line = sprintf ("Actual   line > ");
          #$tmp_exp_line = sprintf ("Expected line > ");
        }
        else {
          if($opt_l) {
            #Erstmal wird die ganze Zeile ausgegeben
            $s_record_diff_line .= sprintf ("Actual   line %03d > $a_lines_act[$s_line_counter] <\n",$s_line_counter + 1);
            $s_record_diff_line .= sprintf ("Expected line %03d > $a_lines_exp[$s_line_counter] <\n",$s_line_counter + 1);
          }
          else {
            #Nur die Zeilennummern ausgeben die verglichen werden
            $s_record_diff_line .= sprintf ("Actual line %d compared with Expected line %d \n",$s_line_counter + 1,$s_line_counter + 1);
          }
        }
#        printf ("\nRecord no: %03d is different:\n", $s_line_counter+1);
#        print "Actual   line: $a_lines_act[$s_line_counter]\n";
#        print "Expected line: $a_lines_exp[$s_line_counter]\n";

        $s_field_counter = 0;
        $s_no_diff_per_record = 0;
        my $b_record_header_already_printed = 0;
        #Lauf ueber alle Felder der Records, so sie existieren
        while(defined $a_act_fields[$s_field_counter] && defined $a_exp_fields[$s_field_counter]){

          $s_compare_message = "";
          $s_diff = cmp_data($a_act_fields[$s_field_counter], $a_exp_fields[$s_field_counter], $s_field_counter);
          #Das aktuelle Feld ist unterschiedlich
          if($s_diff) {
            $s_no_diff_per_record++;
            if($b_record_header_already_printed == 0) {
              print "$s_record_header";
              print "$s_record_diff_line";
              $b_record_header_already_printed=1;
            }
            print STDERR "$s_compare_message";
            print "\n" if($s_no_diff_per_record > 1); #Leerzeile einfuegen
              #zwischen zwei Unterschieden
            printf (" Difference in field no.: %02d",$s_field_counter +1);
            if ($opt_c || $opt_C) {
              print " - field name: $a_col_names[$s_field_counter]";
            }
            print "\n";
            print "  Actual   > $a_act_fields[$s_field_counter] <\n";
            print "  Expected > $a_exp_fields[$s_field_counter] <\n";

            #Ausgeben/Anzeigen an welcher Stelle der erste Unterschied ist
            call_diff_pos($a_act_fields[$s_field_counter],$a_exp_fields[$s_field_counter]);
          }
          #Das aktuelle Feld ist gleich
          else {
            ; #Hier wird nichts ausgegeben
          }
          $s_field_counter++;
        }
      }
    }
    #In dieser Zeile gibt's keinen Unterschied
    else {
      ; #Hier wird nichts ausgegeben
      #print "Zeile: $s_line_counter gleich\n";
    }
  }

  #----------------------------------------------------------------------------
  # Ausgabe der Zeilen die nur in ACT enthalten sind
  #----------------------------------------------------------------------------
  if(scalar @a_lines_act > $s_line_counter) {
    print "\n", '=' x 79, "\n\n";
    print "Folowing lines exists only in actual result $s_act_file:\n";
    for $s_i ($s_line_counter .. (scalar @a_lines_act -1)) {
      #print "\n", '-' x 79, "\n\n";
      if($opt_l) {
        printf (" Line %03d : > $a_lines_act[$s_i] <\n",$s_i +1);
      }
      else {
        printf (" Line %d\n",$s_i +1);
      }
    }
  }

  #----------------------------------------------------------------------------
  # Ausgabe der Zeilen die nur in EXP enthalten sind
  #----------------------------------------------------------------------------
  if(scalar @a_lines_exp > $s_line_counter) {
    print "\n", '=' x 79, "\n\n";
    print "Folowing lines exists only in expected result $s_exp_file:\n";
    for $s_i ($s_line_counter .. (scalar @a_lines_exp -1)) {
      #print "\n", '-' x 79, "\n\n";
      if($opt_l) {
        printf (" Line %03d : > $a_lines_exp[$s_i] <\n",$s_i +1);
      }
      else {
        printf (" Line %d\n",$s_i +1);
      }
    }
  }
}

#------------------------------------------------------------------------------
# Hinweis, das Farbe derzeit nur unter Linux moeglich ist
#------------------------------------------------------------------------------
if(lc($^O) =~ "win" && $coloured_output eq 1) {
  print "\nOutput Coloring is not available for Windows (yet)!\n";
}

#------------------------------------------------------------------------------
# Hinweis das die Anzeige des ersten Unterschieds bei "diff" Output und
#  numerischem Vergleich nicht wirklich clever ist.
#------------------------------------------------------------------------------
if($coloured_output && $s_num_cols && $show_pos_length > 0) {
  print "\nIt may bee not so good to show position of first differnce while using";
  print " diff like output, und numeric column comparision!\n";
}

# ./csvdiff17.pl -e ratio2.csv -a ratio1.csv -o out.txt -s ";" -C -k 1 -S "." -r 2 -n 2  