REQ: Extended printf() with word-wrapping column format flag

N

nimdez

Hi,
I am working on an existing code base in which a lot of data displayed
to the user is formatted in tables. Most tables are printed row-by-row
using printf() with "%[width]s" print conversion specification for
each column (e.g. printf(%10s %25s %15s\n", pszCol1, pszCol2,
pszCol3)). My problem is that when a string is longer the column's
width, it overflows the column and takes the table out of alignment.
What I want it to do is word-wrap within the column, thus keeping the
table aligned.
The most desirable solution for this is an extended printf() function,
which accepts another format-flag that specifies the field as
word-wrapping. For example (new format flag is '='):
xprintf("%=10s %=25s %=15s\n", pszCol1, pszCol2, pszCol3)

1. Can anyone point me to an implementation of such extended printf()?
2. If not, any other creative solutions are most welcome.

Thanks,
Rod
 
M

Malcolm

nimdez said:
I am working on an existing code base in which a lot of data displayed
to the user is formatted in tables.
What I want it to do is word-wrap within the column, thus keeping the
table aligned.

xprintf("%=10s %=25s %=15s\n", pszCol1, pszCol2, pszCol3)

2. If not, any other creative solutions are most welcome.
If you try to support all of printf()'s format specifiers then you will have
a huge job on.
However it should be possible to write a

printcolumns(const char *fmt, ...)

which only takes %s specifiers. You then do the wrapping within the
function.
 
A

Arthur J. O'Dwyer

[snip]
The most desirable solution for this is an extended printf() function,
which accepts another format-flag that specifies the field as
word-wrapping. For example (new format flag is '='):
xprintf("%=10s %=25s %=15s\n", pszCol1, pszCol2, pszCol3)

1. Can anyone point me to an implementation of such extended printf()?
2. If not, any other creative solutions are most welcome.

I think you may have good luck examining the source code to various
"roguelike" computer games - for example, Angband and Nethack (although
maybe not Nethack for your purposes). These games typically have a
"status bar" on which are displayed messages such as "You hit the goblin
for 527 points of damage!", and spend a little CPU time trying to make
those messages look pretty. Word wrapping and such. (Any status-bar
game will have this sort of code somewhere, but roguelikes are
traditionally more open.)


However, wouldn't this be easier?
[extremely untested code]

int wrprintf(int llen, const char *fmt, ...)
{
va_list ap;
char buffer[REALLY_BIG_NUMBER];
int slen;
int start, end, i;
int rc;

/* Use the existing library */
va_start(ap, fmt);
rc = vsprintf(buffer, fmt, ap);
va_end(ap);

slen = strlen(buffer);

for (start = 0; start < slen-llen; )
{
end = start+llen;
while (! isspace(buffer[end])) {
--end;
if (end == start) {
end = start+llen;
break;
}
}

/* print one full line */
for (i=start; i < end; ++i)
putchar(buffer);
/* end of line, go to next line */
putchar('\n');

start = end;
while (isspace(buffer[start]))
++start;
}

/* print last line */
for (i=start; i < slen; ++i)
putchar(buffer);
putchar('\n');

return rc;
}


Now, if you want word wrapping within *each* column separately,
Excel-style, you'll need a lot more thinking in order to do
that in plain C.

HTH,
-Arthur
 
N

Nick Austin

Hi,
I am working on an existing code base in which a lot of data displayed
to the user is formatted in tables. Most tables are printed row-by-row
using printf() with "%[width]s" print conversion specification for
each column (e.g. printf(%10s %25s %15s\n", pszCol1, pszCol2,
pszCol3)). My problem is that when a string is longer the column's
width, it overflows the column and takes the table out of alignment.
[snip]

2. If not, any other creative solutions are most welcome.

I took a simpler solution, which is to reduce the widths of subsequent
columns to bring the table back into alignment. The basic code to
this this is something like this (although to be robust you should
ensure that the '*' parameter is not zero or negative):

int column = 0;

column += printf( "%10s ", pszCol1 );
column += printf( "%*s ", 36 - column, pszCol2 );
column += printf( "%*s ", 52 - column, pszCol2 );
printf( "\n" );

Nick.
 
M

Malcolm

nimdez said:
xprintf("%=10s %=25s %=15s\n", pszCol1, pszCol2, pszCol3)
The problem is it is difficult to pass most of the code on to vsprintf() to
do the work. This is because of the nature of variadic functions.

However this might be useful to you. (Tested slightly).

