'code', 'codes' => 'code', 'serial' => 'code', 'product_name' => 'product_name', 'product' => 'product_name', 'name' => 'product_name', 'batch' => 'batch', 'lot' => 'batch', ); /** * Picks the delimiter used in the first line: comma, semicolon or tab. * * Spreadsheet apps in many European locales save CSV files with semicolons. * * @param string $line First line of the file. * @return string Delimiter. */ public static function detect_delimiter( $line ) { $line = (string) $line; $counts = array( ',' => substr_count( $line, ',' ), ';' => substr_count( $line, ';' ), "\t" => substr_count( $line, "\t" ), ); arsort( $counts ); $best = (string) key( $counts ); return $counts[ $best ] > 0 ? $best : ','; } /** * Removes a UTF-8 byte order mark from the start of a string. * * @param string $value Value. * @return string */ public static function strip_bom( $value ) { $value = (string) $value; return 0 === strpos( $value, "\xEF\xBB\xBF" ) ? substr( $value, 3 ) : $value; } /** * Builds a column map from a header row, or returns null when the row is not a header. * * A row is a header when its first cell is a known column name (e.g. "code"). * * @param array $row First row of the file. * @return array|null Column key => index. */ public static function header_map( array $row ) { if ( empty( $row ) ) { return null; } $first = self::header_key( (string) reset( $row ) ); if ( ! isset( self::HEADER_ALIASES[ $first ] ) ) { return null; } $map = array(); foreach ( array_values( $row ) as $index => $cell ) { $key = self::header_key( (string) $cell ); if ( isset( self::HEADER_ALIASES[ $key ] ) && ! isset( $map[ self::HEADER_ALIASES[ $key ] ] ) ) { $map[ self::HEADER_ALIASES[ $key ] ] = $index; } } return isset( $map['code'] ) ? $map : null; } /** * Default column map when the file has no header: code, product_name, batch. * * @return array */ public static function default_map() { return array_flip( self::COLUMNS ); } /** * Turns a CSV row into a record using a column map. * * @param array $row CSV row. * @param array $map Column map. * @return array{code: string, product_name: string, batch: string} */ public static function row_to_record( array $row, array $map ) { $row = array_values( $row ); $record = array(); foreach ( self::COLUMNS as $column ) { $value = isset( $map[ $column ], $row[ $map[ $column ] ] ) ? (string) $row[ $map[ $column ] ] : ''; $record[ $column ] = trim( $value ); } return $record; } /** * Tells whether a CSV row is empty (blank line). * * @param array $row CSV row. * @return bool */ public static function is_blank_row( array $row ) { foreach ( $row as $cell ) { if ( null !== $cell && '' !== trim( (string) $cell ) ) { return false; } } return true; } /** * Protects a free-text cell against formula injection in spreadsheet apps. * * @param mixed $value Cell value. * @return string */ public static function escape_cell( $value ) { $value = (string) $value; if ( '' !== $value && false !== strpos( "=+-@\t\r", $value[0] ) ) { return "'" . $value; } return $value; } /** * Normalizes a header cell for comparison. * * @param string $cell Header cell. * @return string */ private static function header_key( $cell ) { $cell = strtolower( trim( self::strip_bom( $cell ) ) ); return str_replace( array( ' ', '-' ), '_', $cell ); } }