#define _POSIX_C_SOURCE 200809L #include /* write, read, pread, close, unlinkat */ #include /* openat */ #include /* assert */ #include /* strlen, strcmp */ #include /* errno */ #include /* err */ /* Soubory typu CSV (Comma-Separated Values) existují v řadě * variant, které se liší mimo jiné použitým oddělovačem. Ostatní * vlastnosti budeme pro účely tohoto příkladu uvažovat pevné: * * • obsahuje-li v sobě některá hodnota právě používaný oddělovač, * musí být uzavřena v uvozovkách (jinak jsou uvozovky kolem * hodnot volitelné), * • obsahuje-li hodnota uzavřená v uvozovkách znaky ‹"› nebo ‹\›, * tyto musí být uvozeny znakem ‹\›, * • znak nového řádku v hodnotě nepřipouštíme, * • oddělovač, uvozovka, zpětné lomítko a znak nového řádku jsou * každý kódovány jedním bajtem. * * Vaším úkolem bude naprogramovat proceduru, která na vstupu * (zadaném pomocí popisovače) obdrží soubor v tomto formátu a na * výstup (opět popisovač) zapíše novou verzi, která bude: * * • používat zadaný oddělovač (může být jiný nebo stejný jako byl * na vstupu), * • právě ty hodnoty, které obsahují nový oddělovač, uzavře do * uvozovek. */ int reformat_csv( int fd_in, char old_delim, char new_delim, int fd_out ); /* ┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄┄┄┄ následují testy ┄┄┄┄┄┄┄┄┄┄ %< ┄┄┄┄┄┄┄ */ static void unlink_if_exists( int dir, const char* name ) { if ( unlinkat( dir, name, 0 ) == -1 && errno != ENOENT ) err( 2, "unlinkat '%s'", name ); } static void close_or_warn( int fd, const char *name ) { if ( close( fd ) == -1 ) warn( "closing %s", name ); } static int create_file( int dir, const char *name ) { unlink_if_exists( dir, name ); int fd; if ( ( fd = openat( AT_FDCWD, name, O_CREAT | O_TRUNC | O_RDWR, 0666 ) ) == -1 ) err( 2, "creating %s", name ); return fd; } static int check_output( int fd, const char *name, int offset, const char *expected ) { char buffer[ 4096 + 1 ] = { 0 }; if ( pread( fd, buffer, 4096, offset ) == -1 ) err( 2, "reading %s (fd %d)", name, fd ); return strcmp( expected, buffer ); } static void write_file( int dir, const char *name, const char *str ) { int fd = create_file( dir, name ); if ( write( fd, str, strlen( str ) ) == -1 ) err( 2, "writing file %s", name ); close_or_warn( fd, name ); } int main( void ) { int dir, fd_in, fd_out; const char *name_in = "zt.a_input.csv", *name_out = "zt.a_output.csv"; if ( ( dir = open( ".", O_RDONLY ) ) == -1 ) err( 2, "opening working directory" ); unlink_if_exists( dir, name_in ); unlink_if_exists( dir, name_out ); write_file( dir, name_in, "a;b,c\n" ); fd_out = create_file( dir, name_out ); if ( ( fd_in = openat( dir, name_in, O_RDONLY ) ) == -1 ) err( 2, "opening %s", name_in ); assert( reformat_csv( fd_in, ';', ',', fd_out ) == 0 ); assert( check_output( fd_out, name_out, 0, "a,\"b,c\"\n" ) == 0 ); if ( lseek( fd_in, 0, SEEK_SET ) == -1 ) err( 2, "seeking in %s", name_in ); assert( reformat_csv( fd_in, ',', ';', fd_out ) == 0 ); assert( check_output( fd_out, name_out, 8, "\"a;b\";c\n" ) == 0 ); unlink_if_exists( dir, name_in ); unlink_if_exists( dir, name_out ); close_or_warn( fd_in, name_in ); close_or_warn( fd_out, name_out ); close_or_warn( dir, "working directory" ); return 0; }