/*
print a series of strings and a format string, wrapping
to preserve columns.
Currently only the %<width>s format is supported
*/
void colprint(char *fmt, ...)
{
int nargs = 0;
char ***column;
int *tabs;
int *widths;
int i;
int ii;
int Npercent = 0;

int col = 0;
int width;
char *end;
char *str;
va_list vargs;


/*
In order to simplify the code, allocate enough space
here based on number % signs passed
*/
for(i=0;fmt;i++)
if(fmt == '%')
Npercent++;

column = malloc(Npercent * sizeof(char **));
tabs = malloc(Npercent * sizeof(int));
widths = malloc(Npercent *sizeof(int));
if(!column || !tabs || !widths)
goto cleanup;

va_start(vargs, fmt);

while(*fmt)
{
if(*fmt == '%')
{
switch( getfieldtype(fmt) )
{
case '%':
putchar('%');
fmt += 2;
break;
case 's':
width = strtol(fmt+1, &end, 10);
tabs[nargs] = col;
widths[nargs] = width;

str = va_arg(vargs, char *);
column[nargs] = strwrap(str, width);
if(!column[nargs])
goto cleanup;

for(i=0;column[nargs][0];i++)
{
putchar( column[nargs][0] );
col++;
}
while(i<width)
{
putchar(' ');
col++;
i++;
}
nargs++;
while(*fmt != 's')
fmt++;
break;
/* add other fields here if you wish to support them */
default:
fprintf(stderr, "Format %c not recognised\n", getfieldtype(fmt));
return;
}
}
else
{
putchar(*fmt);
col++;
fmt++;
}
}

va_end(vargs);


printcolumns(column, tabs, widths, nargs);

cleanup:
for(i=0;i<nargs;i++)
{
for(ii=0;column[ii];ii++)
free(column[ii]);
free(column);
}
if(column)
free(column);
if(tabs)
free(tabs);
if(widths)
free(widths);
}

/*
get the type of field we are passed.
*/
char getfieldtype(char *fmt)
{
assert(*fmt == '%');
fmt++;
if(*fmt == '%')
return '%';

while(*fmt && !isalpha(*fmt))
fmt++;

return *fmt;
}

/*
Prints the wrapped text in the right column.
cols - pointer to list of wrapped text
tabs - tab offset of each column
width - widths of each column
ncols - number of columns passed
*/
void printcolumns(char ***cols, int *tabs, int *widths, int ncols)
{
int i;
int ii;
int iii;
int *coldepth;
int maxdepth = 0;
int col;

coldepth = malloc(ncols * sizeof(int));

if(!coldepth)
return;

/* find the depth of each column (NULL-terminated list)
for(i=0;i<ncols;i++)
{
coldepth = 0;
while(cols[coldepth])
coldepth++;
}

/* find the maximum depth */
for(i=0;i<ncols;i++)
if(maxdepth < coldepth)
maxdepth = coldepth;

/* print out the column */
for(i=1;i<maxdepth;i++)
{
col = 0;
for(ii=0;ii<ncols;ii++)
{
while(col < tabs[ii])
{
putchar(' ');
col++;
}
if(coldepth[ii] > i)
{
for(iii=0;cols[ii][iii];iii++)
{
putchar( cols[ii][iii] );
col++;
}
}
while(col < tabs[ii] + widths[ii])
{
putchar(' ');
col++;
}
}
putchar('\n');
}

free(coldepth);

}


/*
string-wrapping function.
Params: str - string to wrap
len - maximum length of column
Returns: NULL-terminated list of truncated strings
*/
char **strwrap(const char *str, int len)
{
char **answer = 0;
char **temp;
char *buff;
int nlines = 0;
int i;

while(*str)
{
temp = realloc(answer, (nlines + 2) * sizeof(char *));
if(!temp)
{
for(i=0;i<nlines;i++)
free(answer);
free(answer);
return 0;
}
answer = temp;

buff = malloc(len+1);
if(!buff)
{
for(i=0;i<nlines;i++)
free(answer);
free(answer);
return 0;
}

wrapline(buff, str, len);
answer[nlines++] = buff;
str += strlen(buff);
}

answer[nlines] = 0;

return answer;
}

/*
wrap a line of text
ret - return pointer for line
str - text to wrap
len - maximum length of line
*/
void wrapline(char *ret, const char *str, int len)
{
char *end;

if(len > (int) strlen(str))
len = strlen(str);

if(len < 1)
{
strcpy(ret, "");
return;
}

end = strchr(str, '\n');
if(end && end - str < len)
{
memcpy(ret, str, end - str + 1);
ret[end-str+1] = 0;
return;
}

memcpy(ret, str, len);
end = &ret[len-1];

if(str[len] != 0)
while(end > ret && !isspace(*end))
end--;

if(end > ret)
end[1] = 0;
else
ret[len] = 0;
}
 
R

Roger Willcocks

nimdez said:
Hi,
I am working on an existing code base in which a lot of data displayed
to the user is formatted in tables. Most tables are printed row-by-row
using printf() with "%[width]s" print conversion specification for
each column (e.g. printf(%10s %25s %15s\n", pszCol1, pszCol2,
pszCol3)). My problem is that when a string is longer the column's
width, it overflows the column and takes the table out of alignment.
What I want it to do is word-wrap within the column, thus keeping the
table aligned.
The most desirable solution for this is an extended printf() function,
which accepts another format-flag that specifies the field as
word-wrapping. For example (new format flag is '='):
xprintf("%=10s %=25s %=15s\n", pszCol1, pszCol2, pszCol3)

1. Can anyone point me to an implementation of such extended printf()?
2. If not, any other creative solutions are most welcome.
Rather than trying to rewrite printf, I'd go for a two step approach:

setcolumnsize(10, 25, 15, 0);
xprintf(%10s|%25s|%15s", s1, s2, s3);

Where xprintf calls vsprintf, and then reprocesses the resulting |-separated
columnar text according to the previously defined column widths. (If you
expect to see | in your data, use another marker.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top