...read a string from a text file at a certain line number?

Author: Barry Kelly

Category: Files

{
 Abstract:
  Im trying to write a function that, given a FileName and a line number
  returns the entire line in a string.
}

{
 The following technique is useful for high-speed processing.
 The sample program file, save it with a .pas or .dpr filename and compile it.
}


{$APPTYPE CONSOLE}
uses SysUtils, Classes;

function GrabLine(const AFileName: string; ALine: Integer): string;
var
  
fs: TFileStream;
  buf: packed array[0..4095] of Char;
  bufRead: Integer;
  bufPos: PChar;
  lineStart: PChar;
  tmp: string;
begin
  
fs := TFileStream.Create(AFileName, fmOpenRead);
  try
    
Dec(ALine);
    bufRead := 0;
    bufPos := nil;

    { read the first line specially }
    
if ALine = 0 then
    begin
      
bufRead := fs.Read(buf, SizeOf(buf));
      if bufRead = 0 then
        raise 
Exception.Create('Line not found');
      bufPos := buf;
    end else
      while 
ALine > 0 do
      begin
        
{ read in a buffer }
        
bufRead := fs.Read(buf, SizeOf(buf));
        if bufRead = 0 then
          raise 
Exception.Create('Line not found');
        bufPos := buf;
        while (bufRead > 0) and (ALine > 0) do
        begin
          if 
bufPos^ = #10 then
            
Dec(ALine);
          Inc(bufPos);
          Dec(bufRead);
        end;
      end;
    { Found the beginning of the line at bufPos... scan for end.
      2 cases:
        1) we'll find it before the end of this buffer
        2) it'll go beyond this buffer and into n more buffers }
    
lineStart := bufPos;
    while (bufRead > 0) and (bufPos^ <> #10) do
    begin
      
Inc(bufPos);
      Dec(bufRead);
    end;
    { if bufRead is positive, we'll have found the end and we can leave. }
    
SetString(Result, lineStart, bufPos - lineStart);
    { determine if there are more buffers to process }
    
while bufRead = 0 do
    begin
      
bufRead := fs.Read(buf, SizeOf(buf));
      lineStart := buf;
      bufPos := buf;
      while (bufRead > 0) and (bufPos^ <> #10) do
      begin
        
Inc(bufPos);
        Dec(bufRead);
      end;
      SetString(tmp, lineStart, bufPos - lineStart);
      Result := Result + tmp;
    end;
  finally
    
fs.Free;
  end;
end;

function GrabLine2(const s: string; ALine: Integer): string;
var
  
sl: TStringList;
begin
  
sl := TStringList.Create;
  try
    
sl.LoadFromFile(s);
    Result := sl[ALine - 1]; // index off by one
  
finally
    
sl.Free;
  end;
end;

begin
  
Writeln(GrabLine(ParamStr(1), StrToInt(ParamStr(2))));
  Writeln(GrabLine2(ParamStr(1), StrToInt(ParamStr(2))));
end.

{

Call it like 'getline testfile.txt 20000', depending on what you call the
.pas (or .dpr) file. For large (i.e. tens of megabytes) files, the (rather
complex) scanning function easily beats the memory expensive StringList
version.

-- Barry
}

 

printed from
www.swissdelphicenter.ch
developers knowledge base