/usr/local/CPAN/Dimedis-Sql/Dimedis/Sql/CSV.pm


package Dimedis::Sql::CSV;

use strict;
use vars qw($VERSION);
use Carp;

use Data::Dumper;
use FileHandle;
use File::Path;

$VERSION = '0.20';

##------------------------------------------------------------------------------
# CLASS
#   Dimedis::Sql::CSV
#
# PURPOSE
#   Diese Klasse ermöglicht das Lesen und Schreiben einer CSV-Datei.
#   <p>
#   Beim Erzeugen eines neuen CSV-Objektes wird die im Konstruktor angegebene
#   Datei entweder zum Lesen oder zum Schreiben geöffnet (abhängig vom 
#   optionalen Parameter <code>write</code>).
#   <p>
#   Mit dem <code>delimiter</code>-Parameter kann das Trennzeichen für die
#   einzelnen Spalten der CSV-Datei angegeben werden (Default-Trennzeichen
#   ist ";").
#   <p>
#   Wurde die Datei zum Schreiben geöffnet, dann kann über die Methode
#   <code>append()</code> eine neue Zeile hinzugefügt werden.
#   <p>
#   Wurde die Datei zum Lesen geöffnet, dann kann über die Methode
#   <code>read_line()</code> eine einzelne Zeile gelesen werden.
#
# AUTHOR
#   Sabine Tonn <stonn@dimedis.de>
#
# COPYRIGHT
#   dimedis GmbH, Cologne, Germany
#-------------------------------------------------------------------------------


##------------------------------------------------------------------------------
# METHOD
#   public constructor: new
#
# DESCRIPTION
#   Erzeugt ein neues CSV-Objekt
#
# INPUT
#   filename	-- Name der CSV-Datei, mit dem dieses Objekt verknüpft ist
#
# OPTIONAL
#   write     -- 1 = CSV-Datei zum Schreiben öffnen
#                (Default = 0, zum Lesen öffnen)
#   delimiter	-- Trennzeichen für die einzelnen Spalten der CSV-Datei
#                (Default = ";")
#
# RETURN
#   neues CSV-Objekt
#-------------------------------------------------------------------------------
sub new {

  my $class = shift;
  my %par   = @_;

  #--- Parameterprüfung
  my $filename  = $par{filename} or croak("'filename' missing");
  my $write     = $par{write};
  my $delimiter = $par{delimiter};
  my $layer     = $par{layer};

  $delimiter = ";"  if $delimiter eq "";

  my $fh = FileHandle->new();
  
  if ( $write ) {
    #--- CSV-Datei zum Schreiben öffnen
  	 open ($fh, ">> $filename")
	 	or die "Can't open file: $!\n";
  }
  else {
    #--- CSV-Datei zum Lesen öffnen
	 open ($fh, $filename) if -e $filename;
  }

  #-- Ggf. IO Layer aktivieren
  binmode $fh, $layer if $layer ne '';

  my $self = {
	fh		=> $fh,
	delimiter	=> $delimiter
  };

  return bless $self, $class;	
}

##------------------------------------------------------------------------------
# METHOD
#   public: append
#
# DESCRIPTION
#   Hängt eine neue Zeile an die CSV-Datei an
#   <p>
#   Übergeben wird eine Referenz auf eine Liste, welche die hinzuzufügenden
#   Daten enthält. Die einzelnen List-Elemente werden beim Schreiben in die
#   CSV-Datei durch das im Konstruktor angegebene Trennzeichen voneinander
#   getrennt.
#   <p>
#   Backslashes, Newlines und Tabulatoren, die in den übergebenen Daten
#   enthalten sind, werden vor dem Schreiben in die CSV-Datei escaped.
#
# INPUT
#   data_lr	   -- Referenz auf die Liste der hinzuzufügenden Daten
#-------------------------------------------------------------------------------
sub append {

  my $self = shift;
  my %par  = @_;

  my $fh = $self->{fh};

  #--- Parameterprüfung
  my $data_lr = $par{data_lr} or croak("'data_lr' missing");

  foreach ( @{$data_lr} ) {
	 #--- escape '\'
	 s/\\/\\\\/g;
	 #--- escape newlines
	 s/\n/\\n/g;
	 s/\r/\\r/g;
	 #--- escape tabs
	 s/\t/\\t/g;
  }

  my $data_string = join ( $self->{delimiter}, @{$data_lr} );

  print $fh "$data_string\n" or die "Can't write file: $!\n";
}

##------------------------------------------------------------------------------
# METHOD
#   public: read_line
#
# DESCRIPTION
#   Liest eine einzelne Zeile aus der CSV-Datei ein
#   <p>
#   Die Methode liefert eine Referenz auf eine Liste, welche die gelesenen
#   Daten enthält. Als Trennzeichen zwischen den einzelnen Spalten der
#   gelesenen Zeile gilt der im Konstruktor angegebene <code>delimiter</code>-
#   Parameter.
#   <p>
#   Backslashes, Newlines und Tabulatoren, die vor dem Schreiben in die
#   CSV-Datei escaped wurden, werden hier wieder unescaped.
#
# RETURN
#   Referenz auf die Liste der eingelesenen Daten oder <code>undef</code>,
#   wenn das Dateiende erreicht wurde
#-------------------------------------------------------------------------------
sub read_line {

  my $self = shift;
  
  my $fh = $self->{fh};
  
  my $data_record;

  if ( $data_record = <$fh> ) {

    #--- Zeilenumbruch am Ende der Zeile abschneiden	  
	 chomp $data_record;

    my @data = split ( /$self->{delimiter}/, $data_record );

    foreach ( @data ) {

      #--- unescape '\t'
      s/([^\\])\\t/$1\t/g;
      s/^\\t/\t/g;

      #--- unescape '\n', '\r'
      s/([^\\])\\n/$1\n/g;
      s/^\\n/\n/g;

      s/([^\\])\\r/$1\r/g;
      s/^\\r/\r/g;

      #--- unescape '\\'
      s/\\\\/\\/g;
    }
    
    return \@data;
  }
  #--- Dateiende
  else {

	  return undef;
  }
}

sub DESTROY {

	my $self = shift;

   #--- CSV-Datei schließen	
	my $fh = $self->{fh};

	close($fh) if $fh;
}