[comp.lang.c] How to FSEEK previous line of text?

maslar@iccgcc.decnet.ab.com (05/07/91)

Does anyone know of a function or technique that is similar to FSEEK
that will allow me to go back to the previous line? The lines of
ASCII text are being read from a file by the FGETS function. The
length of the lines varies. I'm writing an application-specific
file reader, and need to implement a PageUp and UpArrow feature.

Thanks,

Mark Maslar

INTERNET: maslar@icd.ab.com 

henry@zoo.toronto.edu (Henry Spencer) (05/08/91)

In article <4508.28269613@iccgcc.decnet.ab.com> maslar@iccgcc.decnet.ab.com writes:
>Does anyone know of a function or technique that is similar to FSEEK
>that will allow me to go back to the previous line? ...

There is no provision for this in the standard libraries, because it can
be arbitrarily hard to implement on systems that store text files in odd
ways.  You can build it yourself, however, by simply using ftell() to keep
track of the location of the previous line, and fseek()ing there on demand.
-- 
And the bean-counter replied,           | Henry Spencer @ U of Toronto Zoology
"beans are more important".             |  henry@zoo.toronto.edu  utzoo!henry

wirzeniu@kruuna.Helsinki.FI (Lars Wirzenius) (05/09/91)

In article <4508.28269613@iccgcc.decnet.ab.com> maslar@iccgcc.decnet.ab.com writes:
>Does anyone know of a function or technique that is similar to FSEEK
>that will allow me to go back to the previous line?

You can do two things: 1) save all the previous text in a buffer in the
memory, or 2) save the positions of the starts of the lines in memory.

The former method requires plenty of memory (at least as much as the
file size), so it's probably not a very good idea. The latter method
requires you to call ftell at the start of each line and store the value
in an array, something like:

	long line_starts[MAX_LINES];

	/* ... */
	lines_starts[line_no] = ftell(input_file);
	/* read the line */
	/* ... */

Note that you can't assume that the value returned by ftell is in any
way related to the number of characters on the line, so you can't just
do fseek(f, -linelen, SEEK_CUR), well, at least not portably.

You might have a problem if the input isn't coming from a file that is
seekable, e.g. pipes and terminals aren't. In this case you could save
the input in a temporary file, and fseek that instead.

You might want to look at the source of less, which implements these
kinds of things.
-- 
Lars Wirzenius  wirzenius@cc.helsinki.fi

martin@mwtech.UUCP (Martin Weitzel) (05/09/91)

In article <1991May8.154638.21318@zoo.toronto.edu> henry@zoo.toronto.edu (Henry Spencer) writes:
>In article <4508.28269613@iccgcc.decnet.ab.com> maslar@iccgcc.decnet.ab.com writes:
>>Does anyone know of a function or technique that is similar to FSEEK
>>that will allow me to go back to the previous line? ...
>
>There is no provision for this in the standard libraries, because it can
>be arbitrarily hard to implement on systems that store text files in odd
>ways.  You can build it yourself, however, by simply using ftell() to keep
>track of the location of the previous line, and fseek()ing there on demand.

Which brings us to the Question:

What is more portable in ANSI-C - ftell()/fseek() or fgetpos()/fsetpos().

As I understand, the former has some limitations if "really large" files
must be processed. On the other hand it has the advantage that at least on
binary streams you can calculate the number of bytes to skip forward or
backward from a known place. Since this ability is not needed here (nor
is it guaranteed for text files), I think fgetpos()/fsetpos() is the way
to go (provided the compiler of the person who posted the original
question is ANSI-ish enough to have these functions).
-- 
Martin Weitzel, email: martin@mwtech.UUCP, voice: 49-(0)6151-6 56 83

toma@swsrv1.cirr.com (Tom Armistead) (05/11/91)

In article <4508.28269613@iccgcc.decnet.ab.com> maslar@iccgcc.decnet.ab.com writes:
>
>Does anyone know of a function or technique that is similar to FSEEK
>that will allow me to go back to the previous line? The lines of
>ASCII text are being read from a file by the FGETS function. The
>length of the lines varies. I'm writing an application-specific
>file reader, and need to implement a PageUp and UpArrow feature.
>
>Thanks,
>
>Mark Maslar
>
>INTERNET: maslar@icd.ab.com 

