Newsgroup: it.comp.lang.c
Titolo: Leggere caratteri da un file e creare una stringa

Devo leggere una serie di caratteri da stdin e memorizzarli in una
stringa. Il problema è che non so a priori quanti caratteri saranno letti. 
Non so come risolvere questo semplice problema. Sto realizzando un
programmino CGI eseguito in un web server che legge i dati di un form. Per
stringhe di lunghezza definita a priori il programma già funziona. 
Francesco
-- 
-------------------------------------
http://www.riscossione.info/

 Francesco
Imho, l'argomento del tuo post e' interessante e, probabilmente, utile a 
molti. Ho quindi riadattato (e volentieri la espongo la pubblico 
ludibrio :) ) una vecchia funzione fgets-like dinamica che avevo fatto 
un tempo (c'e' voluto di piu' a commentarla che a riadattarla... ma 
visto che ieri era domenica... :) ).
Se vuoi, provala... sono 2 files: dyn_fgets.h e dyn_fgets.c.
Ps: leggi bene doc. ed esempi d'uso.
Ciao
~~~ dyn_fgets.h ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*
dyn_fgets.h
(c) Cybertoldo - open source/public domain :)
*/
#ifndef DYN_FGETS_H
#define DYN_FGETS_H
#include <stdio.h>
#define DFG_ERR_EOF	-1
#define DFG_ERR_READ	-2
#define DFG_ERR_NOMEM	-3
char *dyn_fgets(FILE *fp, char *pLastDynStr, int *pnRes);
#endif
~~~ dyn_fgets.c ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/*---------------------------------------------------------*
dyn_fgets.c
(c) Cybertoldo - open source/public domain :)
char *dyn_fgets(FILE *fp, char *pLastDynStr, int *pnRes);
Legge sequenzialmente da <fp> una linea di testo, allocando
dinamicamente lo spazio di memoria necessario. Il carattere di
fine linea ('\n') viene restituito nella stringa.
* Valore di ritorno:
Restituisce il puntatore (char *) alla stringa dinamicamente
allocata. Restituisce NULL su EOF, o su errore di lettura o
di memoria insufficiente.
* Parametri:
<fp>: puntatore di tipo FILE allo stream
<pnRes>: accetta NULL, oppure un puntatore a 'int'. Nell'ultimo
caso, se il valore restituito nell'intero e' >= 0, questo indica la 
lunghezza della linea letta; se il valore e' < 0, indica un errore:
	DFG_ERR_EOF (lettura oltre la fine del file)
	DFG_ERR_READ (errore di lettura)
	DFG_ERR_NOMEM (memoria insufficiente)
<pLastDynStr>: accetta NULL, oppure il puntatore alla stringa
allocata e restituita dall'ultima chiamata a questa funzione;
in questo caso la memoria viene successivamente riutilizzata e
ridimensionata (realloc) per la prossima linea. Se invece questo
parametro viene impostato a NULL, e' sempre compito dell'utente
liberare la memoria allocata dalla funzione.
* Esempi di chiamata:
=== 1 ===
char *pBf;
while ((pBf = dyn_fgets(stdin, NULL, NULL)))
{	fputs(pBf);
	free(pBf);
	pBf = NULL;
}
if(pBf)
	free(pBf);
=== 2 ===
char *pBf = NULL; // importante == NULL !
while ((pBf = dyn_fgets(stdin, pBf, NULL)))
	fputs(pBf);
if(pBf)
         free(pBf);
=== 3 ===
int nLenOrErr;
char *pBf = NULL; // importante == NULL !
while ((pBf = dyn_fgets(stdin, pBf, &nLenOrErr)))
{	// in nLenOrErr: lunghezza linea, compreso '\n'
	fputs(pBf);
}
if(pBf)
         free(pBf);
if(nLenOrErr < 0)
	fprintf(stderr, "Errore %d....\n", nLenOrErr);
*----------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "dyn_fgets.h"
#define DYN_FGETS_ALLOC_CHUNK	2048
/* ENOMEM - non standard C99, ma di solito definito */
#ifdef ENOMEM
#define DYN_FGETS_SET_ENOMEM(e) ((e) = ENOMEM)
#else
#define DYN_FGETS_SET_ENOMEM(e)
#endif
char *dyn_fgets(FILE *fp, char *pLastDynStr, int *pnRes)
{
	char *p, *pBuffer = NULL;
	int nCurrBfSize = DYN_FGETS_ALLOC_CHUNK;
	int nRes = DFG_ERR_NOMEM;
	int c, nCh;
	if(pnRes)
		*pnRes = nRes;
	if(pLastDynStr)
		pBuffer = pLastDynStr;
	if(pBuffer)
	{	if(!(p = realloc(pBuffer, nCurrBfSize)))
		{	DYN_FGETS_SET_ENOMEM(errno);
			goto esci;
		}
		pBuffer = p;
	}
	else if(!(pBuffer = malloc(nCurrBfSize)))
	{	DYN_FGETS_SET_ENOMEM(errno);
		goto esci;
	}	
	for(nCh = 0; (c = fgetc(fp)) != EOF; )
	{	if(nCh >= nCurrBfSize-1)
		{	nCurrBfSize += DYN_FGETS_ALLOC_CHUNK;
			if(!(p = realloc(pBuffer, nCurrBfSize)))
			{	DYN_FGETS_SET_ENOMEM(errno);
				goto esci;
			}
			pBuffer = p;
		}
		pBuffer[nCh++] = c;
		if(c == '\n')
			break;
	}
         if(ferror(fp))
	{	nRes = DFG_ERR_READ;
		goto esci;
	}
	if(nCh == 0 && c == EOF)
	{	nRes = DFG_ERR_EOF;
		goto esci;
	}
	if(!(p = realloc(pBuffer, nCh+1))) /* ridimensiona */
	{	DYN_FGETS_SET_ENOMEM(errno);
		goto esci;
	}
	pBuffer = p;
	pBuffer[nCh] = '\0';
	nRes = nCh;	/* OK: lunghezza stringa in pBuffer */
esci:
	if(nRes < 0)
	{	if(pBuffer)
			free(pBuffer);
		pBuffer = NULL;
	}
	if(pnRes)
		*pnRes = nRes;
	return pBuffer;
}
/* == esempio d'uso: ==============
per provare la funzione compilare con (eg.):
cc -DTEST_DYN_FGETS -o test_dyn_fgets dyn_fgets.c
*/
#ifdef TEST_DYN_FGETS
int main(void)
{
	FILE	*fp = stdin;
	char	*pBf = NULL;
	int 	nRetLenOrErrNum;
	/* importante! inizializzare a NULL pBf */
	for(pBf = NULL; (pBf = dyn_fgets(fp, pBf, &nRetLenOrErrNum)); )
	{	/* se si desidera togliere lo '\n': */
		if(nRetLenOrErrNum > 0 && pBf[nRetLenOrErrNum-1] == '\n')
	        	pBf[--nRetLenOrErrNum] = '\0';
		/* stampa la lunghezza e la linea */
		printf("%5d > %s\n", nRetLenOrErrNum, pBf);
	}
	/* in caso di "break" dal ciclo sopra, la memoria
	   per pBf rimane allocata */
	if(pBf)
		free(pBf);
	/* un esempio di controllo d'errori dopo il ciclo */
	if(nRetLenOrErrNum == DFG_ERR_READ)
	{	fprintf(stderr, "- Errore di lettura.\n");
		exit(EXIT_FAILURE);
	}
	else if(nRetLenOrErrNum == DFG_ERR_NOMEM)
	{	fprintf(stderr, "- Memoria insufficiente.\n");
		exit(EXIT_FAILURE);
	}
	return EXIT_SUCCESS;
}
#endif
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- 
Cybertoldo
Per rispondermi via email, sostituisci moc.liamg con 
gmail.com





