**/ class twIf_Convert_CSV extends twIf_Convert { const MIME_TYPE = 'text/csv'; const SUFFIX = 'csv'; // {{{ csvValue /** * Convert a given value into CSV-Format (which means 'escape it') * * @param string $Value * @param string $Seperator * * @access private * @return string **/ private static function csvValue ($Value, $Seperator) { $Value = trim (str_replace (array ("\n", "\r"), array (' ', ' '), $Value)); // Check if our seperator is inside the string if (strstr ($Value, $Seperator)) $Value = '"' . str_replace ('"', '""', $Value) . '"'; return $Value; } // }}} // {{{ csvSplit /** * Split a csv-row into its fields * * @param string $Value * @param string $Seperator * @param bool $skipEmptyCheck (optional) Allow empty rows * * @access private * @return array **/ private static function csvSplit ($Value, $Seperator, $skipEmptyCheck = false) { $out = array (); while (strlen ($Value) > 0) { // Escaped field if ($Value [0] == '"') { // Find end of field $p = 1; while (($p = strpos ($Value, '"', $p)) !== false) if ($Value [$p + 1] == '"') $p += 2; else break; if ($p !== false) { $out [] = str_replace ('""', '"', substr ($Value, 1, $p - 1)); $Value = substr ($Value, $p + strlen ($Seperator) + 1); } else { $out [] = str_replace ('""', '"', $Value); $Value = ''; } // Normal data inside row } elseif (($p = strpos ($Value, $Seperator)) !== false) { $out [] = substr ($Value, 0, $p); $Value = substr ($Value, $p + 1); // Normal data at end of row } else { $out [] = $Value; $Value = ''; break; } } // Check if there is at least one field filled with data if ($skipEmptyCheck) return $out; foreach ($out as $data) // Return the result if there is any data if (strlen ($data) > 0) return $out; // Return false if the row was all empty return false; } // }}} // {{{ export /** * @param array $Handles * @param array $Fields **/ public static function export ($Handles, $Fields) { // Start the buffer $Seperator = ';'; $l = strlen ($Seperator); $newLine = "\r\n"; $buf = ''; // Retrive the first handle foreach ($Handles as $Handle) if (is_object ($Handle)) break; // Generate the CSV-Header foreach ($Fields as $ID=>$Field) { $Fields [$ID]['def'] = $Definition = parent::parseFieldDef ($ID, $Field, $Handle, 0, true); $buf .= self::csvValue (isset ($Definition ['Caption']) ? $Definition ['Caption'] : $ID, $Seperator) . $Seperator; } // Trim the last seperator $buf = substr ($buf, 0, strlen ($buf) - $l) . $newLine; // Output the handles foreach ($Handles as $Handle) { // Output the values foreach ($Fields as $Field) $buf .= self::csvValue (parent::formatHuman ($Handle, $Field ['def']), $Seperator) . $Seperator; // Trim the last seperator $buf = substr ($buf, 0, strlen ($buf) - $l) . $newLine; } return $buf; } // }}} // {{{ import /** * Create a set of objects from a given CSV * * @param string $Data Plain CSV-Data * @param string $Classname * @param array $Fields (optional) Field-Definition * @param array $Constructor (optional) Which fields pass to constructor * @param string $Seperator (optional) * * @access public * @return array **/ public static function import ($Data, $Classname, $Fields = null, $Constructor = null, $Seperator = ';') { // Split the data into rows if (!is_array ($Data)) $Data = explode ("\n", $Data); // We need at least two rows if (count ($Data) < 2) return array (); // Parse the Fields foreach ($Fields as $ID=>$Field) { if (!is_array ($Fields [$ID]['def'] = parent::parseFieldDef ($ID, $Field, $Classname, 0, true))) { trigger_error (twIf_i18n::getText ('Could not parse field %s', $ID)); unset ($Fields [$ID]); continue; } $Fields [$ID]['def']['Caption'] = strtolower (trim ($Fields [$ID]['def']['Caption'])); } // Parse the header $hData = array_shift ($Data); $Header = array (); if (($hFields = self::csvSplit ($hData, $Seperator)) === false) { trigger_error (twIf_i18n::getText ('Could not parse definition for %s', 'CSV-Header')); return false; } foreach ($hFields as $ID=>$Name) { $Name = strtolower (trim ($Name)); $Header [$Name] = $ID; if (strlen ($Name) == 0) { trigger_error (twIf_i18n::getText ('Could not parse definition for %s', 'CSV/' . $ID)); return false; } } # TODO: Check if all required fields are there // Handle Rows on CSV $Objects = array (); foreach ($Data as $n=>$Row) { // Parse the row into fields if (!is_array ($fData = self::csvSplit ($Row, $Seperator))) continue; // Create a new object $Params = array (); $myFields = $Fields; if (is_array ($Constructor)) foreach ($Constructor as $Field) { // Pass Objects and Booleans directly to the constructor if (is_object ($Field) || is_bool ($Field) || is_null ($Field)) $Params [] = $Field; // Check if there is such field defined elseif (!isset ($myFields [$Field])) $Params [] = null; // Pass the field from CSV to the constructor else { if (!isset ($Header [$myFields [$Field]['def']['Caption']])) $Params [] = null; else # TODO: Maybe handle types here in an better way $Params [] = self::processValue ($myFields [$Field]['def'], $fData [$Header [$myFields [$Field]['def']['Caption']]]); unset ($myFields [$Field]); } } if (!is_object ($Instance = call_user_func_array (array ($Classname, 'newInstance'), $Params))) { trigger_error ($msg = twIf_i18n::getText ('Could not create class %s', $Classname)); self::storeDeferredImportCSV ($n, $fData, $Header, $msg); continue; } // Fill the object $Changed = false; foreach ($myFields as $Key=>$Field) { // Check if we have an index on the CSV for this field if (!isset ($Header [$Field ['def']['Caption']])) continue; // Retrive the value $Value = self::processValue ($Field ['def'], $fData [$Header [$Field ['def']['Caption']]]); // Set the field on our object if (!($rc = self::setFieldValue ($Instance, $Field ['def'], $Value))) { trigger_error ($msg = twIf_i18n::getText ('setFieldValue() for %s failed', $Field ['def']['Caption'])); self::storeDeferredImportCSV ($n, $fData, $Header, $msg); } $Changed = $Changed || ($rc && (is_array ($Field ['def']['Write']) || $Instance->checkUpdate ())); } if ($Changed && !$Instance->commit ()) { trigger_error ($msg = 'Could not commit handle'); self::storeDeferredImportCSV ($n, $fData, $Header, $msg); $Instance->remove (); continue; } $Objects [$Instance->getID ()] = $Instance; } return $Objects; } // }}} // {{{ storeDeferredImportCSV /** * Store a deferred CSV-Record * * @param int $ID * @param array $Data * @param array $Header * @param string $Message * * @access protected * @return void **/ protected static function storeDeferredImportCSV ($ID, $Data, $Header, $Message) { // Convert the CSV to a dummy-object $Handle = new twIf_Object_Dummy; foreach ($Header as $Name=>$Index) $Handle->$Name = $Data [$Index]; // Forward the deferred handle return self::storeDeferredImport ($ID, $Handle, $Message); } // }}} } ?>