In a text file, you can generally assume that the lines will have a max length
of BUFSIZ.  So, you could seek backward BUFSIZ*2 bytes, read the number of
bytes that were backward seeked and search from the end of that buffer,
backward until you find a the \n prior to the previous line.

Or you could fseek back one character at a time, read that character and look
for '\n's.

Either way is going to be fairly slow (the second option will be REAL slow).

I did this once using the 1st method, like this:

/*===========================================================================*/
#include <stdio.h>
/*
**  Seek back 1 line in the text file associated with the passed fp.
**  Returns 0 on success, or -1 on error (probally because positioned at 1st
**  line in file).
*/
int
backup_one_line( fp )
FILE *fp;
{
    char buf[BUFSIZ*2];                 /* buffer for fread                  */
    long posn;                          /* ftell() position                  */
    int backup_ofs;                     /* distance arg to fseek             */
    int indx;                           /* index into buf[]                  */
    int ret=-1;                         /* function return; assume error     */
    int lf_cnt = 0;                     /* line feed character counter       */

    posn = ftell( fp );                 /* get current position in file      */

    /*************************************************************************
    ** If not far enough down in the file to read the the entire buffer size,
    ** then read all of the file up to the current point.
    **************************************************************************/

    if( posn <= (long)sizeof( buf ) )
        backup_ofs = posn;
    else                                /* else read entire buffer from file */
        backup_ofs = sizeof( buf );

    fseek( fp, (long)-backup_ofs, 1 );  /* backup size of buffer             */
    fread( buf, backup_ofs, 1, fp );    /* read buffer into memory           */

    /*************************************************************************
    ** Search backward from the end of the buffer to the 3rd \n character.
    ** 1st one is for end of current line, 2nd is for end of previous
    ** line and 3rd is for 1 character before previous line.
    **************************************************************************/

    for( indx=backup_ofs-1; indx > 0; indx-- )
        if( buf[indx] == '\n' && ++lf_cnt == 3 )        /* count \n's        */
            break;                                      /* leave on 3rd one  */

    if( indx > 0 )                      /* if 3rd linefeed back found        */
    {
        ret = 0;                        /* signify success                   */
        posn = (backup_ofs - indx - 1); /* position to start of prev line    */
        fseek( fp, -posn, 1 );          /* position there                    */
    }
    else if( indx == 0 && lf_cnt == 2 ) /* else, prev line is 1st in file    */
    {
        ret = 0;                        /* signify success                   */
        fseek( fp, 0, 0 );              /* move to start of file             */
    }

    /*************************************************************************
    ** Else positioned at 1st line in file (can't go back) or was unable to
    ** find \n line separators.
    **************************************************************************/
    else
        fseek( fp, posn, 0 );           /* position back to original pos'n   */

    return ret;
}/*end backup_one_line*/
/*===========================================================================*/

Tom
-- 
Tom Armistead - Software Services - 2918 Dukeswood Dr. - Garland, Tx  75040
===========================================================================
toma@swsrv1.cirr.com                {egsner,letni,ozdaltx,void}!swsrv1!toma

wirzeniu@klaava.Helsinki.FI (Lars Wirzenius) (05/11/91)

In article <1991May11.063436.18318@swsrv1.cirr.com> toma@swsrv1.cirr.com (Tom Armistead) writes:
>In a text file, you can generally assume that the lines will have a max length
>of BUFSIZ.  So, you could seek backward BUFSIZ*2 bytes, read the number of

Unless I'm very much mistaken, as far as text streams are concerned, you
can't portably hand fseek any other values than 0 and those earlier
returned by ftell. You can't do 'fseek(f, -BUFSIZ, SEEK_CUR)', since the
values returned by ftell are not guaranteed to be simple byte offsets
(such offsets don't work very well for text files in all environments).
-- 
Lars Wirzenius  wirzenius@cc.helsinki.fi