Ciao


 Ciao
Si, qua non ricevevo nessuna risposta. Ed ero curioso di risolvere il
problema. Francesco
-- 
-------------------------------------
http://www.riscossione.info/

Il Mon, 27 Nov 2006 15:17:00 +0100, Cybertoldo ha scritto:
Ti ringrazio, sei stato molto gentile, anche se fors il problema che
dovevo risolvere era differente. Posto il mio codice, in modo che ha
risolto il problema, ma che deve essere migliorato. Sicuramente ci saranno
molti errori. Vi prego di segnalarmeli. Non me la cavo molto bene con i
puntatori a stringhe.
// Parsing della query_string
struct nodo *parse_query_string(char *query_string, struct nodo *testa)
{
int pos1=0, pos2=0;
int dim_valore, dim_nome;
char *coppia,*nome, *valore;
char *pos;
while (strlen(query_string)>2)
{
      pos1=find_char(query_string,'&');
         if (pos1>0)    
           {
              coppia=(char *)malloc(pos1); 
              strncpy(coppia,query_string,pos1-1);
              coppia[pos1-1]='\0'; 
           }
            pos=strchr(query_string,'&');
            if (pos!=0) strcpy(query_string,pos+1);
               else 
                     {
                        coppia=(char *)malloc(strlen(query_string)); 
                        strncpy(coppia,query_string,strlen(query_string));
                        coppia[strlen(query_string)]='\0';
                        *query_string='\0';
                      }
            pos2=find_char(coppia,'=');
              if (pos2>0)
                     {
                        nome=(char *)malloc(pos2);
                        dim_valore=strlen(coppia)-pos2;
                        valore=(char *)malloc(dim_valore+1);
                        strncpy(nome,coppia,pos2-1);
                        nome[pos2]='\0';
                        strcpy(valore,strchr(coppia,'=')+1);
                        valore[dim_valore]='\0';
                        testa=crea_nodo(testa,pos2+dim_valore);
                        testa->nome=(char *)malloc(pos2);
                        testa->valore=(char *)malloc(dim_valore+1);
                        strcpy(testa->nome,nome);
                        strcpy(testa->valore,valore);
                      }
}
return testa;


