Projekt

Obecné

Profil

Stáhnout (9.92 KB) Statistiky
| Větev: | Tag: | Revize:
a29b0d63 Michal Kliment
<?php

/*
* This file is part of open source system FreenetIS
* and it is released under GPLv3 licence.
*
* More info about licence can be found:
* http://www.gnu.org/licenses/gpl-3.0.html
*
* More info about project can be found:
* http://www.freenetis.org/
*
*/

/**
* Auxiliary class for parsing CSV bank account listings from czech bank
* "FIO banka". Listing may be obtain from from the ebanking web application
* of FIO bank.
*
* The CSV format looks like this:
* "Datum";"ID operace";"ID pokynu";"KS";"Název banky";"Název protiúčtu";
* "Objem";"Měna";"Protiúčet";"Kód banky";"Zadal";"SS";"Typ";"Poznámka";
* "VS";"Upřesnění - objem";"Upřesnění - měna";"Zpráva pro příjemce"
* "Suma";"";"";"";"";"";"-188170,4";"CZK";"";"";"";"";"";"";"";"";"";""
*
* @author Ondřej Fibich <fibich@freenetis.org>
* @since 1.1.11
* @todo i18n of error messages
*/
class NewFioCsvParser
{
/**
* CSV column separator.
*/
const CSV_COL_DELIM = ';';
/**
* CSV column value wrapper.
*/
const CSV_COL_WRAPPER = '"';

/**
* Last line date string that is used for end of statement detection.
*/
const HEADER_LINE_DATE_VALUE = 'Suma';

/**
* Default CSV file encoding.
*/
const DEFAULT_CHARSET = 'UTF-8';

/**
* All fields available must be used and sorted alphabetically
*
* @var array[string]
*/
private static $fields = array
(
'datum' => 'Datum',
'id_pohybu' => 'ID operace',
'id_pokynu' => 'ID pokynu',
'ks' => 'KS',
'nazev_banky' => 'Název banky',
'nazev_protiuctu' => 'Název protiúčtu',
'castka' => 'Objem',
'mena' => 'Měna',
'protiucet' => 'Protiúčet',
'kod_banky' => 'Kód banky',
'provedl' => 'Zadal',
'ss' => 'SS',
'typ' => 'Typ',
'identifikace' => 'Poznámka',
'vs' => 'VS',
'upresneni_objem' => 'Upřesnění - objem',
'upresneni_mena' => 'Upřesnění - měna',
'zprava' => 'Zpráva pro příjemce'
);

/**
* FIO statement columns fields names.
*
* @return array
*/
public static function get_fields()
{
return self::$fields;
}

/**
* Parse bank statement in CSV format that is passed as string.
*
* @param string $csv string containing the original csv file.
* @param string $charset optional charset name of file, default is UTF-8
* @return array[array] Integer-indexed array of associative arrays.
* Each associative array represents one line of the CSV
* @throws Exception on parse error
*/
public function parse($csv, $charset = self::DEFAULT_CHARSET)
{
$total_sum = -1;
$sum = 0;
$keys = array_keys(self::$fields);
$lines = self::transformFileToLineArray($csv, $charset);
$result = array();
// check each line of CSV
for ($i = 0; $i < count($lines); $i++)
{
$line = trim($lines[$i]);
// header
if ($i == 0)
{
$this->checkHeaders($line);
}
else if ($i == 1)
{
$total_sum = $this->parseHeaderLine($line);
}
else if (empty($line)) // empty last line?
{
break;
}
else
{
// data lines
$cols = $this->parseLine($line, $keys);
// add data row
$sum += $cols['castka'];
$result[] = $cols;
}
}
$this->checkIntegrity($total_sum, $sum);
return $result;
}
/**
* Check whether parser accept given CSV file.
*
* @param string $csv string containing the original csv file.
* @param string $charset optional charset name of file, default is UTF-8
* @return boolean
*/
public function accept_file($csv, $charset = self::DEFAULT_CHARSET)
{
$lines = self::transformFileToLineArray($csv, $charset);
if (count($lines) < 3)
{
return FALSE;
}
try
{
$this->checkHeaders(trim($lines[0]));
$this->parseHeaderLine(trim($lines[1]));
return TRUE;
}
catch (Exception $ex)
{
return FALSE;
}
}

/**
* Parse statement header line with total sum.
*
* @param string $line_str raw CSV line
*/
private function parseHeaderLine($line_str)
{
$fields_keys = array_keys(self::$fields);
$columns = $this->parseLine($line_str, $fields_keys, FALSE);
if ($columns['datum'] != self::HEADER_LINE_DATE_VALUE)
{
throw new Exception('Chybná hlavička výpisu.');
}
if (empty($columns['castka']))
{
throw new Exception('Chybná hlavička výpisu (suma).');
}
return $columns['castka'];
}

/**
* Checks headers that start data part of statement.
*
* @param integer $header_line header line
* @throws Exception
*/
private function checkHeaders($header_line)
{
$em = __("Nelze parsovat hlavičku Fio výpisu. Ujistěte se, že jste "
. "zvolili všech " . count(self::$fields) . " sloupců k importu "
. "v internetovém bankovnictví.");
$expected_header_cols = array_values(self::$fields);
// first column has issue with some UTF-8 characters
$fix_hl = preg_replace('/^[\x00-\x1F\x80-\xFF]+/', '', $header_line);
// extract header
$header_cols = str_getcsv($fix_hl, self::CSV_COL_DELIM,
self::CSV_COL_WRAPPER);
// check if extracted
if (empty($header_cols))
{
throw new Exception($em);
}
// check if count match
if (count($header_cols) != count($expected_header_cols))
{
throw new Exception($em);
}
// check each column
for ($i = count($header_cols) - 1; $i >= 0; $i--)
{
if ($header_cols[$i] != $expected_header_cols[$i])
{
throw new Exception($em);
}
}
}

/**
* Parse line of dump
*
* @param string $line
* @param array $keys
* @param boolean $parse_date
* @return array
* @throws Exception
*/
private function parseLine($line, $keys, $parse_date = TRUE)
{
$cols = str_getcsv($line, self::CSV_COL_DELIM, self::CSV_COL_WRAPPER);

if (count($cols) != count($keys))
{
throw new Exception('Chybný počet políček v položce výpisu.');
}

// Convert to associative array
$assoc_cols = array_combine($keys, $cols);

// Convert date
if ($parse_date)
{
$assoc_cols['datum'] = self::parseDate($assoc_cols['datum']);
}

// Amount has to be converted
$assoc_cols['castka'] = self::parseAmount($assoc_cols['castka']);

// Trim leading zeros from VS
$assoc_cols['vs'] = ltrim($assoc_cols['vs'], '0');
// join both "upresneni"
$assoc_cols['upresneni'] = trim($assoc_cols['upresneni_objem'] . ' ' .
$assoc_cols['upresneni_mena']);
// column prevod N/A
$assoc_cols['prevod'] = NULL;

return $assoc_cols;
}

/**
* Checks parsed money amount agains data from integrity line.
*
* @param array $sum
* @param integer $calculated_sum total counted sum
* @throws Exception on integrity error
*/
private function checkIntegrity($sum, $calculated_sum)
{
if (abs($sum - $calculated_sum) > 0.0001)
{
throw new Exception("Chybný kontrolní součet částky "
. "('$calculated_sum' != '$sum').");
}
}
/**
* Normalize string amount to double value.
*
* @example " 1000 278,40 " -> "1000278.40"
* @param string $amount
* @return double
* @throws InvalidArgumentException on invalid passed amount
*/
public static function parseAmount($amount)
{
$norm_amount = str_replace(array(' ', ','), array('', '.'), $amount);
if (!is_numeric($norm_amount))
{
$m = __('Invalid amount format') . ': ' . $amount;
throw new InvalidArgumentException($m);
}
return doubleval($norm_amount);
}

/**
* Parse date from format DD.MM.YYYY into YYYY-MM-DD.
*
* @param string $date in format DD.MM.YYYY
* @return string date in format YYYY-MM-DD
* @throws InvalidArgumentException on invalid date format
*/
public static function parseDate($date)
{
$matches = NULL;
if (!preg_match("/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/", $date, $matches))
{
$m = __('Invalid date format') . ': ' . $date;
throw new InvalidArgumentException($m);
}
$timestamp = mktime(0, 0, 0, $matches[2], $matches[1], $matches[3]);
return date('Y-m-d', $timestamp);
}

/**
* Transforms file content in passed charset into array of its lines encoded
* in UTF-8 encoding. This function must handle differences of end of line
* separators on all platforms.
*
* @param string $file_content file countent to be transformed
* @param string $charset charset of file content
* @return array array of lines in UTF-8 charset
*/
public static function transformFileToLineArray($file_content, $charset)
{
$internal_charset = 'UTF-8';
$fc_utf8 = NULL;
// transform to uTF-8
if (strtolower($charset) != strtolower($internal_charset))
{
$fc_utf8 = iconv($charset, $internal_charset, $file_content);
}
else
{
$fc_utf8 = $file_content;
}
// eplode lines
return preg_split("/\r\n|\n|\r/", $fc_utf8);
}

}