 puntatori a stringhe.
Puo' darsi che funzioni, nonostante incognite e parti di codice strane o 
che non mi sono chiare. Vedo, pero', che non hai fatto alcun controllo 
d'errore sulle chiamate alle malloc() e alla crea_nodo()...
===
Cmq., con un semplice compromesso [1], l'algoritmo di parsing potrebbe 
essere notevolmente semplificato, eliminando anche macchinosi calcoli di 
spiazzamento che - un domani - potrebbero essere difficili da rileggere 
per fare delle modifiche.
Sperando sia utile, ti posto questa alternativa (e' solo l'algoritmo di 
parsing, la funzione stub() e' un place-holder per la tua crea_nodo() o 
simile):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int stub(char *pNome, char *pValore)
{	/* qui andrebbe fatto l'escape delle stringhe per
	   decodificarle (eg. hex "%XX" in un car. ASCII;
	   il '+' diventa uno ' '; gestione encoding...) */
	printf("==> NOME=%s VALORE=%s\n", pNome, pValore);
	return 0;
}
static int parseQueryString(char *pQueryString)
{
	char *pCurrPair, *pNextPair;
	if(!pQueryString)
		return 0;
	for(pCurrPair = pNextPair = pQueryString; pCurrPair; pCurrPair = pNextPair)
	{	char *p, *pVarName, *pVarValue;
		if((p = strchr(pCurrPair, '&')))
			*p++ = '\0';
		pNextPair = p;
		pVarName = pCurrPair;
		pVarValue = "";
		if((p = strchr(pVarName, '=')))
		{	*p++ = '\0';
			pVarValue = p;
		}
		while(*pVarName == ' ')
			++pVarName;
		if(*pVarName)
			stub(pVarName, pVarValue);
	}
	return 1;
}
int main(void)
{
	char sQueryString[] = "nome1=valore1&nome2=valore2&pippo=pluto&paperino=";
	/* attenzione: la sQueryString viene letteralmente "fatta a pezzi" 
dalla funz. sotto: valutare se passarne una copia */
	parseQueryString(sQueryString);
	return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Immagino che la successiva parte che ti terra' impegnato sara' quella di 
fare l'escape sui caratteri variabile-valore estratti dalla query-string 
CGI (qui, se vuoi, ti guardo se ho qualcosa di gia' fatto) e quindi di 
gestire l'encoding (eg. iso-8859-1, utf-8,..).
Ciao
NOTE:
[1] Il compromesso e' quello di poter passare alla parseQueryString() 
una stringa allocata dinamicamente (o un array) "usa e getta" (puoi 
anche passargli una copia dell'originale con una funzione tipo 
strdup()). Questo perche' la funzione parseQueryString() spezza la 
stringa in ingresso in + parti rendendola quindi inutilizzabile 
successivamente (ma serve averla ancora intatta una volta fatto il 
parsing e copiato i valori in una struttura?).


 gestire l'encoding (eg. iso-8859-1, utf-8,..).
SI, esatto. Dopo aver risolto questo problema, devo passare all'encoding.
Se hai qualcosa già fatto, e potresti passarmelo, me lo studio. Te ne
sarei grato.
Francesco
 
-------------------------------------
http://www.riscossione.info/

 simile):
Credo sia più leggibile del mio codice, e forse meglio costruito.
Grazie di nuovo per i consigli e suggerimenti
Francesco
-------------------------------------
http://www.riscossione.info/

).
Probabilmente si potrebbe fare di meglio ma, purtroppo, non ho molto 
tempo per renderle piu' eleganti :) Cmq spero che funzionino; ti prego 
di segnalare eventuali errori.
Per rendere + chiaro l'utilizzo, ecco il merge di tutto:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
/*  lcHex2Char: Prende i primi 2 car. puntati da pNextTwoHexChars e,
     se sono hex, li converte ritornando il val. in un int.
     Restituisce -1 su errore.
*/
static int lcHex2Char(char *pNextTwoHexChars)
{	int i, nRes = 0;
	unsigned char c;
	if(!pNextTwoHexChars || !*pNextTwoHexChars || !pNextTwoHexChars[1])
		return(-1);
	for(i = 0; i < 2; i++)
	{	nRes <<= 4;
		c = pNextTwoHexChars[i];
		if(c >= 'a' && c <= 'f')
			nRes |= ((c - 'a') + 10) & 0xf;
		else if(c >= 'A' && c <= 'F')
			nRes |= ((c - 'A') + 10) & 0xf;
		else if(c >= '0' && c <= '9')
			nRes |= (c - '0') & 0xf;
		else
		{	nRes = -1;
			break;
		}
	}
	return(nRes);
}
/* lcPlus2Spaces: converte car. '+' in spazi ' '.
    Modifica direttamente la stringa passata in <pString>.
*/
static char *lcPlus2Spaces(char *pString)
{	int i;
	for(i = 0; pString[i]; i++)
		if(pString[i] == '+')
			pString[i] = ' ';
	return pString;
}
/* lcUnescape: Converte una seq. %XX (dove XX e' un numero hex) nel
    carattere ASCII corrispondente. Viene direttamente modificato
    il contenuto della stringa passata in pString. La stringa
    modificata sara' di lunghezza minore od uguale all'originale.
    Torna 0 su errore; oppure 1 se la stringa e' stata convertita.
*/
static int lcUnescape(char *pString)
{	int 	i, x, c;
	char	*pDest = NULL;
	int	bRes = 0;
	if(!pString)
		return(0);
	if(!*pString)
		return(1); /* stringa vuota, ok */
	if(!(pDest = malloc(strlen(pString)+1)))
		goto esci;
	for(x = i = 0; pString[i]; )
	{	if(pString[i] == '%')
		{       if(pString[++i] == '\0')
				break;
			c = lcHex2Char(&pString[i]);
			/* c puo' essere 0 o -1 su errore: viene controllato poi */
			if(pString[i+1] != '\0')
				i++;  /* salta il secondo car. hex */
		}
		else 	c = pString[i];
		if(c > 0)
			pDest[x++] = c;
		i++;
		assert(x <= i);
	}
	pDest[x] = '\0';
	strcpy(pString, pDest);
	bRes = 1;
esci:
	if(pDest)
		free(pDest);
	return(bRes);
}
/*== Esempio di parsing =========*/
/* funzione stub() di esempio */
static int stub(char *pNome, char *pValore)
{	
	printf("==> NOME=%s VALORE=%s\n", pNome, pValore);
	return 0;
}
/* Esempio di funzione di parsing di query string CGI, l'output
    viene scritto a video dalla funzione stub().
    La stringa passata in pQueryString deve essere contenuta in
    un array o allocata dinamicamente.
    NB: La funzione altera il contenuto di pQueryString rendendolo
    inutilizzabile: valutare se passarne una copia.
*/
static int parseQueryString(char *pQueryString)
{
	char *pCurrPair, *pNextPair;
	if(!pQueryString)
		return 0;
	for(pCurrPair = pNextPair = pQueryString; pCurrPair; pCurrPair = pNextPair)
	{	char *p, *pVarName, *pVarValue;
		if((p = strchr(pCurrPair, '&')))
			*p++ = '\0';
		pNextPair = p;
		pVarName = pCurrPair;
		pVarValue = "";
		if((p = strchr(pVarName, '=')))
		{	*p++ = '\0';
			pVarValue = p;
		}
		while(*pVarName == ' ')
			++pVarName;
		if(*pVarName)
		{	lcUnescape(lcPlus2Spaces(pVarName));
			lcUnescape(lcPlus2Spaces(pVarValue));
			stub(pVarName, pVarValue);
		}
	}
	return 1;
}
/* test */
int main(void)
{
	char sQueryString[] = 
"nome%5F1=Stringa+di+prova&nome%5F2=car%2E+e+commerc%2E%3A+%26&pippo=pluto+e+paperino=&a=";
	parseQueryString(sQueryString);
	return 0;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~
<VERY-OT>
Per quanto riguarda l'encoding: non dovresti avere problemi se lavori 
con pagine web con una codifica Latin-1 (iso-8859-1, iso-8859-15, 
windows-1252), se invece lavori (eg.) in utf-8 il discorso cambia... ma 
rimane sempre relativo al tipo di codifica del testo che hai scelto di 
usare.
</VERY-OT>
Ciao
