Sunday, July 23, 2017

#SWMM5 - Delphi Pascal unit that imports a SWMM project's data from a formatted text file

unit Uimport;

{-------------------------------------------------------------------}
{                    Unit:    Uimport.pas                           }
{                    Project: EPA SWMM                              }
{                    Version: 5.1                                   }
{                    Date:    12/02/13    (5.1.001)                 }
{                             04/04/14    (5.1.003)                 }
{                             04/14/14    (5.1.004)                 }
{                             09/15/14    (5.1.007)                 }
{                             03/19/15    (5.1.008)                 }
{                             08/05/15    (5.1.010)                 }
{                             08/01/16    (5.1.011)                 }
{                    Author:  L. Rossman                            }
{                                                                   }
{   Delphi Pascal unit that imports a SWMM project's data from a    }
{   a formatted text file.                                          }
{                                                                   }
{   5.1.011 - Support for reading [EVENTS] section added.           }
{-------------------------------------------------------------------}

interface

uses
  Classes, Forms, Controls, Dialogs, SysUtils, Windows, Math, StrUtils,
  Uutils, Uglobals;

const
  ITEMS_ERR    = 1;
  KEYWORD_ERR  = 2;
  SUBCATCH_ERR = 3;
  NODE_ERR     = 4;
  LINK_ERR     = 5;
  LANDUSE_ERR  = 6;
  POLLUT_ERR   = 7;
  NUMBER_ERR   = 8;
  XSECT_ERR    = 9;
  TRANSECT_ERR = 10;
  TIMESTEP_ERR = 11;
  DATE_ERR     = 12;
  LID_ERR      = 13;
  MAX_ERRORS   = 50;

type
  TFileType = (ftInput, ftImport);

// These routines can be called from other units
function  ErrMsg(const ErrCode: Integer; const Name: String): Integer;
function  OpenProject(const Fname: String): TInputFileType;
function  ReadInpFile(const Fname: String):Boolean;
procedure SetDefaultDates;

implementation

uses
  Fmain, Fmap, Fstatus, Dxsect, Uexport, Uinifile, Uproject, Umap,
  Ucoords, Uvertex, Uupdate, Dreporting, Ulid;

const
  MSG_READING_PROJECT_DATA = 'Reading project data... ';
  TXT_ERROR = 'Error ';
  TXT_AT_LINE = ' at line ';
  TXT_MORE_ERRORS = ' more errors found in file.';
  TXT_ERROR_REPORT = 'Error Report for File ';

  SectionWords : array[0..54] of PChar =                                       //(5.1.011)
    ('[TITLE',                    //0
     '[OPTION',                   //1
     '[RAINGAGE',                 //2
     '[HYDROGRAPH',               //3
     '[EVAPORATION',              //4
     '[SUBCATCHMENT',             //5
     '[SUBAREA',                  //6
     '[INFILTRATION',             //7
     '[AQUIFER',                  //8
     '[GROUNDWATER',              //9
     '[JUNCTION',                 //10
     '[OUTFALL',                  //11
     '[STORAGE',                  //12
     '[DIVIDER',                  //13
     '[CONDUIT',                  //14
     '[PUMP',                     //15
     '[ORIFICE',                  //16
     '[WEIR',                     //17
     '[OUTLET',                   //18
     '[XSECTION',                 //19
     '[TRANSECT',                 //20
     '[LOSS',                     //21
     '[CONTROL',                  //22
     '[POLLUTANT',                //23
     '[LANDUSE',                  //24
     '[BUILDUP',                  //25
     '[WASHOFF',                  //26
     '[COVERAGE',                 //27
     '[INFLOW',                   //28
     '[DWF',                      //29
     '[PATTERN',                  //30
     '[RDII',                     //31
     '[LOAD',                     //32
     '[CURVE',                    //33
     '[TIMESERIES',               //34
     '[REPORT',                   //35
     '[FILE',                     //36
     '[MAP',                      //37
     '[COORDINATES',              //38
     '[VERTICES',                 //39
     '[POLYGONS',                 //40
     '[SYMBOLS',                  //41
     '[LABELS',                   //42
     '[BACKDROP',                 //43
     '[PROFILE',                  //44
     '[TABLE',                    //45
     '[TEMPERATURE',              //46
     '[SNOWPACK',                 //47
     '[TREATMENT',                //48
     '[TAG',                      //49
     '[LID_CONTROL',              //50
     '[LID_USAGE',                //51
     '[GWF',                      //52                                         //(5.1.007)
     '[ADJUSTMENTS',              //53                                         //(5.1.007)
     '[EVENT');                   //54                                         //(5.1.011)

var
  FileType     : TFileType;
  InpFile      : String;
  ErrList      : TStringlist;
  SubcatchList : TStringlist;
  NodeList     : TStringlist;
  LinkList     : TStringlist;
  TokList      : TStringlist;
  Ntoks        : Integer;
  Section      : Integer;
  LineCount    : LongInt;
  ErrCount     : LongInt;
  Line         : String;
  Comment      : String;
  TsectComment : String;
  PrevID       : String;
  PrevIndex    : Integer;
  CurveType    : Integer;
  MapExtentSet : Boolean;
  ManningsN    : array[1..3] of String;

  // These are deprecated attributes of a backdrop image
  BackdropX    : Extended;
  BackdropY    : Extended;
  BackdropW    : Extended;
  BackdropH    : Extended;


function ErrMsg(const ErrCode: Integer; const Name: String): Integer;
//-----------------------------------------------------------------------------
//  Adds an error message for a specific error code to the list
//  of errors encountered when reading an input file.
//-----------------------------------------------------------------------------
var
  S: String;
begin
  if ErrCount <= MAX_ERRORS then
  begin
    case ErrCode of
      ITEMS_ERR:    S := 'Too few items ';
      KEYWORD_ERR:  S := 'Unrecognized keyword (' + Name + ') ';
      SUBCATCH_ERR: S := 'Undefined Subcatchment (' + Name + ') referenced ';
      NODE_ERR:     S := 'Undefined Node (' + Name + ') referenced ';
      LINK_ERR:     S := 'Undefined Link (' + Name + ') referenced ';
      LANDUSE_ERR:  S := 'Undefined Land Use (' + Name + ') referenced ';
      POLLUT_ERR:   S := 'Undefined Pollutant (' + Name + ') referenced ';
      NUMBER_ERR:   S := 'Illegal numeric value (' + Name + ') ';
      XSECT_ERR:    S := 'Illegal cross section for Link ' + Name + ' ';
      TRANSECT_ERR: S := 'No Transect defined for these data ';
      TIMESTEP_ERR: S := 'Illegal time step value ';
      DATE_ERR:     S := 'Illegal date/time value ';
      LID_ERR:      S := 'Undefined LID process (' + Name + ') referenced ';
      else          S := 'Unknown error ';
    end;
    S := S + 'at line ' + IntToStr(LineCount) + ':';
    ErrList.Add(S);
    if Section >= 0 then ErrList.Add(SectionWords[Section] + ']');
    ErrList.Add(Line);
    ErrList.Add('');
  end;
  Result := ErrCode;
end;


function FindSubcatch(const ID: String): TSubcatch;
//-----------------------------------------------------------------------------
//  Finds a Subcatchment object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Atype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if SubcatchList.Find(ID, Index)
    then Result := TSubcatch(SubcatchList.Objects[Index]);
  end
  else
  begin
    if (Project.FindSubcatch(ID, Atype, Index))
    then Result := Project.GetSubcatch(Atype, Index);
  end;
end;


function FindNode(const ID: String): TNode;
//-----------------------------------------------------------------------------
// Finds a Node object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Ntype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if NodeList.Find(ID, Index)
    then Result := TNode(NodeList.Objects[Index]);
  end
  else
  begin
    if (Project.FindNode(ID, Ntype, Index))
    then Result := Project.GetNode(Ntype, Index);
  end;
end;


function FindLink(const ID: String): TLink;
//-----------------------------------------------------------------------------
// Finds a Link object given its ID name.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Ltype : Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if LinkList.Find(ID, Index)
    then Result := TLink(LinkList.Objects[Index]);
  end
  else
  begin
    if (Project.FindLink(ID, Ltype, Index))
    then Result := Project.GetLink(Ltype, Index);
  end;
end;


function ReadTitleData(Line: String): Integer;
//-----------------------------------------------------------------------------
// Adds Line of text to a project's title and notes.
//-----------------------------------------------------------------------------
begin
  if Project.Lists[NOTES].Count = 0 then Project.Title := Line;
  Project.Lists[NOTES].Add(Line);
  Project.HasItems[NOTES] := True;
  Result := 0;
end;


procedure ReadOldRaingageData(I: Integer; aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of parsed rain gage data using older format.
//-----------------------------------------------------------------------------
begin
  if I = 0 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
    if Ntoks > 2 then aGage.Data[GAGE_SERIES_NAME] := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_DATA_FORMAT] := TokList[3];
    if Ntoks > 4 then aGage.Data[GAGE_DATA_FREQ]   := TokList[4];
  end;
  if I = 1 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
    if Ntoks > 2 then aGage.Data[GAGE_FILE_NAME]   := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_STATION_NUM] := TokList[3];
  end;
end;


procedure ReadNewRaingageData(aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of rain gage data using newer format.
//-----------------------------------------------------------------------------
var
  I: Integer;
  Fname: String;
begin
  if Ntoks > 1 then aGage.Data[GAGE_DATA_FORMAT] := TokList[1];
  if Ntoks > 2 then aGage.Data[GAGE_DATA_FREQ]   := TokList[2];
  if Ntoks > 3 then aGage.Data[GAGE_SNOW_CATCH]  := TokList[3];
  if Ntoks > 4 then
  begin
    I := Uutils.FindKeyWord(TokList[4], RaingageOptions, 4);
    if I = 0 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
      if Ntoks > 5 then aGage.Data[GAGE_SERIES_NAME] := TokList[5];
    end;
    if I = 1 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
      if Ntoks > 5 then
      begin
        Fname := TokList[5];
        if ExtractFilePath(Fname) = ''
        then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
        aGage.Data[GAGE_FILE_NAME] := Fname;
      end;
      if Ntoks > 6 then aGage.Data[GAGE_STATION_NUM] := TokList[6];
      if Ntoks > 7 then aGage.Data[GAGE_RAIN_UNITS]  := TokList[7];
    end;
  end;
end;


function ReadRaingageData: Integer;
//-----------------------------------------------------------------------------
//  Parses rain gage data from the input line.
//-----------------------------------------------------------------------------
var
  aGage   : TRaingage;
  ID      : String;
  I       : Integer;
begin
  if Ntoks < 2 then
  begin
    Result := ErrMsg(ITEMS_ERR, '');
    Exit;
  end;
  ID := TokList[0];
  aGage := TRaingage.Create;
  Uutils.CopyStringArray(Project.DefProp[RAINGAGE].Data, aGage.Data);
  aGage.X := MISSING;
  aGage.Y := MISSING;
  aGage.Data[COMMENT_INDEX ] := Comment;
  Project.Lists[RAINGAGE].AddObject(ID, aGage);
  Project.HasItems[RAINGAGE] := True;
  I := Uutils.FindKeyWord(TokList[1], RaingageOptions, 4);
  if I >= 0
    then ReadOldRaingageData(I, aGage)
    else ReadNewRaingageData(aGage);
  Result := 0;
end;


function ReadOldHydrographFormat(const I: Integer; UH: THydrograph): Integer;
//-----------------------------------------------------------------------------
//  Reads older format of unit hydrograph parameters from a line of input.
//-----------------------------------------------------------------------------
var
  J, K, N: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin
    N := 2;
    for K := 1 to 3 do
    begin
      for J := 1 to 3 do
      begin
        UH.Params[I,J,K] := TokList[N];
        Inc(N);
      end;
    end;
    for J := 1 to 3 do
    begin
      UH.InitAbs[I,J,1] := '';
      if Ntoks > N then UH.InitAbs[I,J,1] := TokList[N];
      Inc(N);
      for K := 2 to 3 do UH.InitAbs[I,J,K] := UH.InitAbs[I,J,1];
    end;
  end;
end;


function ReadHydrographData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII unit hydrograph data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J, K: Integer;
  ID: String;
  aUnitHyd: THydrograph;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin

    // Check if hydrograph ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID)
    then Index := PrevIndex
    else Index := Project.Lists[HYDROGRAPH].IndexOf(ID);

    // If starting input for a new hydrograph then create it
    if Index < 0 then
    begin
      aUnitHyd := THydrograph.Create;
      Project.Lists[HYDROGRAPH].AddObject(ID, aUnitHyd);
      Project.HasItems[HYDROGRAPH] := True;
      Index := Project.Lists[HYDROGRAPH].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
    end
    else aUnitHyd := THydrograph(Project.Lists[HYDROGRAPH].Objects[Index]);

    // Parse rain gage name for 2-token line
    if Ntoks = 2 then
    begin
      aUnitHyd.Raingage := TokList[1];
    end

    // Extract remaining hydrograph parameters
    else if Ntoks < 6 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      // Determine month of year
      I := Uutils.FindKeyWord(TokList[1], MonthLabels, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1]);

      // Determine if response type present - if not, process old format
      K := Uutils.FindKeyWord(TokList[2], ResponseTypes, 3) + 1;
      if K < 1 then Result := ReadOldHydrographFormat(I, aUnitHyd)
      else
      begin
        // Extract R-T-K values
        for J := 3 to 5 do
        begin
          aUnitHyd.Params[I,J-2,K] := TokList[J];
        end;
        // Extract IA parameters
        for J := 6 to 8 do
        begin
          if J >= Ntoks then break
          else aUnitHyd.InitAbs[I,J-5,K] := TokList[J];
        end;
      end;
    end;
  end;
end;


function ReadTemperatureData: Integer;
//-----------------------------------------------------------------------------
//  Reads description of air temperature data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else with Project.Climatology do
  begin
    I := Uutils.FindKeyWord(TokList[0], TempKeywords, 10);
    if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[0])
    else case I of
    0:  begin                                              // Time series
          TempDataSource := 1;
          TempTseries := TokList[1];
        end;
    1:  begin                                              // File
          TempDataSource := 2;
          TempFile := TokList[1];
          if ExtractFilePath(TempFile) = ''  then
          TempFile := ExtractFilePath(Uglobals.InputFileName) + TempFile;
          if Ntoks >= 3 then TempStartDate := TokList[2];
        end;
    2:  begin                                              // Wind speed
          if SameText(TokList[1], 'MONTHLY') then
          begin
            if Ntoks < 14 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              WindType := MONTHLY_WINDSPEED;
              for I := 1 to 12 do WindSpeed[I] := TokList[I+1];
            end;
          end
          else if SameText(TokList[1], 'FILE') then WindType := FILE_WINDSPEED
          else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        end;
    3:  begin
          if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
          else for I := 1 to 6 do SnowMelt[I] := TokList[I];
        end;
    4:  begin
          if Ntoks < 12 then Result := ErrMsg(ITEMS_ERR, '')
          else
          begin
            if  SameText(TokList[1], 'IMPERVIOUS') then
              for I := 1 to 10 do ADCurve[1][I] := TokList[I+1]
            else if SameText(TokList[1], 'PERVIOUS') then
              for I := 1 to 10 do ADCurve[2][I] := TokList[I+1]
            else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
          end;
        end;
    end;
  end;
end;


function ReadEvaporationRates(Etype: Integer): Integer;
var
  J: Integer;
begin
  if (Etype <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin
    EvapType := Etype;

    case EvapType of

      CONSTANT_EVAP:
        for J := 0 to 11 do EvapData[J] := TokList[1];

      TSERIES_EVAP:
        EvapTseries := TokList[1];

      FILE_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          PanData[J-1] := TokList[J];
        end;

      MONTHLY_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          EvapData[J-1] := TokList[J];
        end;
    end;

    Result := 0;
  end;

end;


function ReadEvaporationData: Integer;
//-----------------------------------------------------------------------------
//  Reads evaporation data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  I := Uutils.FindKeyWord(TokList[0], EvapOptions, 4);
  if I >= 0 then Result := ReadEvaporationRates(I)

  else if (I <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin

    if SameText(TokList[0], 'RECOVERY')
    then RecoveryPat := TokList[1]

    else if SameText(TokList[0], 'DRY_ONLY') then
    begin
      if SameText(TokList[1], 'NO') then EvapDryOnly := False
      else if SameText(TokList[1], 'YES') then EvapDryOnly := True
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end

    else Result := ErrMsg(KEYWORD_ERR, TokList[0]);

  end;
end;


function ReadSubcatchmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := TSubcatch.Create;
    SubcatchList.AddObject(ID, S);
    S.X := MISSING;
    S.Y := MISSING;
    S.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[SUBCATCH].Data, S.Data);
    S.Data[SUBCATCH_RAINGAGE_INDEX] := TokList[1];
    S.Data[SUBCATCH_OUTLET_INDEX]   := TokList[2];
    S.Data[SUBCATCH_AREA_INDEX]     := TokList[3];
    S.Data[SUBCATCH_IMPERV_INDEX]   := TokList[4];
    S.Data[SUBCATCH_WIDTH_INDEX]    := TokList[5];
    S.Data[SUBCATCH_SLOPE_INDEX]    := TokList[6];
    S.Data[SUBCATCH_CURBLENGTH_INDEX] := TokList[7];
    if Ntoks >= 9
      then S.Data[SUBCATCH_SNOWPACK_INDEX] := TokList[8];
    S.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[SUBCATCH].AddObject(ID, S);
    Project.HasItems[SUBCATCH] := True;
    Result := 0;
  end;
end;


function ReadSubareaData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment sub-area data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := FindSubcatch(ID);
    if S = nil
    then Result := ErrMsg(SUBCATCH_ERR, ID)
    else begin
      S.Data[SUBCATCH_IMPERV_N_INDEX]  := TokList[1];
      S.Data[SUBCATCH_PERV_N_INDEX]    := TokList[2];
      S.Data[SUBCATCH_IMPERV_DS_INDEX] := TokList[3];
      S.Data[SUBCATCH_PERV_DS_INDEX]   := TokList[4];
      S.Data[SUBCATCH_PCTZERO_INDEX]   := TokList[5];
      S.Data[SUBCATCH_ROUTE_TO_INDEX]  := TokList[6];
      if Ntoks >= 8
      then S.Data[SUBCATCH_PCT_ROUTED_INDEX] := TokList[7];
      Result := 0;
    end;
  end;
end;


function ReadInfiltrationData: Integer;
//-----------------------------------------------------------------------------
// Reads subcatchment infiltration data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : TSubcatch;
  ID    : String;
  J     : Integer;
  Jmax  : Integer;
begin
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else begin
    Jmax := Ntoks-1;
    if Jmax > MAXINFILPROPS then Jmax := MAXINFILPROPS;
    for J := 1 to Jmax do S.InfilData[J-1] := TokList[J];
    Result := 0;
  end;
end;


function ReadAquiferData: Integer;
//-----------------------------------------------------------------------------
//  Reads aquifer data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  A  : TAquifer;
  I  : Integer;
begin
  if nToks < MAXAQUIFERPROPS then Result := ErrMsg(ITEMS_ERR, '')              //(5.1.003)
  else begin
    ID := TokList[0];
    A := TAquifer.Create;
    //Uutils.CopyStringArray(Project.DefProp[AQUIFER].Data, A.Data);           //(5.1.004)
    Project.Lists[AQUIFER].AddObject(ID, A);
    Project.HasItems[AQUIFER] := True;
    for I := 0 to MAXAQUIFERPROPS-1 do A.Data[I] := TokList[I+1];              //(5.1.003)
    if nToks >= MAXAQUIFERPROPS + 2                                            //(5.1.004)
    then A.Data[MAXAQUIFERPROPS] := TokList[MAXAQUIFERPROPS+1]                 //(5.1.003)
    else A.Data[MAXAQUIFERPROPS] := '';                                        //(5.1.003)
    Result := 0;
  end;
end;


function ReadGroundwaterData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment groundwater data from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  ID: String;
  P:  String;
  J:  Integer;
  K:  Integer;

begin
  // Get subcatchment name
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else
  begin

    // Line contains GW flow parameters
    if Ntoks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else begin
      S.Groundwater.Clear;

      // Read required parameters
      for J := 1 to 9 do S.Groundwater.Add(TokList[J]);

      // Read optional parameters
      for K := 10 to 13 do
      begin
        if Ntoks > K then
        begin
          P := TokList[K];
          if P = '*' then P := '';
          S.Groundwater.Add(P);
        end
        else S.Groundwater.Add('');
      end;
      S.Data[SUBCATCH_GWATER_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;

////  This function was re-written for release 5.1.007.  ////                  //(5.1.007)
function ReadGroundwaterFlowEqn(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads GW flow math expression from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  N:  Integer;
begin
  // Check for enough tokens in line
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
  // Get subcatchment object referred to by name
  else begin
    Result := 0;
    S := FindSubcatch(TokList[0]);
    if S = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
    else begin
      // Find position in Line where second token ends
      N := Pos(TokList[1], Line) + Length(TokList[1]);
      // Save remainder of line to correct type of GW flow equation
      if SameText(TokList[1], 'LATERAL') then
        S.GwLatFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else if SameText(TokList[1], 'DEEP') then
        S.GwDeepFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;
  end;
end;

function ReadSnowpackData: Integer;
//-----------------------------------------------------------------------------
//  Reads snowpack data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  P  : TSnowpack;
  I  : Integer;
  J  : Integer;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[1], SnowpackOptions, 7);
    if I < 0
    then Result := ErrMsg(KEYWORD_ERR, TokList[1])
    else begin

      // Check if snow pack ID is same as for previous line
      ID := TokList[0];
      if (ID = PrevID)
      then Index := PrevIndex
      else Index := Project.Lists[SNOWPACK].IndexOf(ID);

      // If starting input for a new snow pack then create it
      if Index < 0 then
      begin
        P := TSnowpack.Create;
        Project.Lists[SNOWPACK].AddObject(ID, P);
        Project.HasItems[SNOWPACK] := True;
        Index := Project.Lists[SNOWPACK].Count - 1;
        PrevID := ID;
        PrevIndex := Index;
      end
      else P := TSnowpack(Project.Lists[SNOWPACK].Objects[Index]);

      // Parse line depending on data type
      case I of

      // Plowable area
      0:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              for J := 1 to 6 do P.Data[1][J] := TokList[J+1];
              P.FracPlowable := TokList[8];
            end;
          end;

      // Impervious or Pervious area
      1,
      2:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else for J := 1 to 7 do P.Data[I+1][J] := TokList[J+1];
          end;

      // Plowing parameters
      3:  begin
            for J := 1 to 6 do P.Plowing[J] := TokList[J+1];
            if Ntoks >= 9 then P.Plowing[7] := TokList[8];
          end;
      end;
    end;
  end;
end;


function ReadJunctionData: Integer;
//-----------------------------------------------------------------------------
//  Reads junction data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := JUNCTION;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[JUNCTION].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    if Ntoks > 2 then  aNode.Data[JUNCTION_MAX_DEPTH_INDEX]       := TokList[2];
    if Ntoks > 3 then  aNode.Data[JUNCTION_INIT_DEPTH_INDEX]      := TokList[3];
    if Ntoks > 4 then  aNode.Data[JUNCTION_SURCHARGE_DEPTH_INDEX] := TokList[4];
    if Ntoks > 5 then  aNode.Data[JUNCTION_PONDED_AREA_INDEX]     := TokList[5];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[JUNCTION].AddObject(ID, aNode);
    Project.HasItems[JUNCTION] := True;
    Result := 0;
  end;
end;


function ReadOutfallData: Integer;
//-----------------------------------------------------------------------------
//  Reads outfall data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  I    : Integer;
  N    : Integer;                                                              //(5.1.008)
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    N := 4;                                                                    //(5.1.008)
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := OUTFALL;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[OUTFALL].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    I := Uutils.FindKeyWord(TokList[2], OutfallOptions, 4);
    if I < 0 then I := FREE_OUTFALL;
    if (I > NORMAL_OUTFALL) and (Ntoks >= 4) then
    begin
      case I of
      FIXED_OUTFALL:      aNode.Data[OUTFALL_FIXED_STAGE_INDEX] := TokList[3];
      TIDAL_OUTFALL:      aNode.Data[OUTFALL_TIDE_TABLE_INDEX] := TokList[3];
      TIMESERIES_OUTFALL: aNode.Data[OUTFALL_TIME_SERIES_INDEX] := TokList[3];
      end;
      N := 5;
      if Ntoks >= 5 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[4];    // (5.1.008)
    end
    else if Ntoks >= 4 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[3]; //(5.1.008)
    if Ntoks > N then aNode.Data[OUTFALL_ROUTETO_INDEX] := TokList[N];         //(5.1.008)
    aNode.Data[OUTFALL_TYPE_INDEX] := OutfallOptions[I];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[OUTFALL].AddObject(ID, aNode);
    Project.HasItems[OUTFALL] := True;
    Result := 0;
  end;
end;

////  The following function was modified for release 5.1.007.  ////           //(5.1.007)

function ReadStorageData: Integer;
//-----------------------------------------------------------------------------
//  Reads storage unit data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  N    : Integer;
  X    : Single;
begin
  Result := 0;
  N := 6;
  if Ntoks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := STORAGE;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[STORAGE].Data, aNode.Data);
    Project.Lists[STORAGE].AddObject(ID, aNode);
    aNode.Data[NODE_INVERT_INDEX]        := TokList[1];
    aNode.Data[STORAGE_MAX_DEPTH_INDEX]  := TokList[2];
    aNode.Data[STORAGE_INIT_DEPTH_INDEX] := TokList[3];
    aNode.Data[STORAGE_GEOMETRY_INDEX]   := TokList[4];
    if CompareText(TokList[4], 'TABULAR') = 0 then
    begin
      aNode.Data[STORAGE_ATABLE_INDEX] := TokList[5];
    end
    else begin
      if Ntoks < 7
      then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        aNode.Data[STORAGE_ACOEFF_INDEX] := TokList[5];
        aNode.Data[STORAGE_AEXPON_INDEX] := TokList[6];
        if Ntoks >= 8 then aNode.Data[STORAGE_ACONST_INDEX] := TokList[7];
        N := 8;
      end;
    end;

    // Optional items
    if (Result = 0) and (Ntoks > N) then
    begin
      // Ponded area
      aNode.Data[STORAGE_PONDED_AREA_INDEX] := TokList[N];
      // Evaporation factor
      if Ntoks > N+1 then aNode.Data[STORAGE_EVAP_FACTOR_INDEX] := TokList[N+1];
      // Constant seepage rate
      if Ntoks = N+3 then
      begin
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+2];
      end
      // Green-Ampt seepage parameters
      else if Ntoks = N+5 then
      begin
        aNode.InfilData[STORAGE_SUCTION_INDEX] := TokList[N+2];
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+3];
        aNode.InfilData[STORAGE_IMDMAX_INDEX] := TokList[N+4];
      end;
    end;
    Uutils.GetSingle(aNode.InfilData[STORAGE_KSAT_INDEX ], X);
    if (X > 0) then aNode.Data[STORAGE_SEEPAGE_INDEX] := 'YES'
    else aNode.Data[STORAGE_SEEPAGE_INDEX] := 'NO';
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.HasItems[STORAGE] := True;
  end;
end;


function ReadDividerData: Integer;
//-----------------------------------------------------------------------------
//  Reads flow divider data from a line of input.
//  (Corrected on 6/14/05)
//-----------------------------------------------------------------------------
var
  N, J : Integer;
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Result := 0;
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := DIVIDER;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[DIVIDER].Data, aNode.Data);
    Project.Lists[DIVIDER].AddObject(ID, aNode);
    Project.HasItems[DIVIDER] := True;
    aNode.Data[COMMENT_INDEX ] := Comment;
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    aNode.Data[DIVIDER_LINK_INDEX] := TokList[2];
    aNode.Data[DIVIDER_TYPE_INDEX] := TokList[3];
    N := 5;
    if SameText(TokList[3], 'OVERFLOW') then
    begin
      N := 4;
    end
    else if SameText(TokList[3], 'CUTOFF') then
    begin
      aNode.Data[DIVIDER_CUTOFF_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'TABULAR') then
    begin
      aNode.Data[DIVIDER_TABLE_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'WEIR') and (Ntoks >= 7) then
    begin
      aNode.Data[DIVIDER_QMIN_INDEX]   := TokList[4];
      aNode.Data[DIVIDER_DMAX_INDEX]   := TokList[5];
      aNode.Data[DIVIDER_QCOEFF_INDEX] := TokList[6];
      N := 7;
    end
    else Result := ErrMsg(KEYWORD_ERR, TokList[3]);
    if (Result = 0) and (Ntoks > N) then
    begin
      for J := N to Ntoks-1 do
        aNode.Data[DIVIDER_MAX_DEPTH_INDEX + J - N] := TokList[J];
    end;
  end;
end;


function ReadConduitData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := CONDUIT;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[CONDUIT].Data, aLink.Data);
      Project.Lists[CONDUIT].AddObject(ID, aLink);
      Project.HasItems[CONDUIT] := True;
      aLink.Data[CONDUIT_LENGTH_INDEX]     := TokList[3];
      aLink.Data[CONDUIT_ROUGHNESS_INDEX]  := TokList[4];
      aLink.Data[CONDUIT_INLET_HT_INDEX]   := TokList[5];
      aLink.Data[CONDUIT_OUTLET_HT_INDEX]  := TokList[6];
      if Ntoks > 7 then aLink.Data[CONDUIT_INIT_FLOW_INDEX] := TokList[7];
      if Ntoks > 8 then aLink.Data[CONDUIT_MAX_FLOW_INDEX] := TokList[8];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadPumpData: Integer;
//-----------------------------------------------------------------------------
//  Reads pump data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := PUMP;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[PUMP].Data, aLink.Data);
      Project.Lists[PUMP].AddObject(ID, aLink);
      Project.HasItems[PUMP] := True;

      // Skip over PumpType if line has old format
      if Uutils.FindKeyWord(TokList[3], PumpTypes, 5) >= 0 then N := 4
      else N := 3;
      if Ntoks <= N then Result := ErrMsg(ITEMS_ERR, '')
      else
      begin
        aLink.Data[PUMP_CURVE_INDEX] := TokList[N];
        if nToks > N+1 then aLink.Data[PUMP_STATUS_INDEX] := TokList[N+1];
        if nToks > N+2 then aLink.Data[PUMP_STARTUP_INDEX] := TokList[N+2];
        if nToks > N+3 then aLink.Data[PUMP_SHUTOFF_INDEX] := TokList[N+3];
        aLink.Data[COMMENT_INDEX ] := Comment;
        Result := 0;
      end;
    end;
  end;
end;


function ReadOrificeData: Integer;
//-----------------------------------------------------------------------------
//  Reads orifice data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := ORIFICE;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[ORIFICE].Data, aLink.Data);
      Project.Lists[ORIFICE].AddObject(ID, aLink);
      Project.HasItems[ORIFICE] := True;
      aLink.Data[ORIFICE_TYPE_INDEX]      := TokList[3];
      aLink.Data[ORIFICE_BOTTOM_HT_INDEX] := TokList[4];
      aLink.Data[ORIFICE_COEFF_INDEX]     := TokList[5];
      if nToks >= 7
      then aLink.Data[ORIFICE_FLAPGATE_INDEX] := TokList[6];
      if nToks >= 8
      then aLink.Data[ORIFICE_ORATE_INDEX] := TokList[7];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadWeirData: Integer;
//-----------------------------------------------------------------------------
//  Reads weir data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := WEIR;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[WEIR].Data, aLink.Data);
      Project.Lists[WEIR].AddObject(ID, aLink);
      Project.HasItems[WEIR] := True;
      aLink.Data[WEIR_TYPE_INDEX]  := TokList[3];
      aLink.Data[WEIR_CREST_INDEX] := TokList[4];
      aLink.Data[WEIR_COEFF_INDEX] := TokList[5];

////  Following section modified for release 5.1.007.  ////                    //(5.1.007)
      if (nToks >= 7) and not SameText(TokList[6], '*') then
        aLink.Data[WEIR_FLAPGATE_INDEX] := TokList[6];
      if (nToks >= 8) and not SameText(TokList[7], '*') then
        aLink.Data[WEIR_CONTRACT_INDEX] := TokList[7];
      if (nToks >= 9) and not SameText(TokList[8], '*') then
        aLink.Data[WEIR_END_COEFF_INDEX] := TokList[8];
      if (nToks >= 10) and not SameText(TokList[9], '*') then
        aLink.Data[WEIR_SURCHARGE_INDEX] := TokList[9];

////  Following section added for release 5.1.010.                             //(5.1.010)
      if (nToks >= 11) and not SameText(TokList[10], '*') then
        aLink.Data[WEIR_ROAD_WIDTH_INDEX] := TokList[10];
      if (nToks >= 12) and not SameText(TokList[11], '*') then
        aLink.Data[WEIR_ROAD_SURF_INDEX] := TokList[11];
////

      aLink.Data[COMMENT_INDEX] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadOutletData: Integer;
//-----------------------------------------------------------------------------
//  Reads outlet data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := OUTLET;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[OUTLET].Data, aLink.Data);
      Project.Lists[OUTLET].AddObject(ID, aLink);
      Project.HasItems[OUTLET] := True;
      aLink.Data[OUTLET_CREST_INDEX] := TokList[3];

      //... added for backwards compatibility
      if SameText(TokList[4], 'TABULAR') then TokList[4] := 'TABULAR/DEPTH';
      if SameText(TokList[4], 'FUNCTIONAL') then TokList[4] := 'FUNCTIONAL/DEPTH';
      aLink.Data[OUTLET_TYPE_INDEX]  := TokList[4];

      if AnsiContainsText(TokList[4], 'TABULAR') then
      begin
        aLink.Data[OUTLET_QTABLE_INDEX] := TokList[5];
        N := 6;
      end
      else begin
        if Ntoks < 7 then
        begin
          Result := ErrMsg(ITEMS_ERR, '');
          Exit;
        end
        else
        begin
          aLink.Data[OUTLET_QCOEFF_INDEX] := TokList[5];
          aLink.Data[OUTLET_QEXPON_INDEX] := TokList[6];
          N := 7;
        end;
      end;
      if Ntoks > N then aLink.Data[OUTLET_FLAPGATE_INDEX] := TokList[N];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function GetXsectShape(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Finds the code number corresponding to cross section shape S.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  for I := 0 to High(Dxsect.XsectShapes) do
  begin
    if CompareText(S, Dxsect.XsectShapes[I].Text[1]) = 0 then
    begin
      Result := I;
      Exit;
    end;
  end;
  Result := -1;
end;

////  New procedure added to release 5.1.008.  ////                            //(5.1.008)
procedure CheckForStdSize;
//-----------------------------------------------------------------------------
//  Converts from old format for standard size ellipse and arch pipes
//  to new format.
//-----------------------------------------------------------------------------
var
  J: Integer;
  X: Extended;
begin
// Old format had size code as 3rd token and 0 for 4th token
  J := StrToIntDef(TokList[2], 0);
  Uutils.GetExtended(TokList[3], X);
  if (J > 0) and (X = 0) then
  begin
  // New format has 5th token as size code
    TokList[4] := TokList[2];
    TokList[2] := '0';
    TokList[3] := '0';
  end;
end;

function ReadXsectionData: Integer;
//-----------------------------------------------------------------------------
//  Reads cross section data froma line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  ID    : String;
  aLink : TLink;
begin
  if nToks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil)
    then Result := ErrMsg(LINK_ERR, TokList[0])
    else begin
      I := GetXsectShape(TokList[1]);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else case aLink.Ltype of

      CONDUIT:
        begin
          aLink.Data[CONDUIT_SHAPE_INDEX] := TokList[1];
          if I = Dxsect.IRREG_SHAPE_INDEX then
          begin
            aLink.Data[CONDUIT_TSECT_INDEX] := TokList[2];
            aLink.Data[CONDUIT_GEOM1_INDEX] := '';                             //(5.1.008)
            Result := 0;
          end

          else begin
            if nToks < 6 then Result := ErrMsg(ITEMS_ERR, '')
            else begin
{
////  Added to release 5.1.008.  ////                                          //(5.1.008)
              if I in [Dxsect.HORIZ_ELLIPSE_SHAPE_INDEX,
                       Dxsect.VERT_ELLIPSE_SHAPE_INDEX,
                       Dxsect.ARCH_SHAPE_INDEX]
              then CheckForStdSize;
}
              aLink.Data[CONDUIT_GEOM1_INDEX] := TokList[2];
              aLink.Data[CONDUIT_GEOM2_INDEX] := TokList[3];
              aLink.Data[CONDUIT_GEOM3_INDEX] := TokList[4];
              aLink.Data[CONDUIT_GEOM4_INDEX] := TokList[5];
              if Ntoks > 6 then aLink.Data[CONDUIT_BARRELS_INDEX] := TokList[6];
              if Ntoks > 7 then aLink.Data[CONDUIT_CULVERT_INDEX] := TokList[7];
              if I = Dxsect.CUSTOM_SHAPE_INDEX then
                aLink.Data[CONDUIT_TSECT_INDEX] := TokList[3];
              Result := 0;
            end;
          end;
        end;

      ORIFICE:
        begin
          if not I in [0, 1] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[ORIFICE_SHAPE_INDEX]  := TokList[1];
            aLink.Data[ORIFICE_HEIGHT_INDEX] := TokList[2];
            aLink.Data[ORIFICE_WIDTH_INDEX]  := TokList[3];
            Result := 0;
          end;
        end;

      WEIR:
        begin
          if not I in [2, 3, 4] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[WEIR_SHAPE_INDEX]  := TokList[1];
            aLink.Data[WEIR_HEIGHT_INDEX] := TokList[2];
            aLink.Data[WEIR_WIDTH_INDEX]  := TokList[3];
            aLink.Data[WEIR_SLOPE_INDEX]  := TokList[4];
            Result := 0;
          end;
        end;

      else Result := 0;
      end;
    end;
  end;
end;


function ReadTransectData: Integer;
//-----------------------------------------------------------------------------
//  Reads transect data from a line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  K     : Integer;
  N     : Integer;
  ID    : String;
  Tsect : TTransect;
begin
  Result := 0;
  if SameText(TokList[0], 'NC') then
  begin
    if nToks < 4 then Result := ErrMsg(ITEMS_ERR, '')
    else for I := 1 to 3 do ManningsN[I] := TokList[I];
    TsectComment := Comment;
    Exit;
  end;

  if SameText(TokList[0], 'X1') then
  begin
    if nToks < 2 then Exit;
    ID := TokList[1];
    Tsect := TTransect.Create;

    if Length(Comment) > 0 then TsectComment := Comment;
    Tsect.Comment := TsectComment;

    Project.Lists[TRANSECT].AddObject(ID, Tsect);
    Project.HasItems[TRANSECT] := True;
    Tsect.Data[TRANSECT_N_LEFT]    := ManningsN[1];
    Tsect.Data[TRANSECT_N_RIGHT]   := ManningsN[2];
    Tsect.Data[TRANSECT_N_CHANNEL] := ManningsN[3];
    if nToks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      Tsect.Data[TRANSECT_X_LEFT] := TokList[3];
      Tsect.Data[TRANSECT_X_RIGHT] := TokList[4];
      Tsect.Data[TRANSECT_L_FACTOR] := TokList[7];
      Tsect.Data[TRANSECT_X_FACTOR] := TokList[8];
      Tsect.Data[TRANSECT_Y_FACTOR] := TokList[9];
    end;
    Exit;
  end;

  if SameText(TokList[0], 'GR') then
  begin
    N := Project.Lists[TRANSECT].Count;
    if N = 0 then Result := ErrMsg(TRANSECT_ERR, '')
    else begin
      Tsect := TTransect(Project.Lists[TRANSECT].Objects[N-1]);
      K := 1;
      while K + 1 < Ntoks do
      begin
        Tsect.Ydata.Add(TokList[K]);
        Tsect.Xdata.Add(TokList[K+1]);
        K := K + 2;
      end;
    end;
    Exit;
  end;
end;


function ReadLossData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit loss data from a line of input.
//-----------------------------------------------------------------------------
var
  ID    : String;
  aLink : TLink;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil) then Result := ErrMsg(LINK_ERR, ID)
    else if (aLink.Ltype <> CONDUIT) then Result := 0
    else begin
      aLink.Data[CONDUIT_ENTRY_LOSS_INDEX] := TokList[1];
      aLink.Data[CONDUIT_EXIT_LOSS_INDEX] := TokList[2];
      aLink.Data[CONDUIT_AVG_LOSS_INDEX] := TokList[3];
      if nToks >= 5
      then aLink.Data[CONDUIT_CHECK_VALVE_INDEX] := TokList[4];
      if nToks >= 6
      then aLink.Data[CONDUIT_SEEPAGE_INDEX] := TokList[5];
      Result := 0;
    end;
  end;
end;


function ReadPollutantData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant data from a line of input.
//-----------------------------------------------------------------------------
var
  ID      : String;
  aPollut : TPollutant;
  X       : Single;
begin
  if nToks < 5
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Add new pollutant to project
    ID := TokList[0];
    aPollut := TPollutant.Create;
    Uutils.CopyStringArray(Project.DefProp[POLLUTANT].Data, aPollut.Data);
    Project.Lists[POLLUTANT].AddObject(ID, aPollut);
    Project.HasItems[POLLUTANT] := True;

    // Parse units & concens.
    aPollut.Data[POLLUT_UNITS_INDEX] := TokList[1];
    aPollut.Data[POLLUT_RAIN_INDEX]  := TokList[2];
    aPollut.Data[POLLUT_GW_INDEX]    := TokList[3];

    // This is for old format
    if (Ntoks = 5)
    or ( (Ntoks = 7) and Uutils.GetSingle(TokList[6], X) ) then
    begin
      aPollut.Data[POLLUT_DECAY_INDEX] := TokList[4];
      if nToks >= 7 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[5];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[6];
      end;
    end

    // This is for new format
    else
    begin
      aPollut.Data[POLLUT_II_INDEX] := TokList[4];
      if Ntoks >= 6 then aPollut.Data[POLLUT_DECAY_INDEX] := TokList[5];
      if nToks >= 7 then aPollut.Data[POLLUT_SNOW_INDEX]  := TokList[6];
      if Ntoks >= 9 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[7];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[8];
      end;
      if Ntoks >= 10 then aPollut.Data[POLLUT_DWF_INDEX] := TokList[9];
      if Ntoks >= 11 then aPollut.Data[POLLUT_INIT_INDEX] := TokList[10];
    end;
    Result := 0;
  end;
end;


function ReadLanduseData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use data from a line of input.
//-----------------------------------------------------------------------------
var
  ID           : String;
  aLanduse     : TLanduse;
  aNonPtSource : TNonpointSource;
  J            : Integer;
begin
    ID := TokList[0];
    aLanduse := TLanduse.Create;
    for J := 0 to Project.Lists[POLLUTANT].Count - 1 do
    begin
      aNonPtSource := TNonpointSource.Create;
      aLanduse.NonpointSources.AddObject(Project.Lists[POLLUTANT].Strings[J],
        aNonPtSource);
    end;
    Project.Lists[LANDUSE].AddObject(ID, aLanduse);
    Project.HasItems[LANDUSE] := True;
    if Ntoks > 1 then aLanduse.Data[LANDUSE_CLEANING_INDEX]  := TokList[1];
    if Ntoks > 2 then aLanduse.Data[LANDUSE_AVAILABLE_INDEX] := TokList[2];
    if Ntoks > 3 then aLanduse.Data[LANDUSE_LASTCLEAN_INDEX] := TokList[3];
    aLanduse.Data[COMMENT_INDEX ] := Comment;
    Result := 0;
end;


function ReadBuildupData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant buildup function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.BuildupData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadWashoffData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant washoff function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.WashoffData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadCoverageData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use coverage data from a line of input.
//-----------------------------------------------------------------------------
var
  MaxToks: Integer;
  X      : Single;
  S      : TSubcatch;
  S1     : String;
  I      : Integer;
begin
  Result := 0;
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else begin
    MaxToks := 3;
    while (MaxToks <= nToks) do
    begin
      if not Uutils.GetSingle(TokList[MaxToks-1], X) then
      begin
        Result := ErrMsg(NUMBER_ERR, TokList[MaxToks-1]);
        break;
      end;
      S1 := TokList[MaxToks-2];
      I := S.LandUses.IndexOfName(S1);
      S1 := TokList[MaxToks-2] + '=' + TokList[MaxToks-1];
      if I < 0
      then S.LandUses.Add(S1)
      else S.Landuses[I] := S1;
      MaxToks := MaxToks + 2;
    end;
    S.Data[SUBCATCH_LANDUSE_INDEX] := IntToStr(S.LandUses.Count);
  end;
end;


function ReadTreatmentData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant treatment function from a line of input.
//-----------------------------------------------------------------------------
var
  S     : String;
  aNode : TNode;
  I     : Integer;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0 then
      Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      I := aNode.Treatment.IndexOfName(TokList[1]);
      S := Copy(Line, Pos(TokList[1], Line)+Length(TokList[1]), Length(Line));
      S := TokList[1] + '=' + Trim(S);
      if I < 0 then
        aNode.Treatment.Add(S)
      else
        aNode.Treatment[I] := S;
      aNode.Data[NODE_TREAT_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadExInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads external inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : array[1..7] of String;
  Inflow: String;
  I     : Integer;
  aNode : TNode;
begin

  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S[1] := TokList[1];                // Constituent name
      S[2] := TokList[2];                // Time Series name
      S[3] := 'FLOW';
      S[4] := '1.0';
      if not SameText(S[1], 'FLOW') then
      begin
        if nToks >= 4 then S[3] := TokList[3] else S[3] := 'CONCEN';
        if nToks >= 5 then S[4] := TokList[4] else S[4] := '1.0';
      end;
      if nToks >= 6 then S[5] := TokList[5] else S[5] := '1.0';
      if nToks >= 7 then S[6] := TokList[6] else S[6] := '';
      if nToks >= 8 then S[7] := TokList[7] else S[7] := '';
      Inflow := S[1] + '=' + S[2] + #13 + S[3] + #13 + S[4] + #13 +
                             S[5] + #13 + S[6] + #13 + S[7];
      I := aNode.DXInflow.IndexOfName(S[1]);
      if I < 0 then aNode.DXInflow.Add(Inflow)
      else aNode.DXInflow[I] := Inflow;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadDWInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads dry weather inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  M    : Integer;
  S    : String;
  S1   : String;
  I    : Integer;
  aNode: TNode;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S1 := TokList[1];
      S := S1 + '=' + TokList[2];
      for M := 3 to Ntoks-1 do S := S + #13 + TokList[M];
      I := aNode.DWInflow.IndexOfName(S1);
      if I < 0
      then aNode.DWInflow.Add(S)
      else aNode.DWInflow[I] := S;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadPatternData: Integer;
//-----------------------------------------------------------------------------
//  Reads time pattern data from a line of input.
//-----------------------------------------------------------------------------
var
  ID: String;
  Index: Integer;
  aPattern: TPattern;
  PatType: Integer;
  J: Integer;
begin
  if nToks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    J := 1;
    ID := TokList[0];
    Index := Project.Lists[PATTERN].IndexOf(ID);
    if Index < 0 then
    begin
      aPattern := TPattern.Create;
      aPattern.Comment := Comment;
      Project.Lists[PATTERN].AddObject(ID, aPattern);
      Project.HasItems[PATTERN] := True;
      PatType := Uutils.FindKeyWord(TokList[1], PatternTypes, 7);
      if PatType < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        exit;
      end;
      aPattern.PatternType := PatType;
      J := 2;
    end
    else aPattern := TPattern(Project.Lists[PATTERN].Objects[Index]);
    while (J < nToks) and (aPattern.Count <= High(aPattern.Data)) do
    begin
      aPattern.Data[aPattern.Count] := TokList[J];
      Inc(J);
      Inc(aPattern.Count);
    end;
    Result := 0;
  end;
end;


function ReadIIInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;

begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      aNode.IIInflow.Clear;
      aNode.IIInflow.Add(TokList[1]);
      aNode.IIInflow.Add(TokList[2]);
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadLoadData: Integer;
//-----------------------------------------------------------------------------
//  Reads initial pollutant loading data from a line of input.
//-----------------------------------------------------------------------------
var
  X  : Single;
  S  : TSubcatch;
  S1 : String;
  I  : Integer;

begin
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0
  then Result := ErrMsg(POLLUT_ERR, TokList[1])
  else if not Uutils.GetSingle(TokList[2], X)
  then Result := ErrMsg(NUMBER_ERR, TokList[2])
  else
  begin
      S1 := TokList[1] + '=' + TokList[2];
      I := S.Loadings.IndexOfName(TokList[1]);
      if I < 0
      then S.Loadings.Add(S1)
      else S.Loadings[I] := S1;
      S.Data[SUBCATCH_LOADING_INDEX] := 'YES';
      Result := 0;
  end;
end;


function ReadCurveData: Integer;
//-----------------------------------------------------------------------------
//  Reads curve data from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  K       : Integer;
  L       : Integer;
  M       : Integer;
  ObjType : Integer;
  ID      : String;
  aCurve  : TCurve;
begin
  // Check for too few tokens
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Check if curve ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID) then
    begin
      Index := PrevIndex;
      ObjType := CurveType;
    end
    else Project.FindCurve(ID, ObjType, Index);

    // Create new curve if ID not in data base
    K := 2;
    if Index < 0 then
    begin

      // Check for valid curve type keyword
      M := -1;
      for L := 0 to High(CurveTypeOptions) do
      begin
        if SameText(TokList[1], CurveTypeOptions[L]) then
        begin
          M := L;
          break;
        end;
      end;
      if M < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        Exit;
      end;

      // Convert curve type keyword index to a curve object category
      case M of
      0: ObjType := CONTROLCURVE;
      1: ObjType := DIVERSIONCURVE;
      2..5:
         ObjType := PUMPCURVE;
      6: ObjType := RATINGCURVE;
      7: ObjType := SHAPECURVE;
      8: ObjType := STORAGECURVE;
      9: ObjType := TIDALCURVE;
      end;

      // Create a new curve object
      aCurve := TCurve.Create;
      aCurve.Comment := Comment;
      aCurve.CurveType := TokList[1];
      if ObjType = PUMPCURVE
      then aCurve.CurveCode := M - 1
      else aCurve.CurveCode := 0;
      Project.Lists[ObjType].AddObject(ID, aCurve);
      Project.HasItems[ObjType] := True;
      Index := Project.Lists[ObjType].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
      CurveType := ObjType;
      K := 3;
    end;

    // Add x,y values to the list maintained by the curve
    aCurve := TCurve(Project.Lists[ObjType].Objects[Index]);
    while K <= nToks-1 do
    begin
      aCurve.Xdata.Add(TokList[K-1]);
      aCurve.Ydata.Add(TokList[K]);
      K := K + 2;
    end;
    Result := 0;
  end;
end;


function ReadTimeseriesData: Integer;
//-----------------------------------------------------------------------------
//  Reads time series data from a line of input.
//-----------------------------------------------------------------------------
const
  NEEDS_DATE = 1;
  NEEDS_TIME = 2;
  NEEDS_VALUE = 3;
var
  Index     : Integer;
  State     : Integer;
  K         : Integer;
  ID        : String;
  StrDate   : String;
  aTseries  : TTimeseries;
begin
  // Check for too few tokens
  Result := -1;
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '');

  // Check if series ID is same as for previous line
  ID := TokList[0];
  if (ID = PrevID) then Index := PrevIndex
  else Index := Project.Lists[TIMESERIES].IndexOf(ID);

  // If starting input for a new series then create it
  if Index < 0 then
  begin
    aTseries := TTimeseries.Create;
    aTseries.Comment := Comment;
    Project.Lists[TIMESERIES].AddObject(ID,aTseries);
    Project.HasItems[TIMESERIES] := True;
    Index := Project.Lists[TIMESERIES].Count - 1;
    PrevID := ID;
    PrevIndex := Index;
  end;
  aTseries := TTimeseries(Project.Lists[TIMESERIES].Objects[Index]);

  // Check if external file name used
  if SameText(TokList[1], 'FILE') then
  begin
    aTseries.Filename := TokList[2];
    Result := 0;
    Exit;
  end;

  // Add values to the list maintained by the timeseries
  State := NEEDS_DATE;
  K := 1;
  while K < nToks do
  begin
    case State of

    NEEDS_DATE:
      begin
        try
          StrDate := Uutils.ConvertDate(TokList[K]);
          StrToDate(StrDate, MyFormatSettings);
          aTseries.Dates.Add(StrDate);
          Inc(K);
          if K >= nToks then break;
        except
          On EconvertError do aTseries.Dates.Add('');
        end;
        State := NEEDS_TIME;
      end;

    NEEDS_TIME:
      begin
        aTseries.Times.Add(TokList[K]);
        Inc(K);
        if K >= nToks then break;
        State := NEEDS_VALUE;
      end;

    NEEDS_VALUE:
      begin
        aTseries.Values.Add(TokList[K]);
        Result := 0;
        State := NEEDS_DATE;
        Inc(K);
      end;
    end;
  end;
  if Result = -1 then Result := ErrMsg(ITEMS_ERR, '');
end;


function ReadControlData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads a control rule statement from line of input.
//-----------------------------------------------------------------------------
begin
  Project.ControlRules.Add(Line);
  Result := 0;
end;


function ReadLidUsageData: Integer;
//-----------------------------------------------------------------------------
//  Reads LID usage data from line of input.
//-----------------------------------------------------------------------------
var
  aSubcatch: TSubcatch;
begin
  aSubcatch := FindSubcatch(TokList[0]);
  if aSubcatch = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else Result := Ulid.ReadLIDUsageData(aSubcatch, TokList, Ntoks);
end;


procedure ReadReportOption(Index: Integer);
begin
  if (Ntoks >= 2) and SameText(TokList[1], 'YES') then
    Project.Options.Data[Index] := 'YES'
  else
    Project.Options.Data[Index] := 'NO';
end;


function ReadReportData: Integer;
//-----------------------------------------------------------------------------
//  Reads reporting options from a line of input.
//-----------------------------------------------------------------------------
begin
  if SameText(TokList[0], 'CONTROLS')
  then ReadReportOption(REPORT_CONTROLS_INDEX)
  else if SameText(TokList[0], 'INPUT')
  then ReadReportOption(REPORT_INPUT_INDEX)
  else ReportingForm.Import(TokList, Ntoks);
  Result := 0;
end;


function ReadFileData: Integer;
//-----------------------------------------------------------------------------
//  Reads interface file usage from a line of input.
//-----------------------------------------------------------------------------
var
  Fname: String;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Fname := TokList[2];
    if ExtractFilePath(Fname) = ''
    then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
    Project.IfaceFiles.Add(TokList[0] + ' ' + TokList[1] + ' ' +
     '"' + Fname + '"');
    Result := 0;
  end;
end;


////  This function was added to release 5.1.007.  ////                        //(5.1.007)
function ReadAdjustmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads climate adjustments from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then exit;
  if SameText(TokList[0], 'Temperature') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then TempAdjust[I] := ''
        else TempAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Evaporation') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then EvapAdjust[I] := ''
        else EvapAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Rainfall') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then RainAdjust[I] := ''
        else RainAdjust[I] := TokList[I+1];
    end;
  end

////  Added to release 5.1.008.  ////                                          //(5.1.008)
  else if SameText(TokList[0], 'Conductivity') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then CondAdjust[I] := ''
        else CondAdjust[I] := TokList[I+1];
    end;
  end;
////////////////////////////////////////////////////////
end;


////  This function was added to release 5.1.011.  ////                        //(5.1.011)
function ReadEventData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads hydraulic event data from a line of input.
//-----------------------------------------------------------------------------
begin
  Result := 0;
  if Length(Line) = 0 then exit;
  if LeftStr(Line,2) = ';;' then exit;
  Project.Events.Add(Line);
end;


function ReadOptionData: Integer;
//-----------------------------------------------------------------------------
//  Reads an analysis option from a line of input.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Keyword : String;
  S : String;
  S2: String;
  I : Integer;
  X : Single;
  T : Extended;
begin
  // Check which keyword applies
  Result := 0;
  if Ntoks < 2 then exit;
  Keyword := TokList[0];
  if SameText(Keyword, 'TEMPDIR') then exit;
  Index := Uutils.FindKeyWord(Keyword, OptionLabels, 17);
  case Index of

    -1:  Result := ErrMsg(KEYWORD_ERR, Keyword);

    ROUTING_MODEL_INDEX:
    begin
      if SameText(TokList[1], 'NONE') then
      begin
         Project.Options.Data[IGNORE_ROUTING_INDEX] := 'YES';
         Exit;
      end;

      I := Uutils.FindKeyWord(TokList[1], OldRoutingOptions, 3);
      if I >= 0 then TokList[1] := RoutingOptions[I];
    end;

    START_DATE_INDEX, REPORT_START_DATE_INDEX, END_DATE_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      try
        StrToDate(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    START_TIME_INDEX, REPORT_START_TIME_INDEX, END_TIME_INDEX:
    begin
      S := TokList[1];
      try
        StrToTime(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    SWEEP_START_INDEX, SWEEP_END_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      S2 := S + '/1947';
      try
        StrToDate(S2, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    WET_STEP_INDEX, DRY_STEP_INDEX, REPORT_STEP_INDEX:
    begin
      S := TokList[1];
      if Uutils.StrHoursToTime(S) = -1 then Result := ErrMsg(TIMESTEP_ERR, '');
    end;

    ROUTING_STEP_INDEX:
    begin
      S := TokList[1];
      T := 0.0;
      if not Uutils.GetExtended(S, T) then
      begin
        T := Uutils.StrHoursToTime(S)*86400.;
        if T <= 0.0 then Result := ErrMsg(TIMESTEP_ERR, '')
        else TokList[1] := Format('%.0f',[T]);
      end;
    end;

    VARIABLE_STEP_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
        TokList[1] := IntToStr(Round(100.0*X))
      else
        TokList[1] := '0';
    end;

    INERTIAL_DAMPING_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
      begin
        if X = 0 then TokList[1] := 'NONE'
        else TokList[1] := 'PARTIAL';
      end;
    end;

    // This option is now fixed to SWMM 4.
    COMPATIBILITY_INDEX:
    begin
      TokList[1] := '4';
    end;

    MIN_ROUTE_STEP_INDEX,                                                      //(5.1.008)
    LENGTHEN_STEP_INDEX,
    MIN_SURFAREA_INDEX,
    MIN_SLOPE_INDEX,
    MAX_TRIALS_INDEX,
    HEAD_TOL_INDEX,
    SYS_FLOW_TOL_INDEX,
    LAT_FLOW_TOL_INDEX:
    begin
      Uutils.GetSingle(TokList[1], X);
      if X <= 0 then TokList[1] := '0';
    end;

    NORMAL_FLOW_LTD_INDEX:
    begin
      if SameText(TokList[1], 'NO') or SameText(TokList[1], 'SLOPE')
      then TokList[1] := 'SLOPE'
      else if SameText(TokList[1], 'YES') or SameText(TokList[1], 'FROUDE')
      then TokList[1] := 'FROUDE'
      else if SameText(TokList[1], 'BOTH') then TokList[1] := 'BOTH'
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

    FORCE_MAIN_EQN_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], ForceMainEqnOptions, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := ForceMainEqnOptions[I];
    end;

    LINK_OFFSETS_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], LinkOffsetsOptions, 10);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := LinkOffsetsOptions[I];
    end;

    IGNORE_RAINFALL_INDEX,
    IGNORE_SNOWMELT_INDEX,
    IGNORE_GRNDWTR_INDEX,
    IGNORE_ROUTING_INDEX,
    IGNORE_QUALITY_INDEX:
    begin
      if not SameText(TokList[1], 'YES') and not SameText(TokList[1], 'NO')
      then Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

////  Following section added to release 5.1.010.  ////                        //(5.1.010)
    NUM_THREADS_INDEX:
    begin
      I := StrToIntDef(TokList[1], 1);
      if I < 0 then I := 1;
      if (I = 0) or (I > Uutils.GetCPUs) then I := Uutils.GetCPUs;
      TokList[1] := IntToStr(I);
    end;
////

  end;
  if Result = 0 then Project.Options.Data[Index] := TokList[1];
end;


function ReadTagData: Integer;
//-----------------------------------------------------------------------------
//  Reads in tag data from a line of input.
//-----------------------------------------------------------------------------
const
  TagTypes: array[0..3] of PChar = ('Gage', 'Subcatch', 'Node', 'Link');
var
  I, J : Integer;
  aNode: TNode;
  aLink: TLink;
begin
  Result := 0;
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[0], TagTypes, 4);
    case I of

    -1: Result := ErrMsg(KEYWORD_ERR, TokList[0]);

     0: begin
          J := Project.Lists[RAINGAGE].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetGage(J) do Data[TAG_INDEX] := TokList[2];
        end;

     1: begin
          J := Project.Lists[SUBCATCH].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetSubcatch(SUBCATCH, J) do
            Data[TAG_INDEX] := TokList[2];
        end;

     2: begin
          aNode := FindNode(TokList[1]);
          if (aNode <> nil) then aNode.Data[TAG_INDEX] := TokList[2];
        end;

     3: begin
          aLink := FindLink(TokList[1]);
          if (aLink <> nil) then aLink.Data[TAG_INDEX] := TokList[2];
        end;
     end;
  end;
end;


function ReadSymbolData: Integer;
//-----------------------------------------------------------------------------
//  Reads rain gage coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  J     : Integer;
  X, Y  : Extended;
  aGage : TRaingage;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the gage ID in the database
    J := Project.Lists[RAINGAGE].IndexOf(TokList[0]);

    // If gage exists then assign it X & Y coordinates
    if (J >= 0) then
    begin
      aGage := Project.GetGage(J);
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
          Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aGage.X := X;
        aGage.Y := Y;
      end;
    end;
  end;
end;


function ReadMapData: Integer;
//-----------------------------------------------------------------------------
//  Reads map dimensions data from a line of input.
//-----------------------------------------------------------------------------
var
  I       : Integer;
  Index   : Integer;
  Keyword : String;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, MapWords, 4);
  case Index of

    0:  // Map dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for I := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Dimensions do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
          MapExtentSet := True;
        end;
      end;
    end;

    1:  //Map units
    if Ntoks > 1 then
    begin
      I := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
      with MapForm.Map.Dimensions do
      begin
        if Units = muDegrees then Digits := MAXDEGDIGITS
        else Digits := Umap.DefMapDimensions.Digits;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadCoordData: Integer;
//-----------------------------------------------------------------------------
//  Reads node coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aNode : TNode;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the node ID in the database
    aNode := FindNode(TokList[0]);

    // If node exists then assign it X & Y coordinates
    if (aNode <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aNode.X := X;
        aNode.Y := Y;
      end;
    end;
  end;
end;


function ReadVertexData: Integer;
//-----------------------------------------------------------------------------
//  Reads link vertex coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aLink : TLink;

begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the link ID in the database
    aLink := FindLink(TokList[0]);;

    // If link exists then assign it X & Y coordinates
    if (aLink <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  aLink.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadPolygonData: Integer;
//-----------------------------------------------------------------------------
//  Reads polygon coordinates associated with subcatchment outlines.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  X, Y  : Extended;
  S     : TSubcatch;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the subcatchment ID in the database
    S := nil;
    Index := Project.Lists[SUBCATCH].IndexOf(TokList[0]);
    if Index >= 0 then S := Project.GetSubcatch(SUBCATCH, Index);

    // If subcatchment exists then add a new vertex to it
    if (S <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  S.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadLabelData: Integer;
//-----------------------------------------------------------------------------
//  Reads map label data from a line of input.
//-----------------------------------------------------------------------------
var
  Ntype    : Integer;
  Index    : Integer;
  X, Y     : Extended;
  S        : String;
  aMapLabel: TMapLabel;
begin
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    if not Uutils.GetExtended(TokList[0], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[0])
    else if not Uutils.GetExtended(TokList[1], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
    else begin
      S := TokList[2];
      aMapLabel := TMapLabel.Create;
      aMapLabel.X := X;
      aMapLabel.Y := Y;
      Project.Lists[MAPLABEL].AddObject(S, aMapLabel);
      Index := Project.Lists[MAPLABEL].Count - 1;
      aMapLabel.Text := PChar(Project.Lists[MAPLABEL].Strings[Index]);
      Project.HasItems[MAPLABEL] := True;
      if Ntoks >= 4 then
      begin
        if (Length(TokList[3]) > 0) and
          Project.FindNode(TokList[3], Ntype, Index) then
            aMapLabel.Anchor := Project.GetNode(Ntype, Index);
      end;
      if Ntoks >= 5 then aMapLabel.FontName := TokList[4];
      if Ntoks >= 6 then aMapLabel.FontSize := StrToInt(TokList[5]);
      if Ntoks >= 7 then
        if StrToInt(TokList[6]) = 1 then aMapLabel.FontBold := True;
      if Ntoks >= 8 then
        if StrToInt(TokList[7]) = 1 then aMapLabel.FontItalic := True;
      Result := 0;
    end;
  end;
end;


function ReadBackdropData: Integer;
//-----------------------------------------------------------------------------
//  Reads map backdrop image information from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  Keyword : String;
  I       : Integer;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, BackdropWords, 4);
  case Index of

    0:  //Backdrop file
    if Ntoks > 1 then MapForm.Map.Backdrop.Filename := TokList[1];

    1:  // Backdrop dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for i := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Backdrop do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
        end;
      end;
    end;

    2:  //Map units - deprecated
    if Ntoks > 1 then
    begin
      i := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
    end;

    3, 4:  //Backdrop offset or scaling -- deprecated
    begin
      if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
      else if not Uutils.GetExtended(TokList[1], X[1]) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], X[2]) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        if Index = 3 then
        begin
          BackdropX := X[1];
          BackdropY := X[2];
        end
        else
        begin
          BackdropW := X[1];
          BackdropH := X[2];
        end;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadProfileData: Integer;
//-----------------------------------------------------------------------------
//  Reads profile plot data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
  S   : String;
begin
  Result := 0;
  if (Ntoks < 2) then Exit;

  // Locate the profile name in the database
  I := Project.ProfileNames.IndexOf(TokList[0]);

  // If profile does not exist then create it
  if (I < 0) then
  begin
    Project.ProfileNames.Add(TokList[0]);
    I := Project.ProfileNames.Count-1;
    S := '';
    Project.ProfileLinks.Add(S);
  end
  else S := Project.ProfileLinks[I] + #13;

  // Add each remaining token to the list of links in the profile
  S := S + TokList[1];
  for J := 2 to Ntoks-1 do
    S := S + #13 + TokList[J];
  Project.ProfileLinks[I] := S;
end;


procedure SetMapDimensions;
//-----------------------------------------------------------------------------
// Determines map dimensions based on range of object coordinates.
//-----------------------------------------------------------------------------
begin
  with MapForm.Map do
  begin
    // Dimensions not provided in [MAP] section
    if not MapExtentSet then
    begin

      // Dimensions were provided in [BACKDROP] section
      if (Backdrop.LowerLeft.X <> Backdrop.UpperRight.X) then
      begin

        // Interpret these as map dimensions
        Dimensions.LowerLeft  := Backdrop.LowerLeft;
        Dimensions.UpperRight := Backdrop.UpperRight;

        // Compute backdrop dimensions from Offset and Scale values
        Backdrop.LowerLeft.X  := BackdropX;
        Backdrop.LowerLeft.Y  := BackdropY - BackdropH;
        Backdrop.UpperRight.X := BackdropX + BackdropW;
        Backdrop.UpperRight.Y := BackdropY;
      end

      // No dimensions of any kind provided
      else with Dimensions do
        Ucoords.GetCoordExtents(LowerLeft.X, LowerLeft.Y,
                                UpperRight.X, UpperRight.Y);
    end;
  end;
end;


function ParseInpLine(S: String): Integer;
//-----------------------------------------------------------------------------
//  Parses current input line depending on current section of input file.
//-----------------------------------------------------------------------------
begin
  case Section of
    1:    Result := ReadOptionData;
    2:    Result := ReadRaingageData;
    3:    Result := ReadHydrographData;
    4:    Result := ReadEvaporationData;
    5:    Result := ReadSubcatchmentData;
    6:    Result := ReadSubareaData;
    7:    Result := ReadInfiltrationData;
    8:    Result := ReadAquiferData;
    9:    Result := ReadGroundwaterData;
    10:   Result := ReadJunctionData;
    11:   Result := ReadOutfallData;
    12:   Result := ReadStorageData;
    13:   Result := ReadDividerData;
    14:   Result := ReadConduitData;
    15:   Result := ReadPumpData;
    16:   Result := ReadOrificeData;
    17:   Result := ReadWeirData;
    18:   Result := ReadOutletData;
    19:   Result := ReadXsectionData;
    20:   Result := ReadTransectData;
    21:   Result := ReadLossData;
    //22: ReadControlData called directly from ReadFile
    23:   Result := ReadPollutantData;
    24:   Result := ReadLanduseData;
    25:   Result := ReadBuildupData;
    26:   Result := ReadWashoffData;
    27:   Result := ReadCoverageData;
    28:   Result := ReadExInflowData;
    29:   Result := ReadDWInflowData;
    30:   Result := ReadPatternData;
    31:   Result := ReadIIInflowData;
    32:   Result := ReadLoadData;
    33:   Result := ReadCurveData;
    34:   Result := ReadTimeseriesData;
    35:   Result := ReadReportData;
    36:   Result := ReadFileData;
    37:   Result := ReadMapData;
    38:   Result := ReadCoordData;
    39:   Result := ReadVertexdata;
    40:   Result := ReadPolygonData;
    41:   Result := ReadSymbolData;
    42:   Result := ReadLabelData;
    43:   Result := ReadBackdropData;
    44:   Result := ReadProfileData;
    45:   Result := ReadCurveData;
    46:   Result := ReadTemperatureData;
    47:   Result := ReadSnowpackData;
    48:   Result := ReadTreatmentData(S);
    49:   Result := ReadTagData;
    50:   Result := Ulid.ReadLidData(TokList, Ntoks);
    51:   Result := ReadLidUsageData;
    52:   Result := ReadGroundwaterFlowEqn(S);
    53:   Result := ReadAdjustmentData;                                        //(5.1.007)
    else  Result := 0;
  end;
end;


function ParseImportLine(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Processes line of input from imported scenario or map file.
//  (Not currently used.)
//-----------------------------------------------------------------------------
begin
  Result := 0;
end;


procedure StripComment(const Line: String; var S: String);
//-----------------------------------------------------------------------------
//  Strips comment (text following a ';') from a line of input.
//  Ignores comment if it begins with ';;'.
//-----------------------------------------------------------------------------
var
  P: Integer;
  N: Integer;
  C: String;
begin
  C := '';
  S := Trim(Line);
  P := Pos(';', S);
  if P > 0 then
  begin
    N := Length(S);
    C := Copy(S, P+1, N);
    if (Length(C) >= 1) and (C[1] <> ';') then
    begin
      if Length(Comment) > 0 then Comment := Comment + #13;
      Comment := Comment + C;
    end;
    Delete(S, P, N);
  end;
end;


function FindNewSection(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Checks if S matches any of the section heading key words.
//-----------------------------------------------------------------------------
var
  K: Integer;
begin
  for K := 0 to High(SectionWords) do
  begin
    if Pos(SectionWords[K], S) = 1 then
    begin
      Result := K;
      Exit;
    end;
  end;
  Result := -1;
end;


function StartNewSection(S: String): Integer;
//-----------------------------------------------------------------------------
//  Begins reading a new section of the input file.
//-----------------------------------------------------------------------------
var
  K : Integer;

begin
  // Determine which new section to begin
  K := FindNewSection(UpperCase(S));
  if (K >= 0) then
  begin

    //Update section code
    Section := K;
    PrevID := '';
    Result := 0;
  end
  else Result := ErrMsg(KEYWORD_ERR, S);
  Comment := '';
end;


function ReadFile(var F: Textfile; const Fsize: Int64):Boolean;
//-----------------------------------------------------------------------------
//  Reads each line of a SWMM input file.
//-----------------------------------------------------------------------------
var
  Err         : Integer;
  ByteCount   : Integer;
  StepCount   : Integer;
  StepSize    : Integer;
  S           : String;
begin
  Result := True;
  ErrCount := 0;
  LineCount := 0;
  Comment := '';

  // Initialize progress meter settings
  StepCount := MainForm.ProgressBar.Max div MainForm.ProgressBar.Step;
  StepSize := Fsize div StepCount;
  if StepSize < 1000 then StepSize := 0;
  ByteCount := 0;

  // Read each line of input file
  Reset(F);
  while not Eof(F) do
  begin
    Err := 0;
    Readln(F, Line);
    Inc(LineCount);
    if StepSize > 0 then
    begin
      Inc(ByteCount, Length(Line));
      MainForm.UpdateProgressBar(ByteCount, StepSize);
    end;

    // Strip out trailing spaces, control characters & comment
    Line := TrimRight(Line);
    StripComment(Line, S);

    // Check if line begins a new input section
    if (Pos('[', S) = 1) then Err := StartNewSection(S)
    else
    begin

////  Following code section modified for release 5.1.011.  ////               //(5.1.011)
      // Check if line contains project title/notes
      if (Section = 0) and (Length(Line) > 0) then
      begin
        if LeftStr(Line,2) <> ';;' then Err := ReadTitleData(Line);
      end

      // Check if line contains a control rule clause
      else if (Section = 22) then Err := ReadControlData(Line)

      // Check if line contains an event start/end dates
      else if (Section = 54) then Err := ReadEventData(Trim(Line))

      // If in some section, then process the input line
      else
      begin
        // Break line into string tokens and parse their contents
        Uutils.Tokenize(S, TokList, Ntoks);
        if (Ntoks > 0) and (Section >= 0) then
        begin
          Err := ParseInpLine(S);
          Comment := '';
        end

        // No current section -- file was probably not an EPA-SWMM file
        else if (Ntoks > 0) then
        begin
          Result := False;
          Exit;
        end;
      end;
    end;
////

    // Increment error count
    if Err > 0 then Inc(ErrCount);
  end;  //End of file.

  if ErrCount > MAX_ERRORS then ErrList.Add(
    IntToStr(ErrCount-MAX_ERRORS) + TXT_MORE_ERRORS);
end;


procedure DisplayInpErrForm(const Fname: String);
//-----------------------------------------------------------------------------
//  Displays Status Report form that lists any error messages.
//-----------------------------------------------------------------------------
begin
  SysUtils.DeleteFile((TempReportFile));
  TempReportFile := Uutils.GetTempFile(TempDir,'swmm');
  ErrList.Insert(0, TXT_ERROR_REPORT + Fname + #13);
  ErrList.SaveToFile(TempReportFile);
  MainForm.MnuReportStatusClick(MainForm);
end;


procedure ReverseVertexLists;
//-----------------------------------------------------------------------------
//  Reverses list of vertex points for each link in the project.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
begin
  for I := 0 to MAXCLASS do
  begin
    if not Project.IsLink(I) then continue;
    for J := 0 to Project.Lists[I].Count-1 do
      if Project.GetLink(I, J).Vlist <> nil then
        Project.GetLink(I, J).Vlist.Reverse;
  end;
end;


procedure SetSubcatchCentroids;
//-----------------------------------------------------------------------------
//  Determines the centroid of each subcatchment polygon.
//-----------------------------------------------------------------------------
var
  I : Integer;
begin
  for I := 0 to Project.Lists[SUBCATCH].Count - 1 do
  begin
    Project.GetSubcatch(SUBCATCH, I).SetCentroid;
  end;
end;


procedure SetIDPtrs;
//-----------------------------------------------------------------------------
//  Makes pointers to ID strings the ID property of objects.
//-----------------------------------------------------------------------------
var
  I, J : Integer;
  C : TSubcatch;
  S : String;

begin
  for I := 0 to MAXCLASS do
  begin
    if I = RAINGAGE then with Project.Lists[RAINGAGE] do
    begin
      for J := 0 to Count-1 do TRaingage(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsSubcatch(I) then with Project.Lists[SUBCATCH] do
    begin
      for J := 0 to Count-1 do
      begin
        C := TSubcatch(Objects[J]);
        C.ID := PChar(Strings[J]);
        S := Trim(C.Data[SUBCATCH_OUTLET_INDEX]);
        C.OutSubcatch := FindSubcatch(S);
        if C.OutSubcatch = nil then C.OutNode := FindNode(S);
      end;
    end
    else if Project.IsNode(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TNode(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsLink(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TLink(Objects[J]).ID := PChar(Strings[J]);
    end
    else if I = TRANSECT then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do
      begin
        TTransect(Objects[J]).CheckData;
        TTransect(Objects[J]).SetMaxDepth;
        Project.SetTransectConduitDepth(Strings[J],
          TTransect(Objects[J]).Data[TRANSECT_MAX_DEPTH]);
      end;
    end
    else continue;
  end;
end;


function ReadInpFile(const Fname: String):Boolean;
//-----------------------------------------------------------------------------
//  Reads SWMM input data from a text file.
//-----------------------------------------------------------------------------
var
  F : Textfile;
begin
  // Try to open the file
  Result := False;
  AssignFile(F,Fname);
  {$I-}
  Reset(F);
  {$I+}
  if (IOResult = 0) then
  begin

    // Create stringlists
    Screen.Cursor := crHourGlass;
    MapExtentSet := False;
    ErrList := TStringList.Create;
    TokList := TStringList.Create;
    SubcatchList := TStringList.Create;
    NodeList := TStringList.Create;
    LinkList := TStringList.Create;
    FileType := ftInput;
    InpFile := Fname;
    try

      // Read the file
      MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);
      SubcatchList.Sorted := True;
      NodeList.Sorted := True;
      LinkList.Sorted := True;
      Section := -1;
      Result := ReadFile(F, Uutils.GetFileSize(Fname));
      if (Result = True) then
      begin
        // Establish pointers to ID names
        SetIDPtrs;
      end;

    finally
      // Free the stringlists
      SubcatchList.Free;
      NodeList.Free;
      LinkList.Free;
      TokList.Free;
      MainForm.PageSetupDialog.Header.Text := Project.Title;
      MainForm.HideProgressBar;
      Screen.Cursor := crDefault;

      // Display errors if found & set map dimensions
      if Result = True then
      begin
        if ErrList.Count > 0 then DisplayInpErrForm(Fname);
        SetSubcatchCentroids;
        SetMapDimensions;
      end;
      ErrList.Free;
    end;
  end;

  // Close the input file
  CloseFile(F);
end;


procedure ClearDefaultDates;
//-----------------------------------------------------------------------------
//  Clears project's date/time settings.
//-----------------------------------------------------------------------------
begin
  with Project.Options do
  begin
    Data[START_DATE_INDEX]        := '';
    Data[START_TIME_INDEX]        := '';
    Data[REPORT_START_DATE_INDEX] := '';
    Data[REPORT_START_TIME_INDEX] := '';
    Data[END_DATE_INDEX]          := '';
    Data[END_TIME_INDEX]          := '';
  end;
end;


procedure SetDefaultDates;
//-----------------------------------------------------------------------------
//  Sets default values for project's date/time settings.
//-----------------------------------------------------------------------------
var
  StartTime: TDateTime;
  StartDate: TDateTime;
  T: TDateTime;
  D: TDateTime;
begin
  with Project.Options do
  begin

  // Process starting date/time
    try
      StartDate := StrToDate(Data[START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do StartDate := Date;
    end;
    StartTime := Uutils.StrHoursToTime(Data[START_TIME_INDEX]);
    if StartTime < 0 then StartTime := 0;
    D := StartDate + StartTime;
    Data[START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process reporting start date/time
    try
      D := StrToDate(Data[REPORT_START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[REPORT_START_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[REPORT_START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[REPORT_START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process ending date/time
    try
      D := StrToDate(Data[END_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[END_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[END_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[END_TIME_INDEX] := TimeToStr(D, MyFormatSettings);
  end;
end;


function OpenProject(const Fname: String): TInputFileType;
//-----------------------------------------------------------------------------
//  Reads in project data from a file.
//-----------------------------------------------------------------------------
begin
  // Show progress meter
  ClearDefaultDates;
  Screen.Cursor := crHourGlass;
  MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);

  // Use default map dimensions and backdrop settings
  MapForm.Map.Dimensions := DefMapDimensions;
  MapForm.Map.Backdrop := DefMapBackdrop;

  // Do the following for non-temporary input files
  if not SameText(Fname, Uglobals.TempInputFile) then
  begin

    // Create a backup file
    if AutoBackup
    then CopyFile(PChar(Fname), PChar(ChangeFileExt(Fname, '.bak')), FALSE);

    // Retrieve project defaults from .INI file
    if CompareText(ExtractFileExt(Fname), '.ini') <> 0
    then Uinifile.ReadProjIniFile(ChangeFileExt(Fname, '.ini'));
  end;

  // Read and parse each line of input file
  Result := iftNone;
  if ReadInpFile(Fname) then Result := iftINP;

  // Finish processing the input dataunit Uimport;

{-------------------------------------------------------------------}
{                    Unit:    Uimport.pas                           }
{                    Project: EPA SWMM                              }
{                    Version: 5.1                                   }
{                    Date:    12/02/13    (5.1.001)                 }
{                             04/04/14    (5.1.003)                 }
{                             04/14/14    (5.1.004)                 }
{                             09/15/14    (5.1.007)                 }
{                             03/19/15    (5.1.008)                 }
{                             08/05/15    (5.1.010)                 }
{                             08/01/16    (5.1.011)                 }
{                    Author:  L. Rossman                            }
{                                                                   }
{   Delphi Pascal unit that imports a SWMM project's data from a    }
{   a formatted text file.                                          }
{                                                                   }
{   5.1.011 - Support for reading [EVENTS] section added.           }
{-------------------------------------------------------------------}

interface

uses
  Classes, Forms, Controls, Dialogs, SysUtils, Windows, Math, StrUtils,
  Uutils, Uglobals;

const
  ITEMS_ERR    = 1;
  KEYWORD_ERR  = 2;
  SUBCATCH_ERR = 3;
  NODE_ERR     = 4;
  LINK_ERR     = 5;
  LANDUSE_ERR  = 6;
  POLLUT_ERR   = 7;
  NUMBER_ERR   = 8;
  XSECT_ERR    = 9;
  TRANSECT_ERR = 10;
  TIMESTEP_ERR = 11;
  DATE_ERR     = 12;
  LID_ERR      = 13;
  MAX_ERRORS   = 50;

type
  TFileType = (ftInput, ftImport);

// These routines can be called from other units
function  ErrMsg(const ErrCode: Integer; const Name: String): Integer;
function  OpenProject(const Fname: String): TInputFileType;
function  ReadInpFile(const Fname: String):Boolean;
procedure SetDefaultDates;

implementation

uses
  Fmain, Fmap, Fstatus, Dxsect, Uexport, Uinifile, Uproject, Umap,
  Ucoords, Uvertex, Uupdate, Dreporting, Ulid;

const
  MSG_READING_PROJECT_DATA = 'Reading project data... ';
  TXT_ERROR = 'Error ';
  TXT_AT_LINE = ' at line ';
  TXT_MORE_ERRORS = ' more errors found in file.';
  TXT_ERROR_REPORT = 'Error Report for File ';

  SectionWords : array[0..54] of PChar =                                       //(5.1.011)
    ('[TITLE',                    //0
     '[OPTION',                   //1
     '[RAINGAGE',                 //2
     '[HYDROGRAPH',               //3
     '[EVAPORATION',              //4
     '[SUBCATCHMENT',             //5
     '[SUBAREA',                  //6
     '[INFILTRATION',             //7
     '[AQUIFER',                  //8
     '[GROUNDWATER',              //9
     '[JUNCTION',                 //10
     '[OUTFALL',                  //11
     '[STORAGE',                  //12
     '[DIVIDER',                  //13
     '[CONDUIT',                  //14
     '[PUMP',                     //15
     '[ORIFICE',                  //16
     '[WEIR',                     //17
     '[OUTLET',                   //18
     '[XSECTION',                 //19
     '[TRANSECT',                 //20
     '[LOSS',                     //21
     '[CONTROL',                  //22
     '[POLLUTANT',                //23
     '[LANDUSE',                  //24
     '[BUILDUP',                  //25
     '[WASHOFF',                  //26
     '[COVERAGE',                 //27
     '[INFLOW',                   //28
     '[DWF',                      //29
     '[PATTERN',                  //30
     '[RDII',                     //31
     '[LOAD',                     //32
     '[CURVE',                    //33
     '[TIMESERIES',               //34
     '[REPORT',                   //35
     '[FILE',                     //36
     '[MAP',                      //37
     '[COORDINATES',              //38
     '[VERTICES',                 //39
     '[POLYGONS',                 //40
     '[SYMBOLS',                  //41
     '[LABELS',                   //42
     '[BACKDROP',                 //43
     '[PROFILE',                  //44
     '[TABLE',                    //45
     '[TEMPERATURE',              //46
     '[SNOWPACK',                 //47
     '[TREATMENT',                //48
     '[TAG',                      //49
     '[LID_CONTROL',              //50
     '[LID_USAGE',                //51
     '[GWF',                      //52                                         //(5.1.007)
     '[ADJUSTMENTS',              //53                                         //(5.1.007)
     '[EVENT');                   //54                                         //(5.1.011)

var
  FileType     : TFileType;
  InpFile      : String;
  ErrList      : TStringlist;
  SubcatchList : TStringlist;
  NodeList     : TStringlist;
  LinkList     : TStringlist;
  TokList      : TStringlist;
  Ntoks        : Integer;
  Section      : Integer;
  LineCount    : LongInt;
  ErrCount     : LongInt;
  Line         : String;
  Comment      : String;
  TsectComment : String;
  PrevID       : String;
  PrevIndex    : Integer;
  CurveType    : Integer;
  MapExtentSet : Boolean;
  ManningsN    : array[1..3] of String;

  // These are deprecated attributes of a backdrop image
  BackdropX    : Extended;
  BackdropY    : Extended;
  BackdropW    : Extended;
  BackdropH    : Extended;


function ErrMsg(const ErrCode: Integer; const Name: String): Integer;
//-----------------------------------------------------------------------------
//  Adds an error message for a specific error code to the list
//  of errors encountered when reading an input file.
//-----------------------------------------------------------------------------
var
  S: String;
begin
  if ErrCount <= MAX_ERRORS then
  begin
    case ErrCode of
      ITEMS_ERR:    S := 'Too few items ';
      KEYWORD_ERR:  S := 'Unrecognized keyword (' + Name + ') ';
      SUBCATCH_ERR: S := 'Undefined Subcatchment (' + Name + ') referenced ';
      NODE_ERR:     S := 'Undefined Node (' + Name + ') referenced ';
      LINK_ERR:     S := 'Undefined Link (' + Name + ') referenced ';
      LANDUSE_ERR:  S := 'Undefined Land Use (' + Name + ') referenced ';
      POLLUT_ERR:   S := 'Undefined Pollutant (' + Name + ') referenced ';
      NUMBER_ERR:   S := 'Illegal numeric value (' + Name + ') ';
      XSECT_ERR:    S := 'Illegal cross section for Link ' + Name + ' ';
      TRANSECT_ERR: S := 'No Transect defined for these data ';
      TIMESTEP_ERR: S := 'Illegal time step value ';
      DATE_ERR:     S := 'Illegal date/time value ';
      LID_ERR:      S := 'Undefined LID process (' + Name + ') referenced ';
      else          S := 'Unknown error ';
    end;
    S := S + 'at line ' + IntToStr(LineCount) + ':';
    ErrList.Add(S);
    if Section >= 0 then ErrList.Add(SectionWords[Section] + ']');
    ErrList.Add(Line);
    ErrList.Add('');
  end;
  Result := ErrCode;
end;


function FindSubcatch(const ID: String): TSubcatch;
//-----------------------------------------------------------------------------
//  Finds a Subcatchment object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Atype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if SubcatchList.Find(ID, Index)
    then Result := TSubcatch(SubcatchList.Objects[Index]);
  end
  else
  begin
    if (Project.FindSubcatch(ID, Atype, Index))
    then Result := Project.GetSubcatch(Atype, Index);
  end;
end;


function FindNode(const ID: String): TNode;
//-----------------------------------------------------------------------------
// Finds a Node object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Ntype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if NodeList.Find(ID, Index)
    then Result := TNode(NodeList.Objects[Index]);
  end
  else
  begin
    if (Project.FindNode(ID, Ntype, Index))
    then Result := Project.GetNode(Ntype, Index);
  end;
end;


function FindLink(const ID: String): TLink;
//-----------------------------------------------------------------------------
// Finds a Link object given its ID name.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Ltype : Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if LinkList.Find(ID, Index)
    then Result := TLink(LinkList.Objects[Index]);
  end
  else
  begin
    if (Project.FindLink(ID, Ltype, Index))
    then Result := Project.GetLink(Ltype, Index);
  end;
end;


function ReadTitleData(Line: String): Integer;
//-----------------------------------------------------------------------------
// Adds Line of text to a project's title and notes.
//-----------------------------------------------------------------------------
begin
  if Project.Lists[NOTES].Count = 0 then Project.Title := Line;
  Project.Lists[NOTES].Add(Line);
  Project.HasItems[NOTES] := True;
  Result := 0;
end;


procedure ReadOldRaingageData(I: Integer; aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of parsed rain gage data using older format.
//-----------------------------------------------------------------------------
begin
  if I = 0 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
    if Ntoks > 2 then aGage.Data[GAGE_SERIES_NAME] := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_DATA_FORMAT] := TokList[3];
    if Ntoks > 4 then aGage.Data[GAGE_DATA_FREQ]   := TokList[4];
  end;
  if I = 1 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
    if Ntoks > 2 then aGage.Data[GAGE_FILE_NAME]   := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_STATION_NUM] := TokList[3];
  end;
end;


procedure ReadNewRaingageData(aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of rain gage data using newer format.
//-----------------------------------------------------------------------------
var
  I: Integer;
  Fname: String;
begin
  if Ntoks > 1 then aGage.Data[GAGE_DATA_FORMAT] := TokList[1];
  if Ntoks > 2 then aGage.Data[GAGE_DATA_FREQ]   := TokList[2];
  if Ntoks > 3 then aGage.Data[GAGE_SNOW_CATCH]  := TokList[3];
  if Ntoks > 4 then
  begin
    I := Uutils.FindKeyWord(TokList[4], RaingageOptions, 4);
    if I = 0 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
      if Ntoks > 5 then aGage.Data[GAGE_SERIES_NAME] := TokList[5];
    end;
    if I = 1 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
      if Ntoks > 5 then
      begin
        Fname := TokList[5];
        if ExtractFilePath(Fname) = ''
        then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
        aGage.Data[GAGE_FILE_NAME] := Fname;
      end;
      if Ntoks > 6 then aGage.Data[GAGE_STATION_NUM] := TokList[6];
      if Ntoks > 7 then aGage.Data[GAGE_RAIN_UNITS]  := TokList[7];
    end;
  end;
end;


function ReadRaingageData: Integer;
//-----------------------------------------------------------------------------
//  Parses rain gage data from the input line.
//-----------------------------------------------------------------------------
var
  aGage   : TRaingage;
  ID      : String;
  I       : Integer;
begin
  if Ntoks < 2 then
  begin
    Result := ErrMsg(ITEMS_ERR, '');
    Exit;
  end;
  ID := TokList[0];
  aGage := TRaingage.Create;
  Uutils.CopyStringArray(Project.DefProp[RAINGAGE].Data, aGage.Data);
  aGage.X := MISSING;
  aGage.Y := MISSING;
  aGage.Data[COMMENT_INDEX ] := Comment;
  Project.Lists[RAINGAGE].AddObject(ID, aGage);
  Project.HasItems[RAINGAGE] := True;
  I := Uutils.FindKeyWord(TokList[1], RaingageOptions, 4);
  if I >= 0
    then ReadOldRaingageData(I, aGage)
    else ReadNewRaingageData(aGage);
  Result := 0;
end;


function ReadOldHydrographFormat(const I: Integer; UH: THydrograph): Integer;
//-----------------------------------------------------------------------------
//  Reads older format of unit hydrograph parameters from a line of input.
//-----------------------------------------------------------------------------
var
  J, K, N: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin
    N := 2;
    for K := 1 to 3 do
    begin
      for J := 1 to 3 do
      begin
        UH.Params[I,J,K] := TokList[N];
        Inc(N);
      end;
    end;
    for J := 1 to 3 do
    begin
      UH.InitAbs[I,J,1] := '';
      if Ntoks > N then UH.InitAbs[I,J,1] := TokList[N];
      Inc(N);
      for K := 2 to 3 do UH.InitAbs[I,J,K] := UH.InitAbs[I,J,1];
    end;
  end;
end;


function ReadHydrographData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII unit hydrograph data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J, K: Integer;
  ID: String;
  aUnitHyd: THydrograph;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin

    // Check if hydrograph ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID)
    then Index := PrevIndex
    else Index := Project.Lists[HYDROGRAPH].IndexOf(ID);

    // If starting input for a new hydrograph then create it
    if Index < 0 then
    begin
      aUnitHyd := THydrograph.Create;
      Project.Lists[HYDROGRAPH].AddObject(ID, aUnitHyd);
      Project.HasItems[HYDROGRAPH] := True;
      Index := Project.Lists[HYDROGRAPH].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
    end
    else aUnitHyd := THydrograph(Project.Lists[HYDROGRAPH].Objects[Index]);

    // Parse rain gage name for 2-token line
    if Ntoks = 2 then
    begin
      aUnitHyd.Raingage := TokList[1];
    end

    // Extract remaining hydrograph parameters
    else if Ntoks < 6 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      // Determine month of year
      I := Uutils.FindKeyWord(TokList[1], MonthLabels, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1]);

      // Determine if response type present - if not, process old format
      K := Uutils.FindKeyWord(TokList[2], ResponseTypes, 3) + 1;
      if K < 1 then Result := ReadOldHydrographFormat(I, aUnitHyd)
      else
      begin
        // Extract R-T-K values
        for J := 3 to 5 do
        begin
          aUnitHyd.Params[I,J-2,K] := TokList[J];
        end;
        // Extract IA parameters
        for J := 6 to 8 do
        begin
          if J >= Ntoks then break
          else aUnitHyd.InitAbs[I,J-5,K] := TokList[J];
        end;
      end;
    end;
  end;
end;


function ReadTemperatureData: Integer;
//-----------------------------------------------------------------------------
//  Reads description of air temperature data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else with Project.Climatology do
  begin
    I := Uutils.FindKeyWord(TokList[0], TempKeywords, 10);
    if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[0])
    else case I of
    0:  begin                                              // Time series
          TempDataSource := 1;
          TempTseries := TokList[1];
        end;
    1:  begin                                              // File
          TempDataSource := 2;
          TempFile := TokList[1];
          if ExtractFilePath(TempFile) = ''  then
          TempFile := ExtractFilePath(Uglobals.InputFileName) + TempFile;
          if Ntoks >= 3 then TempStartDate := TokList[2];
        end;
    2:  begin                                              // Wind speed
          if SameText(TokList[1], 'MONTHLY') then
          begin
            if Ntoks < 14 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              WindType := MONTHLY_WINDSPEED;
              for I := 1 to 12 do WindSpeed[I] := TokList[I+1];
            end;
          end
          else if SameText(TokList[1], 'FILE') then WindType := FILE_WINDSPEED
          else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        end;
    3:  begin
          if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
          else for I := 1 to 6 do SnowMelt[I] := TokList[I];
        end;
    4:  begin
          if Ntoks < 12 then Result := ErrMsg(ITEMS_ERR, '')
          else
          begin
            if  SameText(TokList[1], 'IMPERVIOUS') then
              for I := 1 to 10 do ADCurve[1][I] := TokList[I+1]
            else if SameText(TokList[1], 'PERVIOUS') then
              for I := 1 to 10 do ADCurve[2][I] := TokList[I+1]
            else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
          end;
        end;
    end;
  end;
end;


function ReadEvaporationRates(Etype: Integer): Integer;
var
  J: Integer;
begin
  if (Etype <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin
    EvapType := Etype;

    case EvapType of

      CONSTANT_EVAP:
        for J := 0 to 11 do EvapData[J] := TokList[1];

      TSERIES_EVAP:
        EvapTseries := TokList[1];

      FILE_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          PanData[J-1] := TokList[J];
        end;

      MONTHLY_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          EvapData[J-1] := TokList[J];
        end;
    end;

    Result := 0;
  end;

end;


function ReadEvaporationData: Integer;
//-----------------------------------------------------------------------------
//  Reads evaporation data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  I := Uutils.FindKeyWord(TokList[0], EvapOptions, 4);
  if I >= 0 then Result := ReadEvaporationRates(I)

  else if (I <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin

    if SameText(TokList[0], 'RECOVERY')
    then RecoveryPat := TokList[1]

    else if SameText(TokList[0], 'DRY_ONLY') then
    begin
      if SameText(TokList[1], 'NO') then EvapDryOnly := False
      else if SameText(TokList[1], 'YES') then EvapDryOnly := True
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end

    else Result := ErrMsg(KEYWORD_ERR, TokList[0]);

  end;
end;


function ReadSubcatchmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := TSubcatch.Create;
    SubcatchList.AddObject(ID, S);
    S.X := MISSING;
    S.Y := MISSING;
    S.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[SUBCATCH].Data, S.Data);
    S.Data[SUBCATCH_RAINGAGE_INDEX] := TokList[1];
    S.Data[SUBCATCH_OUTLET_INDEX]   := TokList[2];
    S.Data[SUBCATCH_AREA_INDEX]     := TokList[3];
    S.Data[SUBCATCH_IMPERV_INDEX]   := TokList[4];
    S.Data[SUBCATCH_WIDTH_INDEX]    := TokList[5];
    S.Data[SUBCATCH_SLOPE_INDEX]    := TokList[6];
    S.Data[SUBCATCH_CURBLENGTH_INDEX] := TokList[7];
    if Ntoks >= 9
      then S.Data[SUBCATCH_SNOWPACK_INDEX] := TokList[8];
    S.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[SUBCATCH].AddObject(ID, S);
    Project.HasItems[SUBCATCH] := True;
    Result := 0;
  end;
end;


function ReadSubareaData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment sub-area data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := FindSubcatch(ID);
    if S = nil
    then Result := ErrMsg(SUBCATCH_ERR, ID)
    else begin
      S.Data[SUBCATCH_IMPERV_N_INDEX]  := TokList[1];
      S.Data[SUBCATCH_PERV_N_INDEX]    := TokList[2];
      S.Data[SUBCATCH_IMPERV_DS_INDEX] := TokList[3];
      S.Data[SUBCATCH_PERV_DS_INDEX]   := TokList[4];
      S.Data[SUBCATCH_PCTZERO_INDEX]   := TokList[5];
      S.Data[SUBCATCH_ROUTE_TO_INDEX]  := TokList[6];
      if Ntoks >= 8
      then S.Data[SUBCATCH_PCT_ROUTED_INDEX] := TokList[7];
      Result := 0;
    end;
  end;
end;


function ReadInfiltrationData: Integer;
//-----------------------------------------------------------------------------
// Reads subcatchment infiltration data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : TSubcatch;
  ID    : String;
  J     : Integer;
  Jmax  : Integer;
begin
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else begin
    Jmax := Ntoks-1;
    if Jmax > MAXINFILPROPS then Jmax := MAXINFILPROPS;
    for J := 1 to Jmax do S.InfilData[J-1] := TokList[J];
    Result := 0;
  end;
end;


function ReadAquiferData: Integer;
//-----------------------------------------------------------------------------
//  Reads aquifer data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  A  : TAquifer;
  I  : Integer;
begin
  if nToks < MAXAQUIFERPROPS then Result := ErrMsg(ITEMS_ERR, '')              //(5.1.003)
  else begin
    ID := TokList[0];
    A := TAquifer.Create;
    //Uutils.CopyStringArray(Project.DefProp[AQUIFER].Data, A.Data);           //(5.1.004)
    Project.Lists[AQUIFER].AddObject(ID, A);
    Project.HasItems[AQUIFER] := True;
    for I := 0 to MAXAQUIFERPROPS-1 do A.Data[I] := TokList[I+1];              //(5.1.003)
    if nToks >= MAXAQUIFERPROPS + 2                                            //(5.1.004)
    then A.Data[MAXAQUIFERPROPS] := TokList[MAXAQUIFERPROPS+1]                 //(5.1.003)
    else A.Data[MAXAQUIFERPROPS] := '';                                        //(5.1.003)
    Result := 0;
  end;
end;


function ReadGroundwaterData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment groundwater data from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  ID: String;
  P:  String;
  J:  Integer;
  K:  Integer;

begin
  // Get subcatchment name
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else
  begin

    // Line contains GW flow parameters
    if Ntoks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else begin
      S.Groundwater.Clear;

      // Read required parameters
      for J := 1 to 9 do S.Groundwater.Add(TokList[J]);

      // Read optional parameters
      for K := 10 to 13 do
      begin
        if Ntoks > K then
        begin
          P := TokList[K];
          if P = '*' then P := '';
          S.Groundwater.Add(P);
        end
        else S.Groundwater.Add('');
      end;
      S.Data[SUBCATCH_GWATER_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;

////  This function was re-written for release 5.1.007.  ////                  //(5.1.007)
function ReadGroundwaterFlowEqn(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads GW flow math expression from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  N:  Integer;
begin
  // Check for enough tokens in line
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
  // Get subcatchment object referred to by name
  else begin
    Result := 0;
    S := FindSubcatch(TokList[0]);
    if S = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
    else begin
      // Find position in Line where second token ends
      N := Pos(TokList[1], Line) + Length(TokList[1]);
      // Save remainder of line to correct type of GW flow equation
      if SameText(TokList[1], 'LATERAL') then
        S.GwLatFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else if SameText(TokList[1], 'DEEP') then
        S.GwDeepFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;
  end;
end;

function ReadSnowpackData: Integer;
//-----------------------------------------------------------------------------
//  Reads snowpack data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  P  : TSnowpack;
  I  : Integer;
  J  : Integer;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[1], SnowpackOptions, 7);
    if I < 0
    then Result := ErrMsg(KEYWORD_ERR, TokList[1])
    else begin

      // Check if snow pack ID is same as for previous line
      ID := TokList[0];
      if (ID = PrevID)
      then Index := PrevIndex
      else Index := Project.Lists[SNOWPACK].IndexOf(ID);

      // If starting input for a new snow pack then create it
      if Index < 0 then
      begin
        P := TSnowpack.Create;
        Project.Lists[SNOWPACK].AddObject(ID, P);
        Project.HasItems[SNOWPACK] := True;
        Index := Project.Lists[SNOWPACK].Count - 1;
        PrevID := ID;
        PrevIndex := Index;
      end
      else P := TSnowpack(Project.Lists[SNOWPACK].Objects[Index]);

      // Parse line depending on data type
      case I of

      // Plowable area
      0:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              for J := 1 to 6 do P.Data[1][J] := TokList[J+1];
              P.FracPlowable := TokList[8];
            end;
          end;

      // Impervious or Pervious area
      1,
      2:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else for J := 1 to 7 do P.Data[I+1][J] := TokList[J+1];
          end;

      // Plowing parameters
      3:  begin
            for J := 1 to 6 do P.Plowing[J] := TokList[J+1];
            if Ntoks >= 9 then P.Plowing[7] := TokList[8];
          end;
      end;
    end;
  end;
end;


function ReadJunctionData: Integer;
//-----------------------------------------------------------------------------
//  Reads junction data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := JUNCTION;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[JUNCTION].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    if Ntoks > 2 then  aNode.Data[JUNCTION_MAX_DEPTH_INDEX]       := TokList[2];
    if Ntoks > 3 then  aNode.Data[JUNCTION_INIT_DEPTH_INDEX]      := TokList[3];
    if Ntoks > 4 then  aNode.Data[JUNCTION_SURCHARGE_DEPTH_INDEX] := TokList[4];
    if Ntoks > 5 then  aNode.Data[JUNCTION_PONDED_AREA_INDEX]     := TokList[5];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[JUNCTION].AddObject(ID, aNode);
    Project.HasItems[JUNCTION] := True;
    Result := 0;
  end;
end;


function ReadOutfallData: Integer;
//-----------------------------------------------------------------------------
//  Reads outfall data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  I    : Integer;
  N    : Integer;                                                              //(5.1.008)
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    N := 4;                                                                    //(5.1.008)
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := OUTFALL;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[OUTFALL].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    I := Uutils.FindKeyWord(TokList[2], OutfallOptions, 4);
    if I < 0 then I := FREE_OUTFALL;
    if (I > NORMAL_OUTFALL) and (Ntoks >= 4) then
    begin
      case I of
      FIXED_OUTFALL:      aNode.Data[OUTFALL_FIXED_STAGE_INDEX] := TokList[3];
      TIDAL_OUTFALL:      aNode.Data[OUTFALL_TIDE_TABLE_INDEX] := TokList[3];
      TIMESERIES_OUTFALL: aNode.Data[OUTFALL_TIME_SERIES_INDEX] := TokList[3];
      end;
      N := 5;
      if Ntoks >= 5 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[4];    // (5.1.008)
    end
    else if Ntoks >= 4 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[3]; //(5.1.008)
    if Ntoks > N then aNode.Data[OUTFALL_ROUTETO_INDEX] := TokList[N];         //(5.1.008)
    aNode.Data[OUTFALL_TYPE_INDEX] := OutfallOptions[I];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[OUTFALL].AddObject(ID, aNode);
    Project.HasItems[OUTFALL] := True;
    Result := 0;
  end;
end;

////  The following function was modified for release 5.1.007.  ////           //(5.1.007)

function ReadStorageData: Integer;
//-----------------------------------------------------------------------------
//  Reads storage unit data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  N    : Integer;
  X    : Single;
begin
  Result := 0;
  N := 6;
  if Ntoks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := STORAGE;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[STORAGE].Data, aNode.Data);
    Project.Lists[STORAGE].AddObject(ID, aNode);
    aNode.Data[NODE_INVERT_INDEX]        := TokList[1];
    aNode.Data[STORAGE_MAX_DEPTH_INDEX]  := TokList[2];
    aNode.Data[STORAGE_INIT_DEPTH_INDEX] := TokList[3];
    aNode.Data[STORAGE_GEOMETRY_INDEX]   := TokList[4];
    if CompareText(TokList[4], 'TABULAR') = 0 then
    begin
      aNode.Data[STORAGE_ATABLE_INDEX] := TokList[5];
    end
    else begin
      if Ntoks < 7
      then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        aNode.Data[STORAGE_ACOEFF_INDEX] := TokList[5];
        aNode.Data[STORAGE_AEXPON_INDEX] := TokList[6];
        if Ntoks >= 8 then aNode.Data[STORAGE_ACONST_INDEX] := TokList[7];
        N := 8;
      end;
    end;

    // Optional items
    if (Result = 0) and (Ntoks > N) then
    begin
      // Ponded area
      aNode.Data[STORAGE_PONDED_AREA_INDEX] := TokList[N];
      // Evaporation factor
      if Ntoks > N+1 then aNode.Data[STORAGE_EVAP_FACTOR_INDEX] := TokList[N+1];
      // Constant seepage rate
      if Ntoks = N+3 then
      begin
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+2];
      end
      // Green-Ampt seepage parameters
      else if Ntoks = N+5 then
      begin
        aNode.InfilData[STORAGE_SUCTION_INDEX] := TokList[N+2];
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+3];
        aNode.InfilData[STORAGE_IMDMAX_INDEX] := TokList[N+4];
      end;
    end;
    Uutils.GetSingle(aNode.InfilData[STORAGE_KSAT_INDEX ], X);
    if (X > 0) then aNode.Data[STORAGE_SEEPAGE_INDEX] := 'YES'
    else aNode.Data[STORAGE_SEEPAGE_INDEX] := 'NO';
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.HasItems[STORAGE] := True;
  end;
end;


function ReadDividerData: Integer;
//-----------------------------------------------------------------------------
//  Reads flow divider data from a line of input.
//  (Corrected on 6/14/05)
//-----------------------------------------------------------------------------
var
  N, J : Integer;
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Result := 0;
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := DIVIDER;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[DIVIDER].Data, aNode.Data);
    Project.Lists[DIVIDER].AddObject(ID, aNode);
    Project.HasItems[DIVIDER] := True;
    aNode.Data[COMMENT_INDEX ] := Comment;
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    aNode.Data[DIVIDER_LINK_INDEX] := TokList[2];
    aNode.Data[DIVIDER_TYPE_INDEX] := TokList[3];
    N := 5;
    if SameText(TokList[3], 'OVERFLOW') then
    begin
      N := 4;
    end
    else if SameText(TokList[3], 'CUTOFF') then
    begin
      aNode.Data[DIVIDER_CUTOFF_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'TABULAR') then
    begin
      aNode.Data[DIVIDER_TABLE_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'WEIR') and (Ntoks >= 7) then
    begin
      aNode.Data[DIVIDER_QMIN_INDEX]   := TokList[4];
      aNode.Data[DIVIDER_DMAX_INDEX]   := TokList[5];
      aNode.Data[DIVIDER_QCOEFF_INDEX] := TokList[6];
      N := 7;
    end
    else Result := ErrMsg(KEYWORD_ERR, TokList[3]);
    if (Result = 0) and (Ntoks > N) then
    begin
      for J := N to Ntoks-1 do
        aNode.Data[DIVIDER_MAX_DEPTH_INDEX + J - N] := TokList[J];
    end;
  end;
end;


function ReadConduitData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := CONDUIT;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[CONDUIT].Data, aLink.Data);
      Project.Lists[CONDUIT].AddObject(ID, aLink);
      Project.HasItems[CONDUIT] := True;
      aLink.Data[CONDUIT_LENGTH_INDEX]     := TokList[3];
      aLink.Data[CONDUIT_ROUGHNESS_INDEX]  := TokList[4];
      aLink.Data[CONDUIT_INLET_HT_INDEX]   := TokList[5];
      aLink.Data[CONDUIT_OUTLET_HT_INDEX]  := TokList[6];
      if Ntoks > 7 then aLink.Data[CONDUIT_INIT_FLOW_INDEX] := TokList[7];
      if Ntoks > 8 then aLink.Data[CONDUIT_MAX_FLOW_INDEX] := TokList[8];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadPumpData: Integer;
//-----------------------------------------------------------------------------
//  Reads pump data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := PUMP;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[PUMP].Data, aLink.Data);
      Project.Lists[PUMP].AddObject(ID, aLink);
      Project.HasItems[PUMP] := True;

      // Skip over PumpType if line has old format
      if Uutils.FindKeyWord(TokList[3], PumpTypes, 5) >= 0 then N := 4
      else N := 3;
      if Ntoks <= N then Result := ErrMsg(ITEMS_ERR, '')
      else
      begin
        aLink.Data[PUMP_CURVE_INDEX] := TokList[N];
        if nToks > N+1 then aLink.Data[PUMP_STATUS_INDEX] := TokList[N+1];
        if nToks > N+2 then aLink.Data[PUMP_STARTUP_INDEX] := TokList[N+2];
        if nToks > N+3 then aLink.Data[PUMP_SHUTOFF_INDEX] := TokList[N+3];
        aLink.Data[COMMENT_INDEX ] := Comment;
        Result := 0;
      end;
    end;
  end;
end;


function ReadOrificeData: Integer;
//-----------------------------------------------------------------------------
//  Reads orifice data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := ORIFICE;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[ORIFICE].Data, aLink.Data);
      Project.Lists[ORIFICE].AddObject(ID, aLink);
      Project.HasItems[ORIFICE] := True;
      aLink.Data[ORIFICE_TYPE_INDEX]      := TokList[3];
      aLink.Data[ORIFICE_BOTTOM_HT_INDEX] := TokList[4];
      aLink.Data[ORIFICE_COEFF_INDEX]     := TokList[5];
      if nToks >= 7
      then aLink.Data[ORIFICE_FLAPGATE_INDEX] := TokList[6];
      if nToks >= 8
      then aLink.Data[ORIFICE_ORATE_INDEX] := TokList[7];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadWeirData: Integer;
//-----------------------------------------------------------------------------
//  Reads weir data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := WEIR;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[WEIR].Data, aLink.Data);
      Project.Lists[WEIR].AddObject(ID, aLink);
      Project.HasItems[WEIR] := True;
      aLink.Data[WEIR_TYPE_INDEX]  := TokList[3];
      aLink.Data[WEIR_CREST_INDEX] := TokList[4];
      aLink.Data[WEIR_COEFF_INDEX] := TokList[5];

////  Following section modified for release 5.1.007.  ////                    //(5.1.007)
      if (nToks >= 7) and not SameText(TokList[6], '*') then
        aLink.Data[WEIR_FLAPGATE_INDEX] := TokList[6];
      if (nToks >= 8) and not SameText(TokList[7], '*') then
        aLink.Data[WEIR_CONTRACT_INDEX] := TokList[7];
      if (nToks >= 9) and not SameText(TokList[8], '*') then
        aLink.Data[WEIR_END_COEFF_INDEX] := TokList[8];
      if (nToks >= 10) and not SameText(TokList[9], '*') then
        aLink.Data[WEIR_SURCHARGE_INDEX] := TokList[9];

////  Following section added for release 5.1.010.                             //(5.1.010)
      if (nToks >= 11) and not SameText(TokList[10], '*') then
        aLink.Data[WEIR_ROAD_WIDTH_INDEX] := TokList[10];
      if (nToks >= 12) and not SameText(TokList[11], '*') then
        aLink.Data[WEIR_ROAD_SURF_INDEX] := TokList[11];
////

      aLink.Data[COMMENT_INDEX] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadOutletData: Integer;
//-----------------------------------------------------------------------------
//  Reads outlet data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := OUTLET;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[OUTLET].Data, aLink.Data);
      Project.Lists[OUTLET].AddObject(ID, aLink);
      Project.HasItems[OUTLET] := True;
      aLink.Data[OUTLET_CREST_INDEX] := TokList[3];

      //... added for backwards compatibility
      if SameText(TokList[4], 'TABULAR') then TokList[4] := 'TABULAR/DEPTH';
      if SameText(TokList[4], 'FUNCTIONAL') then TokList[4] := 'FUNCTIONAL/DEPTH';
      aLink.Data[OUTLET_TYPE_INDEX]  := TokList[4];

      if AnsiContainsText(TokList[4], 'TABULAR') then
      begin
        aLink.Data[OUTLET_QTABLE_INDEX] := TokList[5];
        N := 6;
      end
      else begin
        if Ntoks < 7 then
        begin
          Result := ErrMsg(ITEMS_ERR, '');
          Exit;
        end
        else
        begin
          aLink.Data[OUTLET_QCOEFF_INDEX] := TokList[5];
          aLink.Data[OUTLET_QEXPON_INDEX] := TokList[6];
          N := 7;
        end;
      end;
      if Ntoks > N then aLink.Data[OUTLET_FLAPGATE_INDEX] := TokList[N];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function GetXsectShape(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Finds the code number corresponding to cross section shape S.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  for I := 0 to High(Dxsect.XsectShapes) do
  begin
    if CompareText(S, Dxsect.XsectShapes[I].Text[1]) = 0 then
    begin
      Result := I;
      Exit;
    end;
  end;
  Result := -1;
end;

////  New procedure added to release 5.1.008.  ////                            //(5.1.008)
procedure CheckForStdSize;
//-----------------------------------------------------------------------------
//  Converts from old format for standard size ellipse and arch pipes
//  to new format.
//-----------------------------------------------------------------------------
var
  J: Integer;
  X: Extended;
begin
// Old format had size code as 3rd token and 0 for 4th token
  J := StrToIntDef(TokList[2], 0);
  Uutils.GetExtended(TokList[3], X);
  if (J > 0) and (X = 0) then
  begin
  // New format has 5th token as size code
    TokList[4] := TokList[2];
    TokList[2] := '0';
    TokList[3] := '0';
  end;
end;

function ReadXsectionData: Integer;
//-----------------------------------------------------------------------------
//  Reads cross section data froma line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  ID    : String;
  aLink : TLink;
begin
  if nToks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil)
    then Result := ErrMsg(LINK_ERR, TokList[0])
    else begin
      I := GetXsectShape(TokList[1]);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else case aLink.Ltype of

      CONDUIT:
        begin
          aLink.Data[CONDUIT_SHAPE_INDEX] := TokList[1];
          if I = Dxsect.IRREG_SHAPE_INDEX then
          begin
            aLink.Data[CONDUIT_TSECT_INDEX] := TokList[2];
            aLink.Data[CONDUIT_GEOM1_INDEX] := '';                             //(5.1.008)
            Result := 0;
          end

          else begin
            if nToks < 6 then Result := ErrMsg(ITEMS_ERR, '')
            else begin
{
////  Added to release 5.1.008.  ////                                          //(5.1.008)
              if I in [Dxsect.HORIZ_ELLIPSE_SHAPE_INDEX,
                       Dxsect.VERT_ELLIPSE_SHAPE_INDEX,
                       Dxsect.ARCH_SHAPE_INDEX]
              then CheckForStdSize;
}
              aLink.Data[CONDUIT_GEOM1_INDEX] := TokList[2];
              aLink.Data[CONDUIT_GEOM2_INDEX] := TokList[3];
              aLink.Data[CONDUIT_GEOM3_INDEX] := TokList[4];
              aLink.Data[CONDUIT_GEOM4_INDEX] := TokList[5];
              if Ntoks > 6 then aLink.Data[CONDUIT_BARRELS_INDEX] := TokList[6];
              if Ntoks > 7 then aLink.Data[CONDUIT_CULVERT_INDEX] := TokList[7];
              if I = Dxsect.CUSTOM_SHAPE_INDEX then
                aLink.Data[CONDUIT_TSECT_INDEX] := TokList[3];
              Result := 0;
            end;
          end;
        end;

      ORIFICE:
        begin
          if not I in [0, 1] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[ORIFICE_SHAPE_INDEX]  := TokList[1];
            aLink.Data[ORIFICE_HEIGHT_INDEX] := TokList[2];
            aLink.Data[ORIFICE_WIDTH_INDEX]  := TokList[3];
            Result := 0;
          end;
        end;

      WEIR:
        begin
          if not I in [2, 3, 4] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[WEIR_SHAPE_INDEX]  := TokList[1];
            aLink.Data[WEIR_HEIGHT_INDEX] := TokList[2];
            aLink.Data[WEIR_WIDTH_INDEX]  := TokList[3];
            aLink.Data[WEIR_SLOPE_INDEX]  := TokList[4];
            Result := 0;
          end;
        end;

      else Result := 0;
      end;
    end;
  end;
end;


function ReadTransectData: Integer;
//-----------------------------------------------------------------------------
//  Reads transect data from a line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  K     : Integer;
  N     : Integer;
  ID    : String;
  Tsect : TTransect;
begin
  Result := 0;
  if SameText(TokList[0], 'NC') then
  begin
    if nToks < 4 then Result := ErrMsg(ITEMS_ERR, '')
    else for I := 1 to 3 do ManningsN[I] := TokList[I];
    TsectComment := Comment;
    Exit;
  end;

  if SameText(TokList[0], 'X1') then
  begin
    if nToks < 2 then Exit;
    ID := TokList[1];
    Tsect := TTransect.Create;

    if Length(Comment) > 0 then TsectComment := Comment;
    Tsect.Comment := TsectComment;

    Project.Lists[TRANSECT].AddObject(ID, Tsect);
    Project.HasItems[TRANSECT] := True;
    Tsect.Data[TRANSECT_N_LEFT]    := ManningsN[1];
    Tsect.Data[TRANSECT_N_RIGHT]   := ManningsN[2];
    Tsect.Data[TRANSECT_N_CHANNEL] := ManningsN[3];
    if nToks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      Tsect.Data[TRANSECT_X_LEFT] := TokList[3];
      Tsect.Data[TRANSECT_X_RIGHT] := TokList[4];
      Tsect.Data[TRANSECT_L_FACTOR] := TokList[7];
      Tsect.Data[TRANSECT_X_FACTOR] := TokList[8];
      Tsect.Data[TRANSECT_Y_FACTOR] := TokList[9];
    end;
    Exit;
  end;

  if SameText(TokList[0], 'GR') then
  begin
    N := Project.Lists[TRANSECT].Count;
    if N = 0 then Result := ErrMsg(TRANSECT_ERR, '')
    else begin
      Tsect := TTransect(Project.Lists[TRANSECT].Objects[N-1]);
      K := 1;
      while K + 1 < Ntoks do
      begin
        Tsect.Ydata.Add(TokList[K]);
        Tsect.Xdata.Add(TokList[K+1]);
        K := K + 2;
      end;
    end;
    Exit;
  end;
end;


function ReadLossData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit loss data from a line of input.
//-----------------------------------------------------------------------------
var
  ID    : String;
  aLink : TLink;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil) then Result := ErrMsg(LINK_ERR, ID)
    else if (aLink.Ltype <> CONDUIT) then Result := 0
    else begin
      aLink.Data[CONDUIT_ENTRY_LOSS_INDEX] := TokList[1];
      aLink.Data[CONDUIT_EXIT_LOSS_INDEX] := TokList[2];
      aLink.Data[CONDUIT_AVG_LOSS_INDEX] := TokList[3];
      if nToks >= 5
      then aLink.Data[CONDUIT_CHECK_VALVE_INDEX] := TokList[4];
      if nToks >= 6
      then aLink.Data[CONDUIT_SEEPAGE_INDEX] := TokList[5];
      Result := 0;
    end;
  end;
end;


function ReadPollutantData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant data from a line of input.
//-----------------------------------------------------------------------------
var
  ID      : String;
  aPollut : TPollutant;
  X       : Single;
begin
  if nToks < 5
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Add new pollutant to project
    ID := TokList[0];
    aPollut := TPollutant.Create;
    Uutils.CopyStringArray(Project.DefProp[POLLUTANT].Data, aPollut.Data);
    Project.Lists[POLLUTANT].AddObject(ID, aPollut);
    Project.HasItems[POLLUTANT] := True;

    // Parse units & concens.
    aPollut.Data[POLLUT_UNITS_INDEX] := TokList[1];
    aPollut.Data[POLLUT_RAIN_INDEX]  := TokList[2];
    aPollut.Data[POLLUT_GW_INDEX]    := TokList[3];

    // This is for old format
    if (Ntoks = 5)
    or ( (Ntoks = 7) and Uutils.GetSingle(TokList[6], X) ) then
    begin
      aPollut.Data[POLLUT_DECAY_INDEX] := TokList[4];
      if nToks >= 7 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[5];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[6];
      end;
    end

    // This is for new format
    else
    begin
      aPollut.Data[POLLUT_II_INDEX] := TokList[4];
      if Ntoks >= 6 then aPollut.Data[POLLUT_DECAY_INDEX] := TokList[5];
      if nToks >= 7 then aPollut.Data[POLLUT_SNOW_INDEX]  := TokList[6];
      if Ntoks >= 9 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[7];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[8];
      end;
      if Ntoks >= 10 then aPollut.Data[POLLUT_DWF_INDEX] := TokList[9];
      if Ntoks >= 11 then aPollut.Data[POLLUT_INIT_INDEX] := TokList[10];
    end;
    Result := 0;
  end;
end;


function ReadLanduseData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use data from a line of input.
//-----------------------------------------------------------------------------
var
  ID           : String;
  aLanduse     : TLanduse;
  aNonPtSource : TNonpointSource;
  J            : Integer;
begin
    ID := TokList[0];
    aLanduse := TLanduse.Create;
    for J := 0 to Project.Lists[POLLUTANT].Count - 1 do
    begin
      aNonPtSource := TNonpointSource.Create;
      aLanduse.NonpointSources.AddObject(Project.Lists[POLLUTANT].Strings[J],
        aNonPtSource);
    end;
    Project.Lists[LANDUSE].AddObject(ID, aLanduse);
    Project.HasItems[LANDUSE] := True;
    if Ntoks > 1 then aLanduse.Data[LANDUSE_CLEANING_INDEX]  := TokList[1];
    if Ntoks > 2 then aLanduse.Data[LANDUSE_AVAILABLE_INDEX] := TokList[2];
    if Ntoks > 3 then aLanduse.Data[LANDUSE_LASTCLEAN_INDEX] := TokList[3];
    aLanduse.Data[COMMENT_INDEX ] := Comment;
    Result := 0;
end;


function ReadBuildupData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant buildup function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.BuildupData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadWashoffData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant washoff function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.WashoffData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadCoverageData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use coverage data from a line of input.
//-----------------------------------------------------------------------------
var
  MaxToks: Integer;
  X      : Single;
  S      : TSubcatch;
  S1     : String;
  I      : Integer;
begin
  Result := 0;
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else begin
    MaxToks := 3;
    while (MaxToks <= nToks) do
    begin
      if not Uutils.GetSingle(TokList[MaxToks-1], X) then
      begin
        Result := ErrMsg(NUMBER_ERR, TokList[MaxToks-1]);
        break;
      end;
      S1 := TokList[MaxToks-2];
      I := S.LandUses.IndexOfName(S1);
      S1 := TokList[MaxToks-2] + '=' + TokList[MaxToks-1];
      if I < 0
      then S.LandUses.Add(S1)
      else S.Landuses[I] := S1;
      MaxToks := MaxToks + 2;
    end;
    S.Data[SUBCATCH_LANDUSE_INDEX] := IntToStr(S.LandUses.Count);
  end;
end;


function ReadTreatmentData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant treatment function from a line of input.
//-----------------------------------------------------------------------------
var
  S     : String;
  aNode : TNode;
  I     : Integer;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0 then
      Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      I := aNode.Treatment.IndexOfName(TokList[1]);
      S := Copy(Line, Pos(TokList[1], Line)+Length(TokList[1]), Length(Line));
      S := TokList[1] + '=' + Trim(S);
      if I < 0 then
        aNode.Treatment.Add(S)
      else
        aNode.Treatment[I] := S;
      aNode.Data[NODE_TREAT_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadExInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads external inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : array[1..7] of String;
  Inflow: String;
  I     : Integer;
  aNode : TNode;
begin

  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S[1] := TokList[1];                // Constituent name
      S[2] := TokList[2];                // Time Series name
      S[3] := 'FLOW';
      S[4] := '1.0';
      if not SameText(S[1], 'FLOW') then
      begin
        if nToks >= 4 then S[3] := TokList[3] else S[3] := 'CONCEN';
        if nToks >= 5 then S[4] := TokList[4] else S[4] := '1.0';
      end;
      if nToks >= 6 then S[5] := TokList[5] else S[5] := '1.0';
      if nToks >= 7 then S[6] := TokList[6] else S[6] := '';
      if nToks >= 8 then S[7] := TokList[7] else S[7] := '';
      Inflow := S[1] + '=' + S[2] + #13 + S[3] + #13 + S[4] + #13 +
                             S[5] + #13 + S[6] + #13 + S[7];
      I := aNode.DXInflow.IndexOfName(S[1]);
      if I < 0 then aNode.DXInflow.Add(Inflow)
      else aNode.DXInflow[I] := Inflow;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadDWInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads dry weather inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  M    : Integer;
  S    : String;
  S1   : String;
  I    : Integer;
  aNode: TNode;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S1 := TokList[1];
      S := S1 + '=' + TokList[2];
      for M := 3 to Ntoks-1 do S := S + #13 + TokList[M];
      I := aNode.DWInflow.IndexOfName(S1);
      if I < 0
      then aNode.DWInflow.Add(S)
      else aNode.DWInflow[I] := S;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadPatternData: Integer;
//-----------------------------------------------------------------------------
//  Reads time pattern data from a line of input.
//-----------------------------------------------------------------------------
var
  ID: String;
  Index: Integer;
  aPattern: TPattern;
  PatType: Integer;
  J: Integer;
begin
  if nToks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    J := 1;
    ID := TokList[0];
    Index := Project.Lists[PATTERN].IndexOf(ID);
    if Index < 0 then
    begin
      aPattern := TPattern.Create;
      aPattern.Comment := Comment;
      Project.Lists[PATTERN].AddObject(ID, aPattern);
      Project.HasItems[PATTERN] := True;
      PatType := Uutils.FindKeyWord(TokList[1], PatternTypes, 7);
      if PatType < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        exit;
      end;
      aPattern.PatternType := PatType;
      J := 2;
    end
    else aPattern := TPattern(Project.Lists[PATTERN].Objects[Index]);
    while (J < nToks) and (aPattern.Count <= High(aPattern.Data)) do
    begin
      aPattern.Data[aPattern.Count] := TokList[J];
      Inc(J);
      Inc(aPattern.Count);
    end;
    Result := 0;
  end;
end;


function ReadIIInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;

begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      aNode.IIInflow.Clear;
      aNode.IIInflow.Add(TokList[1]);
      aNode.IIInflow.Add(TokList[2]);
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadLoadData: Integer;
//-----------------------------------------------------------------------------
//  Reads initial pollutant loading data from a line of input.
//-----------------------------------------------------------------------------
var
  X  : Single;
  S  : TSubcatch;
  S1 : String;
  I  : Integer;

begin
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0
  then Result := ErrMsg(POLLUT_ERR, TokList[1])
  else if not Uutils.GetSingle(TokList[2], X)
  then Result := ErrMsg(NUMBER_ERR, TokList[2])
  else
  begin
      S1 := TokList[1] + '=' + TokList[2];
      I := S.Loadings.IndexOfName(TokList[1]);
      if I < 0
      then S.Loadings.Add(S1)
      else S.Loadings[I] := S1;
      S.Data[SUBCATCH_LOADING_INDEX] := 'YES';
      Result := 0;
  end;
end;


function ReadCurveData: Integer;
//-----------------------------------------------------------------------------
//  Reads curve data from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  K       : Integer;
  L       : Integer;
  M       : Integer;
  ObjType : Integer;
  ID      : String;
  aCurve  : TCurve;
begin
  // Check for too few tokens
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Check if curve ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID) then
    begin
      Index := PrevIndex;
      ObjType := CurveType;
    end
    else Project.FindCurve(ID, ObjType, Index);

    // Create new curve if ID not in data base
    K := 2;
    if Index < 0 then
    begin

      // Check for valid curve type keyword
      M := -1;
      for L := 0 to High(CurveTypeOptions) do
      begin
        if SameText(TokList[1], CurveTypeOptions[L]) then
        begin
          M := L;
          break;
        end;
      end;
      if M < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        Exit;
      end;

      // Convert curve type keyword index to a curve object category
      case M of
      0: ObjType := CONTROLCURVE;
      1: ObjType := DIVERSIONCURVE;
      2..5:
         ObjType := PUMPCURVE;
      6: ObjType := RATINGCURVE;
      7: ObjType := SHAPECURVE;
      8: ObjType := STORAGECURVE;
      9: ObjType := TIDALCURVE;
      end;

      // Create a new curve object
      aCurve := TCurve.Create;
      aCurve.Comment := Comment;
      aCurve.CurveType := TokList[1];
      if ObjType = PUMPCURVE
      then aCurve.CurveCode := M - 1
      else aCurve.CurveCode := 0;
      Project.Lists[ObjType].AddObject(ID, aCurve);
      Project.HasItems[ObjType] := True;
      Index := Project.Lists[ObjType].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
      CurveType := ObjType;
      K := 3;
    end;

    // Add x,y values to the list maintained by the curve
    aCurve := TCurve(Project.Lists[ObjType].Objects[Index]);
    while K <= nToks-1 do
    begin
      aCurve.Xdata.Add(TokList[K-1]);
      aCurve.Ydata.Add(TokList[K]);
      K := K + 2;
    end;
    Result := 0;
  end;
end;


function ReadTimeseriesData: Integer;
//-----------------------------------------------------------------------------
//  Reads time series data from a line of input.
//-----------------------------------------------------------------------------
const
  NEEDS_DATE = 1;
  NEEDS_TIME = 2;
  NEEDS_VALUE = 3;
var
  Index     : Integer;
  State     : Integer;
  K         : Integer;
  ID        : String;
  StrDate   : String;
  aTseries  : TTimeseries;
begin
  // Check for too few tokens
  Result := -1;
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '');

  // Check if series ID is same as for previous line
  ID := TokList[0];
  if (ID = PrevID) then Index := PrevIndex
  else Index := Project.Lists[TIMESERIES].IndexOf(ID);

  // If starting input for a new series then create it
  if Index < 0 then
  begin
    aTseries := TTimeseries.Create;
    aTseries.Comment := Comment;
    Project.Lists[TIMESERIES].AddObject(ID,aTseries);
    Project.HasItems[TIMESERIES] := True;
    Index := Project.Lists[TIMESERIES].Count - 1;
    PrevID := ID;
    PrevIndex := Index;
  end;
  aTseries := TTimeseries(Project.Lists[TIMESERIES].Objects[Index]);

  // Check if external file name used
  if SameText(TokList[1], 'FILE') then
  begin
    aTseries.Filename := TokList[2];
    Result := 0;
    Exit;
  end;

  // Add values to the list maintained by the timeseries
  State := NEEDS_DATE;
  K := 1;
  while K < nToks do
  begin
    case State of

    NEEDS_DATE:
      begin
        try
          StrDate := Uutils.ConvertDate(TokList[K]);
          StrToDate(StrDate, MyFormatSettings);
          aTseries.Dates.Add(StrDate);
          Inc(K);
          if K >= nToks then break;
        except
          On EconvertError do aTseries.Dates.Add('');
        end;
        State := NEEDS_TIME;
      end;

    NEEDS_TIME:
      begin
        aTseries.Times.Add(TokList[K]);
        Inc(K);
        if K >= nToks then break;
        State := NEEDS_VALUE;
      end;

    NEEDS_VALUE:
      begin
        aTseries.Values.Add(TokList[K]);
        Result := 0;
        State := NEEDS_DATE;
        Inc(K);
      end;
    end;
  end;
  if Result = -1 then Result := ErrMsg(ITEMS_ERR, '');
end;


function ReadControlData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads a control rule statement from line of input.
//-----------------------------------------------------------------------------
begin
  Project.ControlRules.Add(Line);
  Result := 0;
end;


function ReadLidUsageData: Integer;
//-----------------------------------------------------------------------------
//  Reads LID usage data from line of input.
//-----------------------------------------------------------------------------
var
  aSubcatch: TSubcatch;
begin
  aSubcatch := FindSubcatch(TokList[0]);
  if aSubcatch = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else Result := Ulid.ReadLIDUsageData(aSubcatch, TokList, Ntoks);
end;


procedure ReadReportOption(Index: Integer);
begin
  if (Ntoks >= 2) and SameText(TokList[1], 'YES') then
    Project.Options.Data[Index] := 'YES'
  else
    Project.Options.Data[Index] := 'NO';
end;


function ReadReportData: Integer;
//-----------------------------------------------------------------------------
//  Reads reporting options from a line of input.
//-----------------------------------------------------------------------------
begin
  if SameText(TokList[0], 'CONTROLS')
  then ReadReportOption(REPORT_CONTROLS_INDEX)
  else if SameText(TokList[0], 'INPUT')
  then ReadReportOption(REPORT_INPUT_INDEX)
  else ReportingForm.Import(TokList, Ntoks);
  Result := 0;
end;


function ReadFileData: Integer;
//-----------------------------------------------------------------------------
//  Reads interface file usage from a line of input.
//-----------------------------------------------------------------------------
var
  Fname: String;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Fname := TokList[2];
    if ExtractFilePath(Fname) = ''
    then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
    Project.IfaceFiles.Add(TokList[0] + ' ' + TokList[1] + ' ' +
     '"' + Fname + '"');
    Result := 0;
  end;
end;


////  This function was added to release 5.1.007.  ////                        //(5.1.007)
function ReadAdjustmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads climate adjustments from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then exit;
  if SameText(TokList[0], 'Temperature') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then TempAdjust[I] := ''
        else TempAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Evaporation') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then EvapAdjust[I] := ''
        else EvapAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Rainfall') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then RainAdjust[I] := ''
        else RainAdjust[I] := TokList[I+1];
    end;
  end

////  Added to release 5.1.008.  ////                                          //(5.1.008)
  else if SameText(TokList[0], 'Conductivity') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then CondAdjust[I] := ''
        else CondAdjust[I] := TokList[I+1];
    end;
  end;
////////////////////////////////////////////////////////
end;


////  This function was added to release 5.1.011.  ////                        //(5.1.011)
function ReadEventData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads hydraulic event data from a line of input.
//-----------------------------------------------------------------------------
begin
  Result := 0;
  if Length(Line) = 0 then exit;
  if LeftStr(Line,2) = ';;' then exit;
  Project.Events.Add(Line);
end;


function ReadOptionData: Integer;
//-----------------------------------------------------------------------------
//  Reads an analysis option from a line of input.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Keyword : String;
  S : String;
  S2: String;
  I : Integer;
  X : Single;
  T : Extended;
begin
  // Check which keyword applies
  Result := 0;
  if Ntoks < 2 then exit;
  Keyword := TokList[0];
  if SameText(Keyword, 'TEMPDIR') then exit;
  Index := Uutils.FindKeyWord(Keyword, OptionLabels, 17);
  case Index of

    -1:  Result := ErrMsg(KEYWORD_ERR, Keyword);

    ROUTING_MODEL_INDEX:
    begin
      if SameText(TokList[1], 'NONE') then
      begin
         Project.Options.Data[IGNORE_ROUTING_INDEX] := 'YES';
         Exit;
      end;

      I := Uutils.FindKeyWord(TokList[1], OldRoutingOptions, 3);
      if I >= 0 then TokList[1] := RoutingOptions[I];
    end;

    START_DATE_INDEX, REPORT_START_DATE_INDEX, END_DATE_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      try
        StrToDate(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    START_TIME_INDEX, REPORT_START_TIME_INDEX, END_TIME_INDEX:
    begin
      S := TokList[1];
      try
        StrToTime(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    SWEEP_START_INDEX, SWEEP_END_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      S2 := S + '/1947';
      try
        StrToDate(S2, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    WET_STEP_INDEX, DRY_STEP_INDEX, REPORT_STEP_INDEX:
    begin
      S := TokList[1];
      if Uutils.StrHoursToTime(S) = -1 then Result := ErrMsg(TIMESTEP_ERR, '');
    end;

    ROUTING_STEP_INDEX:
    begin
      S := TokList[1];
      T := 0.0;
      if not Uutils.GetExtended(S, T) then
      begin
        T := Uutils.StrHoursToTime(S)*86400.;
        if T <= 0.0 then Result := ErrMsg(TIMESTEP_ERR, '')
        else TokList[1] := Format('%.0f',[T]);
      end;
    end;

    VARIABLE_STEP_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
        TokList[1] := IntToStr(Round(100.0*X))
      else
        TokList[1] := '0';
    end;

    INERTIAL_DAMPING_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
      begin
        if X = 0 then TokList[1] := 'NONE'
        else TokList[1] := 'PARTIAL';
      end;
    end;

    // This option is now fixed to SWMM 4.
    COMPATIBILITY_INDEX:
    begin
      TokList[1] := '4';
    end;

    MIN_ROUTE_STEP_INDEX,                                                      //(5.1.008)
    LENGTHEN_STEP_INDEX,
    MIN_SURFAREA_INDEX,
    MIN_SLOPE_INDEX,
    MAX_TRIALS_INDEX,
    HEAD_TOL_INDEX,
    SYS_FLOW_TOL_INDEX,
    LAT_FLOW_TOL_INDEX:
    begin
      Uutils.GetSingle(TokList[1], X);
      if X <= 0 then TokList[1] := '0';
    end;

    NORMAL_FLOW_LTD_INDEX:
    begin
      if SameText(TokList[1], 'NO') or SameText(TokList[1], 'SLOPE')
      then TokList[1] := 'SLOPE'
      else if SameText(TokList[1], 'YES') or SameText(TokList[1], 'FROUDE')
      then TokList[1] := 'FROUDE'
      else if SameText(TokList[1], 'BOTH') then TokList[1] := 'BOTH'
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

    FORCE_MAIN_EQN_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], ForceMainEqnOptions, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := ForceMainEqnOptions[I];
    end;

    LINK_OFFSETS_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], LinkOffsetsOptions, 10);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := LinkOffsetsOptions[I];
    end;

    IGNORE_RAINFALL_INDEX,
    IGNORE_SNOWMELT_INDEX,
    IGNORE_GRNDWTR_INDEX,
    IGNORE_ROUTING_INDEX,
    IGNORE_QUALITY_INDEX:
    begin
      if not SameText(TokList[1], 'YES') and not SameText(TokList[1], 'NO')
      then Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

////  Following section added to release 5.1.010.  ////                        //(5.1.010)
    NUM_THREADS_INDEX:
    begin
      I := StrToIntDef(TokList[1], 1);
      if I < 0 then I := 1;
      if (I = 0) or (I > Uutils.GetCPUs) then I := Uutils.GetCPUs;
      TokList[1] := IntToStr(I);
    end;
////

  end;
  if Result = 0 then Project.Options.Data[Index] := TokList[1];
end;


function ReadTagData: Integer;
//-----------------------------------------------------------------------------
//  Reads in tag data from a line of input.
//-----------------------------------------------------------------------------
const
  TagTypes: array[0..3] of PChar = ('Gage', 'Subcatch', 'Node', 'Link');
var
  I, J : Integer;
  aNode: TNode;
  aLink: TLink;
begin
  Result := 0;
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[0], TagTypes, 4);
    case I of

    -1: Result := ErrMsg(KEYWORD_ERR, TokList[0]);

     0: begin
          J := Project.Lists[RAINGAGE].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetGage(J) do Data[TAG_INDEX] := TokList[2];
        end;

     1: begin
          J := Project.Lists[SUBCATCH].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetSubcatch(SUBCATCH, J) do
            Data[TAG_INDEX] := TokList[2];
        end;

     2: begin
          aNode := FindNode(TokList[1]);
          if (aNode <> nil) then aNode.Data[TAG_INDEX] := TokList[2];
        end;

     3: begin
          aLink := FindLink(TokList[1]);
          if (aLink <> nil) then aLink.Data[TAG_INDEX] := TokList[2];
        end;
     end;
  end;
end;


function ReadSymbolData: Integer;
//-----------------------------------------------------------------------------
//  Reads rain gage coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  J     : Integer;
  X, Y  : Extended;
  aGage : TRaingage;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the gage ID in the database
    J := Project.Lists[RAINGAGE].IndexOf(TokList[0]);

    // If gage exists then assign it X & Y coordinates
    if (J >= 0) then
    begin
      aGage := Project.GetGage(J);
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
          Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aGage.X := X;
        aGage.Y := Y;
      end;
    end;
  end;
end;


function ReadMapData: Integer;
//-----------------------------------------------------------------------------
//  Reads map dimensions data from a line of input.
//-----------------------------------------------------------------------------
var
  I       : Integer;
  Index   : Integer;
  Keyword : String;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, MapWords, 4);
  case Index of

    0:  // Map dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for I := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Dimensions do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
          MapExtentSet := True;
        end;
      end;
    end;

    1:  //Map units
    if Ntoks > 1 then
    begin
      I := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
      with MapForm.Map.Dimensions do
      begin
        if Units = muDegrees then Digits := MAXDEGDIGITS
        else Digits := Umap.DefMapDimensions.Digits;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadCoordData: Integer;
//-----------------------------------------------------------------------------
//  Reads node coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aNode : TNode;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the node ID in the database
    aNode := FindNode(TokList[0]);

    // If node exists then assign it X & Y coordinates
    if (aNode <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aNode.X := X;
        aNode.Y := Y;
      end;
    end;
  end;
end;


function ReadVertexData: Integer;
//-----------------------------------------------------------------------------
//  Reads link vertex coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aLink : TLink;

begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the link ID in the database
    aLink := FindLink(TokList[0]);;

    // If link exists then assign it X & Y coordinates
    if (aLink <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  aLink.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadPolygonData: Integer;
//-----------------------------------------------------------------------------
//  Reads polygon coordinates associated with subcatchment outlines.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  X, Y  : Extended;
  S     : TSubcatch;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the subcatchment ID in the database
    S := nil;
    Index := Project.Lists[SUBCATCH].IndexOf(TokList[0]);
    if Index >= 0 then S := Project.GetSubcatch(SUBCATCH, Index);

    // If subcatchment exists then add a new vertex to it
    if (S <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  S.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadLabelData: Integer;
//-----------------------------------------------------------------------------
//  Reads map label data from a line of input.
//-----------------------------------------------------------------------------
var
  Ntype    : Integer;
  Index    : Integer;
  X, Y     : Extended;
  S        : String;
  aMapLabel: TMapLabel;
begin
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    if not Uutils.GetExtended(TokList[0], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[0])
    else if not Uutils.GetExtended(TokList[1], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
    else begin
      S := TokList[2];
      aMapLabel := TMapLabel.Create;
      aMapLabel.X := X;
      aMapLabel.Y := Y;
      Project.Lists[MAPLABEL].AddObject(S, aMapLabel);
      Index := Project.Lists[MAPLABEL].Count - 1;
      aMapLabel.Text := PChar(Project.Lists[MAPLABEL].Strings[Index]);
      Project.HasItems[MAPLABEL] := True;
      if Ntoks >= 4 then
      begin
        if (Length(TokList[3]) > 0) and
          Project.FindNode(TokList[3], Ntype, Index) then
            aMapLabel.Anchor := Project.GetNode(Ntype, Index);
      end;
      if Ntoks >= 5 then aMapLabel.FontName := TokList[4];
      if Ntoks >= 6 then aMapLabel.FontSize := StrToInt(TokList[5]);
      if Ntoks >= 7 then
        if StrToInt(TokList[6]) = 1 then aMapLabel.FontBold := True;
      if Ntoks >= 8 then
        if StrToInt(TokList[7]) = 1 then aMapLabel.FontItalic := True;
      Result := 0;
    end;
  end;
end;


function ReadBackdropData: Integer;
//-----------------------------------------------------------------------------
//  Reads map backdrop image information from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  Keyword : String;
  I       : Integer;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, BackdropWords, 4);
  case Index of

    0:  //Backdrop file
    if Ntoks > 1 then MapForm.Map.Backdrop.Filename := TokList[1];

    1:  // Backdrop dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for i := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Backdrop do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
        end;
      end;
    end;

    2:  //Map units - deprecated
    if Ntoks > 1 then
    begin
      i := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
    end;

    3, 4:  //Backdrop offset or scaling -- deprecated
    begin
      if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
      else if not Uutils.GetExtended(TokList[1], X[1]) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], X[2]) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        if Index = 3 then
        begin
          BackdropX := X[1];
          BackdropY := X[2];
        end
        else
        begin
          BackdropW := X[1];
          BackdropH := X[2];
        end;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadProfileData: Integer;
//-----------------------------------------------------------------------------
//  Reads profile plot data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
  S   : String;
begin
  Result := 0;
  if (Ntoks < 2) then Exit;

  // Locate the profile name in the database
  I := Project.ProfileNames.IndexOf(TokList[0]);

  // If profile does not exist then create it
  if (I < 0) then
  begin
    Project.ProfileNames.Add(TokList[0]);
    I := Project.ProfileNames.Count-1;
    S := '';
    Project.ProfileLinks.Add(S);
  end
  else S := Project.ProfileLinks[I] + #13;

  // Add each remaining token to the list of links in the profile
  S := S + TokList[1];
  for J := 2 to Ntoks-1 do
    S := S + #13 + TokList[J];
  Project.ProfileLinks[I] := S;
end;


procedure SetMapDimensions;
//-----------------------------------------------------------------------------
// Determines map dimensions based on range of object coordinates.
//-----------------------------------------------------------------------------
begin
  with MapForm.Map do
  begin
    // Dimensions not provided in [MAP] section
    if not MapExtentSet then
    begin

      // Dimensions were provided in [BACKDROP] section
      if (Backdrop.LowerLeft.X <> Backdrop.UpperRight.X) then
      begin

        // Interpret these as map dimensions
        Dimensions.LowerLeft  := Backdrop.LowerLeft;
        Dimensions.UpperRight := Backdrop.UpperRight;

        // Compute backdrop dimensions from Offset and Scale values
        Backdrop.LowerLeft.X  := BackdropX;
        Backdrop.LowerLeft.Y  := BackdropY - BackdropH;
        Backdrop.UpperRight.X := BackdropX + BackdropW;
        Backdrop.UpperRight.Y := BackdropY;
      end

      // No dimensions of any kind provided
      else with Dimensions do
        Ucoords.GetCoordExtents(LowerLeft.X, LowerLeft.Y,
                                UpperRight.X, UpperRight.Y);
    end;
  end;
end;


function ParseInpLine(S: String): Integer;
//-----------------------------------------------------------------------------
//  Parses current input line depending on current section of input file.
//-----------------------------------------------------------------------------
begin
  case Section of
    1:    Result := ReadOptionData;
    2:    Result := ReadRaingageData;
    3:    Result := ReadHydrographData;
    4:    Result := ReadEvaporationData;
    5:    Result := ReadSubcatchmentData;
    6:    Result := ReadSubareaData;
    7:    Result := ReadInfiltrationData;
    8:    Result := ReadAquiferData;
    9:    Result := ReadGroundwaterData;
    10:   Result := ReadJunctionData;
    11:   Result := ReadOutfallData;
    12:   Result := ReadStorageData;
    13:   Result := ReadDividerData;
    14:   Result := ReadConduitData;
    15:   Result := ReadPumpData;
    16:   Result := ReadOrificeData;
    17:   Result := ReadWeirData;
    18:   Result := ReadOutletData;
    19:   Result := ReadXsectionData;
    20:   Result := ReadTransectData;
    21:   Result := ReadLossData;
    //22: ReadControlData called directly from ReadFile
    23:   Result := ReadPollutantData;
    24:   Result := ReadLanduseData;
    25:   Result := ReadBuildupData;
    26:   Result := ReadWashoffData;
    27:   Result := ReadCoverageData;
    28:   Result := ReadExInflowData;
    29:   Result := ReadDWInflowData;
    30:   Result := ReadPatternData;
    31:   Result := ReadIIInflowData;
    32:   Result := ReadLoadData;
    33:   Result := ReadCurveData;
    34:   Result := ReadTimeseriesData;
    35:   Result := ReadReportData;
    36:   Result := ReadFileData;
    37:   Result := ReadMapData;
    38:   Result := ReadCoordData;
    39:   Result := ReadVertexdata;
    40:   Result := ReadPolygonData;
    41:   Result := ReadSymbolData;
    42:   Result := ReadLabelData;
    43:   Result := ReadBackdropData;
    44:   Result := ReadProfileData;
    45:   Result := ReadCurveData;
    46:   Result := ReadTemperatureData;
    47:   Result := ReadSnowpackData;
    48:   Result := ReadTreatmentData(S);
    49:   Result := ReadTagData;
    50:   Result := Ulid.ReadLidData(TokList, Ntoks);
    51:   Result := ReadLidUsageData;
    52:   Result := ReadGroundwaterFlowEqn(S);
    53:   Result := ReadAdjustmentData;                                        //(5.1.007)
    else  Result := 0;
  end;
end;


function ParseImportLine(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Processes line of input from imported scenario or map file.
//  (Not currently used.)
//-----------------------------------------------------------------------------
begin
  Result := 0;
end;


procedure StripComment(const Line: String; var S: String);
//-----------------------------------------------------------------------------
//  Strips comment (text following a ';') from a line of input.
//  Ignores comment if it begins with ';;'.
//-----------------------------------------------------------------------------
var
  P: Integer;
  N: Integer;
  C: String;
begin
  C := '';
  S := Trim(Line);
  P := Pos(';', S);
  if P > 0 then
  begin
    N := Length(S);
    C := Copy(S, P+1, N);
    if (Length(C) >= 1) and (C[1] <> ';') then
    begin
      if Length(Comment) > 0 then Comment := Comment + #13;
      Comment := Comment + C;
    end;
    Delete(S, P, N);
  end;
end;


function FindNewSection(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Checks if S matches any of the section heading key words.
//-----------------------------------------------------------------------------
var
  K: Integer;
begin
  for K := 0 to High(SectionWords) do
  begin
    if Pos(SectionWords[K], S) = 1 then
    begin
      Result := K;
      Exit;
    end;
  end;
  Result := -1;
end;


function StartNewSection(S: String): Integer;
//-----------------------------------------------------------------------------
//  Begins reading a new section of the input file.
//-----------------------------------------------------------------------------
var
  K : Integer;

begin
  // Determine which new section to begin
  K := FindNewSection(UpperCase(S));
  if (K >= 0) then
  begin

    //Update section code
    Section := K;
    PrevID := '';
    Result := 0;
  end
  else Result := ErrMsg(KEYWORD_ERR, S);
  Comment := '';
end;


function ReadFile(var F: Textfile; const Fsize: Int64):Boolean;
//-----------------------------------------------------------------------------
//  Reads each line of a SWMM input file.
//-----------------------------------------------------------------------------
var
  Err         : Integer;
  ByteCount   : Integer;
  StepCount   : Integer;
  StepSize    : Integer;
  S           : String;
begin
  Result := True;
  ErrCount := 0;
  LineCount := 0;
  Comment := '';

  // Initialize progress meter settings
  StepCount := MainForm.ProgressBar.Max div MainForm.ProgressBar.Step;
  StepSize := Fsize div StepCount;
  if StepSize < 1000 then StepSize := 0;
  ByteCount := 0;

  // Read each line of input file
  Reset(F);
  while not Eof(F) do
  begin
    Err := 0;
    Readln(F, Line);
    Inc(LineCount);
    if StepSize > 0 then
    begin
      Inc(ByteCount, Length(Line));
      MainForm.UpdateProgressBar(ByteCount, StepSize);
    end;

    // Strip out trailing spaces, control characters & comment
    Line := TrimRight(Line);
    StripComment(Line, S);

    // Check if line begins a new input section
    if (Pos('[', S) = 1) then Err := StartNewSection(S)
    else
    begin

////  Following code section modified for release 5.1.011.  ////               //(5.1.011)
      // Check if line contains project title/notes
      if (Section = 0) and (Length(Line) > 0) then
      begin
        if LeftStr(Line,2) <> ';;' then Err := ReadTitleData(Line);
      end

      // Check if line contains a control rule clause
      else if (Section = 22) then Err := ReadControlData(Line)

      // Check if line contains an event start/end dates
      else if (Section = 54) then Err := ReadEventData(Trim(Line))

      // If in some section, then process the input line
      else
      begin
        // Break line into string tokens and parse their contents
        Uutils.Tokenize(S, TokList, Ntoks);
        if (Ntoks > 0) and (Section >= 0) then
        begin
          Err := ParseInpLine(S);
          Comment := '';
        end

        // No current section -- file was probably not an EPA-SWMM file
        else if (Ntoks > 0) then
        begin
          Result := False;
          Exit;
        end;
      end;
    end;
////

    // Increment error count
    if Err > 0 then Inc(ErrCount);
  end;  //End of file.

  if ErrCount > MAX_ERRORS then ErrList.Add(
    IntToStr(ErrCount-MAX_ERRORS) + TXT_MORE_ERRORS);
end;


procedure DisplayInpErrForm(const Fname: String);
//-----------------------------------------------------------------------------
//  Displays Status Report form that lists any error messages.
//-----------------------------------------------------------------------------
begin
  SysUtils.DeleteFile((TempReportFile));
  TempReportFile := Uutils.GetTempFile(TempDir,'swmm');
  ErrList.Insert(0, TXT_ERROR_REPORT + Fname + #13);
  ErrList.SaveToFile(TempReportFile);
  MainForm.MnuReportStatusClick(MainForm);
end;


procedure ReverseVertexLists;
//-----------------------------------------------------------------------------
//  Reverses list of vertex points for each link in the project.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
begin
  for I := 0 to MAXCLASS do
  begin
    if not Project.IsLink(I) then continue;
    for J := 0 to Project.Lists[I].Count-1 do
      if Project.GetLink(I, J).Vlist <> nil then
        Project.GetLink(I, J).Vlist.Reverse;
  end;
end;


procedure SetSubcatchCentroids;
//-----------------------------------------------------------------------------
//  Determines the centroid of each subcatchment polygon.
//-----------------------------------------------------------------------------
var
  I : Integer;
begin
  for I := 0 to Project.Lists[SUBCATCH].Count - 1 do
  begin
    Project.GetSubcatch(SUBCATCH, I).SetCentroid;
  end;
end;


procedure SetIDPtrs;
//-----------------------------------------------------------------------------
//  Makes pointers to ID strings the ID property of objects.
//-----------------------------------------------------------------------------
var
  I, J : Integer;
  C : TSubcatch;
  S : String;

begin
  for I := 0 to MAXCLASS do
  begin
    if I = RAINGAGE then with Project.Lists[RAINGAGE] do
    begin
      for J := 0 to Count-1 do TRaingage(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsSubcatch(I) then with Project.Lists[SUBCATCH] do
    begin
      for J := 0 to Count-1 do
      begin
        C := TSubcatch(Objects[J]);
        C.ID := PChar(Strings[J]);
        S := Trim(C.Data[SUBCATCH_OUTLET_INDEX]);
        C.OutSubcatch := FindSubcatch(S);
        if C.OutSubcatch = nil then C.OutNode := FindNode(S);
      end;
    end
    else if Project.IsNode(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TNode(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsLink(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TLink(Objects[J]).ID := PChar(Strings[J]);
    end
    else if I = TRANSECT then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do
      begin
        TTransect(Objects[J]).CheckData;
        TTransect(Objects[J]).SetMaxDepth;
        Project.SetTransectConduitDepth(Strings[J],
          TTransect(Objects[J]).Data[TRANSECT_MAX_DEPTH]);
      end;
    end
    else continue;
  end;
end;


function ReadInpFile(const Fname: String):Boolean;
//-----------------------------------------------------------------------------
//  Reads SWMM input data from a text file.
//-----------------------------------------------------------------------------
var
  F : Textfile;
begin
  // Try to open the file
  Result := False;
  AssignFile(F,Fname);
  {$I-}
  Reset(F);
  {$I+}
  if (IOResult = 0) then
  begin

    // Create stringlists
    Screen.Cursor := crHourGlass;
    MapExtentSet := False;
    ErrList := TStringList.Create;
    TokList := TStringList.Create;
    SubcatchList := TStringList.Create;
    NodeList := TStringList.Create;
    LinkList := TStringList.Create;
    FileType := ftInput;
    InpFile := Fname;
    try

      // Read the file
      MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);
      SubcatchList.Sorted := True;
      NodeList.Sorted := True;
      LinkList.Sorted := True;
      Section := -1;
      Result := ReadFile(F, Uutils.GetFileSize(Fname));
      if (Result = True) then
      begin
        // Establish pointers to ID names
        SetIDPtrs;
      end;

    finally
      // Free the stringlists
      SubcatchList.Free;
      NodeList.Free;
      LinkList.Free;
      TokList.Free;
      MainForm.PageSetupDialog.Header.Text := Project.Title;
      MainForm.HideProgressBar;
      Screen.Cursor := crDefault;

      // Display errors if found & set map dimensions
      if Result = True then
      begin
        if ErrList.Count > 0 then DisplayInpErrForm(Fname);
        SetSubcatchCentroids;
        SetMapDimensions;
      end;
      ErrList.Free;
    end;
  end;

  // Close the input file
  CloseFile(F);
end;


procedure ClearDefaultDates;
//-----------------------------------------------------------------------------
//  Clears project's date/time settings.
//-----------------------------------------------------------------------------
begin
  with Project.Options do
  begin
    Data[START_DATE_INDEX]        := '';
    Data[START_TIME_INDEX]        := '';
    Data[REPORT_START_DATE_INDEX] := '';
    Data[REPORT_START_TIME_INDEX] := '';
    Data[END_DATE_INDEX]          := '';
    Data[END_TIME_INDEX]          := '';
  end;
end;


procedure SetDefaultDates;
//-----------------------------------------------------------------------------
//  Sets default values for project's date/time settings.
//-----------------------------------------------------------------------------
var
  StartTime: TDateTime;
  StartDate: TDateTime;
  T: TDateTime;
  D: TDateTime;
begin
  with Project.Options do
  begin

  // Process starting date/time
    try
      StartDate := StrToDate(Data[START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do StartDate := Date;
    end;
    StartTime := Uutils.StrHoursToTime(Data[START_TIME_INDEX]);
    if StartTime < 0 then StartTime := 0;
    D := StartDate + StartTime;
    Data[START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process reporting start date/time
    try
      D := StrToDate(Data[REPORT_START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[REPORT_START_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[REPORT_START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[REPORT_START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process ending date/time
    try
      D := StrToDate(Data[END_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[END_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[END_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[END_TIME_INDEX] := TimeToStr(D, MyFormatSettings);
  end;
end;


function OpenProject(const Fname: String): TInputFileType;
//-----------------------------------------------------------------------------
//  Reads in project data from a file.
//-----------------------------------------------------------------------------
begin
  // Show progress meter
  ClearDefaultDates;
  Screen.Cursor := crHourGlass;
  MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);

  // Use default map dimensions and backdrop settings
  MapForm.Map.Dimensions := DefMapDimensions;
  MapForm.Map.Backdrop := DefMapBackdrop;

  // Do the following for non-temporary input files
  if not SameText(Fname, Uglobals.TempInputFile) then
  begin

    // Create a backup file
    if AutoBackup
    then CopyFile(PChar(Fname), PChar(ChangeFileExt(Fname, '.bak')), FALSE);

    // Retrieve project defaults from .INI file
    if CompareText(ExtractFileExt(Fname), '.ini') <> 0
    then Uinifile.ReadProjIniFile(ChangeFileExt(Fname, '.ini'));
  end;

  // Read and parse each line of input file
  Result := iftNone;
  if ReadInpFile(Fname) then Result := iftINP;

  // Finish processing the input data
  if Result <> iftNone then
  begin
    SetDefaultDates;                   // set any missing analysis dates
    Uglobals.RegisterCalibData;        // register calibration data files
    Uupdate.UpdateUnits;               // update choice of unit system
    Uupdate.UpdateDefOptions;          // update default analysis options
    Uupdate.UpdateLinkHints;           // update hints used for offsets
    Project.GetControlRuleNames;       // store control ruunit Uimport;

{-------------------------------------------------------------------}
{                    Unit:    Uimport.pas                           }
{                    Project: EPA SWMM                              }
{                    Version: 5.1                                   }
{                    Date:    12/02/13    (5.1.001)                 }
{                             04/04/14    (5.1.003)                 }
{                             04/14/14    (5.1.004)                 }
{                             09/15/14    (5.1.007)                 }
{                             03/19/15    (5.1.008)                 }
{                             08/05/15    (5.1.010)                 }
{                             08/01/16    (5.1.011)                 }
{                    Author:  L. Rossman                            }
{                                                                   }
{   Delphi Pascal unit that imports a SWMM project's data from a    }
{   a formatted text file.                                          }
{                                                                   }
{   5.1.011 - Support for reading [EVENTS] section added.           }
{-------------------------------------------------------------------}

interface

uses
  Classes, Forms, Controls, Dialogs, SysUtils, Windows, Math, StrUtils,
  Uutils, Uglobals;

const
  ITEMS_ERR    = 1;
  KEYWORD_ERR  = 2;
  SUBCATCH_ERR = 3;
  NODE_ERR     = 4;
  LINK_ERR     = 5;
  LANDUSE_ERR  = 6;
  POLLUT_ERR   = 7;
  NUMBER_ERR   = 8;
  XSECT_ERR    = 9;
  TRANSECT_ERR = 10;
  TIMESTEP_ERR = 11;
  DATE_ERR     = 12;
  LID_ERR      = 13;
  MAX_ERRORS   = 50;

type
  TFileType = (ftInput, ftImport);

// These routines can be called from other units
function  ErrMsg(const ErrCode: Integer; const Name: String): Integer;
function  OpenProject(const Fname: String): TInputFileType;
function  ReadInpFile(const Fname: String):Boolean;
procedure SetDefaultDates;

implementation

uses
  Fmain, Fmap, Fstatus, Dxsect, Uexport, Uinifile, Uproject, Umap,
  Ucoords, Uvertex, Uupdate, Dreporting, Ulid;

const
  MSG_READING_PROJECT_DATA = 'Reading project data... ';
  TXT_ERROR = 'Error ';
  TXT_AT_LINE = ' at line ';
  TXT_MORE_ERRORS = ' more errors found in file.';
  TXT_ERROR_REPORT = 'Error Report for File ';

  SectionWords : array[0..54] of PChar =                                       //(5.1.011)
    ('[TITLE',                    //0
     '[OPTION',                   //1
     '[RAINGAGE',                 //2
     '[HYDROGRAPH',               //3
     '[EVAPORATION',              //4
     '[SUBCATCHMENT',             //5
     '[SUBAREA',                  //6
     '[INFILTRATION',             //7
     '[AQUIFER',                  //8
     '[GROUNDWATER',              //9
     '[JUNCTION',                 //10
     '[OUTFALL',                  //11
     '[STORAGE',                  //12
     '[DIVIDER',                  //13
     '[CONDUIT',                  //14
     '[PUMP',                     //15
     '[ORIFICE',                  //16
     '[WEIR',                     //17
     '[OUTLET',                   //18
     '[XSECTION',                 //19
     '[TRANSECT',                 //20
     '[LOSS',                     //21
     '[CONTROL',                  //22
     '[POLLUTANT',                //23
     '[LANDUSE',                  //24
     '[BUILDUP',                  //25
     '[WASHOFF',                  //26
     '[COVERAGE',                 //27
     '[INFLOW',                   //28
     '[DWF',                      //29
     '[PATTERN',                  //30
     '[RDII',                     //31
     '[LOAD',                     //32
     '[CURVE',                    //33
     '[TIMESERIES',               //34
     '[REPORT',                   //35
     '[FILE',                     //36
     '[MAP',                      //37
     '[COORDINATES',              //38
     '[VERTICES',                 //39
     '[POLYGONS',                 //40
     '[SYMBOLS',                  //41
     '[LABELS',                   //42
     '[BACKDROP',                 //43
     '[PROFILE',                  //44
     '[TABLE',                    //45
     '[TEMPERATURE',              //46
     '[SNOWPACK',                 //47
     '[TREATMENT',                //48
     '[TAG',                      //49
     '[LID_CONTROL',              //50
     '[LID_USAGE',                //51
     '[GWF',                      //52                                         //(5.1.007)
     '[ADJUSTMENTS',              //53                                         //(5.1.007)
     '[EVENT');                   //54                                         //(5.1.011)

var
  FileType     : TFileType;
  InpFile      : String;
  ErrList      : TStringlist;
  SubcatchList : TStringlist;
  NodeList     : TStringlist;
  LinkList     : TStringlist;
  TokList      : TStringlist;
  Ntoks        : Integer;
  Section      : Integer;
  LineCount    : LongInt;
  ErrCount     : LongInt;
  Line         : String;
  Comment      : String;
  TsectComment : String;
  PrevID       : String;
  PrevIndex    : Integer;
  CurveType    : Integer;
  MapExtentSet : Boolean;
  ManningsN    : array[1..3] of String;

  // These are deprecated attributes of a backdrop image
  BackdropX    : Extended;
  BackdropY    : Extended;
  BackdropW    : Extended;
  BackdropH    : Extended;


function ErrMsg(const ErrCode: Integer; const Name: String): Integer;
//-----------------------------------------------------------------------------
//  Adds an error message for a specific error code to the list
//  of errors encountered when reading an input file.
//-----------------------------------------------------------------------------
var
  S: String;
begin
  if ErrCount <= MAX_ERRORS then
  begin
    case ErrCode of
      ITEMS_ERR:    S := 'Too few items ';
      KEYWORD_ERR:  S := 'Unrecognized keyword (' + Name + ') ';
      SUBCATCH_ERR: S := 'Undefined Subcatchment (' + Name + ') referenced ';
      NODE_ERR:     S := 'Undefined Node (' + Name + ') referenced ';
      LINK_ERR:     S := 'Undefined Link (' + Name + ') referenced ';
      LANDUSE_ERR:  S := 'Undefined Land Use (' + Name + ') referenced ';
      POLLUT_ERR:   S := 'Undefined Pollutant (' + Name + ') referenced ';
      NUMBER_ERR:   S := 'Illegal numeric value (' + Name + ') ';
      XSECT_ERR:    S := 'Illegal cross section for Link ' + Name + ' ';
      TRANSECT_ERR: S := 'No Transect defined for these data ';
      TIMESTEP_ERR: S := 'Illegal time step value ';
      DATE_ERR:     S := 'Illegal date/time value ';
      LID_ERR:      S := 'Undefined LID process (' + Name + ') referenced ';
      else          S := 'Unknown error ';
    end;
    S := S + 'at line ' + IntToStr(LineCount) + ':';
    ErrList.Add(S);
    if Section >= 0 then ErrList.Add(SectionWords[Section] + ']');
    ErrList.Add(Line);
    ErrList.Add('');
  end;
  Result := ErrCode;
end;


function FindSubcatch(const ID: String): TSubcatch;
//-----------------------------------------------------------------------------
//  Finds a Subcatchment object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Atype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if SubcatchList.Find(ID, Index)
    then Result := TSubcatch(SubcatchList.Objects[Index]);
  end
  else
  begin
    if (Project.FindSubcatch(ID, Atype, Index))
    then Result := Project.GetSubcatch(Atype, Index);
  end;
end;


function FindNode(const ID: String): TNode;
//-----------------------------------------------------------------------------
// Finds a Node object given its ID name.
//-----------------------------------------------------------------------------
var
  Index: Integer;
  Ntype: Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if NodeList.Find(ID, Index)
    then Result := TNode(NodeList.Objects[Index]);
  end
  else
  begin
    if (Project.FindNode(ID, Ntype, Index))
    then Result := Project.GetNode(Ntype, Index);
  end;
end;


function FindLink(const ID: String): TLink;
//-----------------------------------------------------------------------------
// Finds a Link object given its ID name.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Ltype : Integer;
begin
  Result := nil;
  if (FileType = ftInput) then
  begin
    if LinkList.Find(ID, Index)
    then Result := TLink(LinkList.Objects[Index]);
  end
  else
  begin
    if (Project.FindLink(ID, Ltype, Index))
    then Result := Project.GetLink(Ltype, Index);
  end;
end;


function ReadTitleData(Line: String): Integer;
//-----------------------------------------------------------------------------
// Adds Line of text to a project's title and notes.
//-----------------------------------------------------------------------------
begin
  if Project.Lists[NOTES].Count = 0 then Project.Title := Line;
  Project.Lists[NOTES].Add(Line);
  Project.HasItems[NOTES] := True;
  Result := 0;
end;


procedure ReadOldRaingageData(I: Integer; aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of parsed rain gage data using older format.
//-----------------------------------------------------------------------------
begin
  if I = 0 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
    if Ntoks > 2 then aGage.Data[GAGE_SERIES_NAME] := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_DATA_FORMAT] := TokList[3];
    if Ntoks > 4 then aGage.Data[GAGE_DATA_FREQ]   := TokList[4];
  end;
  if I = 1 then
  begin
    aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
    if Ntoks > 2 then aGage.Data[GAGE_FILE_NAME]   := TokList[2];
    if Ntoks > 3 then aGage.Data[GAGE_STATION_NUM] := TokList[3];
  end;
end;


procedure ReadNewRaingageData(aGage: TRaingage);
//-----------------------------------------------------------------------------
//  Reads a line of rain gage data using newer format.
//-----------------------------------------------------------------------------
var
  I: Integer;
  Fname: String;
begin
  if Ntoks > 1 then aGage.Data[GAGE_DATA_FORMAT] := TokList[1];
  if Ntoks > 2 then aGage.Data[GAGE_DATA_FREQ]   := TokList[2];
  if Ntoks > 3 then aGage.Data[GAGE_SNOW_CATCH]  := TokList[3];
  if Ntoks > 4 then
  begin
    I := Uutils.FindKeyWord(TokList[4], RaingageOptions, 4);
    if I = 0 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[0];
      if Ntoks > 5 then aGage.Data[GAGE_SERIES_NAME] := TokList[5];
    end;
    if I = 1 then
    begin
      aGage.Data[GAGE_DATA_SOURCE] := RaingageOptions[1];
      if Ntoks > 5 then
      begin
        Fname := TokList[5];
        if ExtractFilePath(Fname) = ''
        then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
        aGage.Data[GAGE_FILE_NAME] := Fname;
      end;
      if Ntoks > 6 then aGage.Data[GAGE_STATION_NUM] := TokList[6];
      if Ntoks > 7 then aGage.Data[GAGE_RAIN_UNITS]  := TokList[7];
    end;
  end;
end;


function ReadRaingageData: Integer;
//-----------------------------------------------------------------------------
//  Parses rain gage data from the input line.
//-----------------------------------------------------------------------------
var
  aGage   : TRaingage;
  ID      : String;
  I       : Integer;
begin
  if Ntoks < 2 then
  begin
    Result := ErrMsg(ITEMS_ERR, '');
    Exit;
  end;
  ID := TokList[0];
  aGage := TRaingage.Create;
  Uutils.CopyStringArray(Project.DefProp[RAINGAGE].Data, aGage.Data);
  aGage.X := MISSING;
  aGage.Y := MISSING;
  aGage.Data[COMMENT_INDEX ] := Comment;
  Project.Lists[RAINGAGE].AddObject(ID, aGage);
  Project.HasItems[RAINGAGE] := True;
  I := Uutils.FindKeyWord(TokList[1], RaingageOptions, 4);
  if I >= 0
    then ReadOldRaingageData(I, aGage)
    else ReadNewRaingageData(aGage);
  Result := 0;
end;


function ReadOldHydrographFormat(const I: Integer; UH: THydrograph): Integer;
//-----------------------------------------------------------------------------
//  Reads older format of unit hydrograph parameters from a line of input.
//-----------------------------------------------------------------------------
var
  J, K, N: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin
    N := 2;
    for K := 1 to 3 do
    begin
      for J := 1 to 3 do
      begin
        UH.Params[I,J,K] := TokList[N];
        Inc(N);
      end;
    end;
    for J := 1 to 3 do
    begin
      UH.InitAbs[I,J,1] := '';
      if Ntoks > N then UH.InitAbs[I,J,1] := TokList[N];
      Inc(N);
      for K := 2 to 3 do UH.InitAbs[I,J,K] := UH.InitAbs[I,J,1];
    end;
  end;
end;


function ReadHydrographData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII unit hydrograph data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J, K: Integer;
  ID: String;
  aUnitHyd: THydrograph;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else
  begin

    // Check if hydrograph ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID)
    then Index := PrevIndex
    else Index := Project.Lists[HYDROGRAPH].IndexOf(ID);

    // If starting input for a new hydrograph then create it
    if Index < 0 then
    begin
      aUnitHyd := THydrograph.Create;
      Project.Lists[HYDROGRAPH].AddObject(ID, aUnitHyd);
      Project.HasItems[HYDROGRAPH] := True;
      Index := Project.Lists[HYDROGRAPH].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
    end
    else aUnitHyd := THydrograph(Project.Lists[HYDROGRAPH].Objects[Index]);

    // Parse rain gage name for 2-token line
    if Ntoks = 2 then
    begin
      aUnitHyd.Raingage := TokList[1];
    end

    // Extract remaining hydrograph parameters
    else if Ntoks < 6 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      // Determine month of year
      I := Uutils.FindKeyWord(TokList[1], MonthLabels, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1]);

      // Determine if response type present - if not, process old format
      K := Uutils.FindKeyWord(TokList[2], ResponseTypes, 3) + 1;
      if K < 1 then Result := ReadOldHydrographFormat(I, aUnitHyd)
      else
      begin
        // Extract R-T-K values
        for J := 3 to 5 do
        begin
          aUnitHyd.Params[I,J-2,K] := TokList[J];
        end;
        // Extract IA parameters
        for J := 6 to 8 do
        begin
          if J >= Ntoks then break
          else aUnitHyd.InitAbs[I,J-5,K] := TokList[J];
        end;
      end;
    end;
  end;
end;


function ReadTemperatureData: Integer;
//-----------------------------------------------------------------------------
//  Reads description of air temperature data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then Result := ErrMsg(ITEMS_ERR, '')
  else with Project.Climatology do
  begin
    I := Uutils.FindKeyWord(TokList[0], TempKeywords, 10);
    if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[0])
    else case I of
    0:  begin                                              // Time series
          TempDataSource := 1;
          TempTseries := TokList[1];
        end;
    1:  begin                                              // File
          TempDataSource := 2;
          TempFile := TokList[1];
          if ExtractFilePath(TempFile) = ''  then
          TempFile := ExtractFilePath(Uglobals.InputFileName) + TempFile;
          if Ntoks >= 3 then TempStartDate := TokList[2];
        end;
    2:  begin                                              // Wind speed
          if SameText(TokList[1], 'MONTHLY') then
          begin
            if Ntoks < 14 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              WindType := MONTHLY_WINDSPEED;
              for I := 1 to 12 do WindSpeed[I] := TokList[I+1];
            end;
          end
          else if SameText(TokList[1], 'FILE') then WindType := FILE_WINDSPEED
          else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        end;
    3:  begin
          if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
          else for I := 1 to 6 do SnowMelt[I] := TokList[I];
        end;
    4:  begin
          if Ntoks < 12 then Result := ErrMsg(ITEMS_ERR, '')
          else
          begin
            if  SameText(TokList[1], 'IMPERVIOUS') then
              for I := 1 to 10 do ADCurve[1][I] := TokList[I+1]
            else if SameText(TokList[1], 'PERVIOUS') then
              for I := 1 to 10 do ADCurve[2][I] := TokList[I+1]
            else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
          end;
        end;
    end;
  end;
end;


function ReadEvaporationRates(Etype: Integer): Integer;
var
  J: Integer;
begin
  if (Etype <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin
    EvapType := Etype;

    case EvapType of

      CONSTANT_EVAP:
        for J := 0 to 11 do EvapData[J] := TokList[1];

      TSERIES_EVAP:
        EvapTseries := TokList[1];

      FILE_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          PanData[J-1] := TokList[J];
        end;

      MONTHLY_EVAP:
        for J := 1 to 12 do
        begin
          if J >= Ntoks then break;
          EvapData[J-1] := TokList[J];
        end;
    end;

    Result := 0;
  end;

end;


function ReadEvaporationData: Integer;
//-----------------------------------------------------------------------------
//  Reads evaporation data from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  I := Uutils.FindKeyWord(TokList[0], EvapOptions, 4);
  if I >= 0 then Result := ReadEvaporationRates(I)

  else if (I <> TEMP_EVAP) and (Ntoks < 2)
  then  Result := ErrMsg(ITEMS_ERR, '')

  else with Project.Climatology do
  begin

    if SameText(TokList[0], 'RECOVERY')
    then RecoveryPat := TokList[1]

    else if SameText(TokList[0], 'DRY_ONLY') then
    begin
      if SameText(TokList[1], 'NO') then EvapDryOnly := False
      else if SameText(TokList[1], 'YES') then EvapDryOnly := True
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end

    else Result := ErrMsg(KEYWORD_ERR, TokList[0]);

  end;
end;


function ReadSubcatchmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := TSubcatch.Create;
    SubcatchList.AddObject(ID, S);
    S.X := MISSING;
    S.Y := MISSING;
    S.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[SUBCATCH].Data, S.Data);
    S.Data[SUBCATCH_RAINGAGE_INDEX] := TokList[1];
    S.Data[SUBCATCH_OUTLET_INDEX]   := TokList[2];
    S.Data[SUBCATCH_AREA_INDEX]     := TokList[3];
    S.Data[SUBCATCH_IMPERV_INDEX]   := TokList[4];
    S.Data[SUBCATCH_WIDTH_INDEX]    := TokList[5];
    S.Data[SUBCATCH_SLOPE_INDEX]    := TokList[6];
    S.Data[SUBCATCH_CURBLENGTH_INDEX] := TokList[7];
    if Ntoks >= 9
      then S.Data[SUBCATCH_SNOWPACK_INDEX] := TokList[8];
    S.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[SUBCATCH].AddObject(ID, S);
    Project.HasItems[SUBCATCH] := True;
    Result := 0;
  end;
end;


function ReadSubareaData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment sub-area data from a line of input.
//-----------------------------------------------------------------------------
var
  S  : TSubcatch;
  ID : String;
begin
  if Ntoks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    S := FindSubcatch(ID);
    if S = nil
    then Result := ErrMsg(SUBCATCH_ERR, ID)
    else begin
      S.Data[SUBCATCH_IMPERV_N_INDEX]  := TokList[1];
      S.Data[SUBCATCH_PERV_N_INDEX]    := TokList[2];
      S.Data[SUBCATCH_IMPERV_DS_INDEX] := TokList[3];
      S.Data[SUBCATCH_PERV_DS_INDEX]   := TokList[4];
      S.Data[SUBCATCH_PCTZERO_INDEX]   := TokList[5];
      S.Data[SUBCATCH_ROUTE_TO_INDEX]  := TokList[6];
      if Ntoks >= 8
      then S.Data[SUBCATCH_PCT_ROUTED_INDEX] := TokList[7];
      Result := 0;
    end;
  end;
end;


function ReadInfiltrationData: Integer;
//-----------------------------------------------------------------------------
// Reads subcatchment infiltration data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : TSubcatch;
  ID    : String;
  J     : Integer;
  Jmax  : Integer;
begin
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else begin
    Jmax := Ntoks-1;
    if Jmax > MAXINFILPROPS then Jmax := MAXINFILPROPS;
    for J := 1 to Jmax do S.InfilData[J-1] := TokList[J];
    Result := 0;
  end;
end;


function ReadAquiferData: Integer;
//-----------------------------------------------------------------------------
//  Reads aquifer data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  A  : TAquifer;
  I  : Integer;
begin
  if nToks < MAXAQUIFERPROPS then Result := ErrMsg(ITEMS_ERR, '')              //(5.1.003)
  else begin
    ID := TokList[0];
    A := TAquifer.Create;
    //Uutils.CopyStringArray(Project.DefProp[AQUIFER].Data, A.Data);           //(5.1.004)
    Project.Lists[AQUIFER].AddObject(ID, A);
    Project.HasItems[AQUIFER] := True;
    for I := 0 to MAXAQUIFERPROPS-1 do A.Data[I] := TokList[I+1];              //(5.1.003)
    if nToks >= MAXAQUIFERPROPS + 2                                            //(5.1.004)
    then A.Data[MAXAQUIFERPROPS] := TokList[MAXAQUIFERPROPS+1]                 //(5.1.003)
    else A.Data[MAXAQUIFERPROPS] := '';                                        //(5.1.003)
    Result := 0;
  end;
end;


function ReadGroundwaterData: Integer;
//-----------------------------------------------------------------------------
//  Reads subcatchment groundwater data from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  ID: String;
  P:  String;
  J:  Integer;
  K:  Integer;

begin
  // Get subcatchment name
  ID := TokList[0];
  S := FindSubcatch(ID);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, ID)
  else
  begin

    // Line contains GW flow parameters
    if Ntoks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else begin
      S.Groundwater.Clear;

      // Read required parameters
      for J := 1 to 9 do S.Groundwater.Add(TokList[J]);

      // Read optional parameters
      for K := 10 to 13 do
      begin
        if Ntoks > K then
        begin
          P := TokList[K];
          if P = '*' then P := '';
          S.Groundwater.Add(P);
        end
        else S.Groundwater.Add('');
      end;
      S.Data[SUBCATCH_GWATER_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;

////  This function was re-written for release 5.1.007.  ////                  //(5.1.007)
function ReadGroundwaterFlowEqn(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads GW flow math expression from a line of input.
//-----------------------------------------------------------------------------
var
  S:  TSubcatch;
  N:  Integer;
begin
  // Check for enough tokens in line
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
  // Get subcatchment object referred to by name
  else begin
    Result := 0;
    S := FindSubcatch(TokList[0]);
    if S = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
    else begin
      // Find position in Line where second token ends
      N := Pos(TokList[1], Line) + Length(TokList[1]);
      // Save remainder of line to correct type of GW flow equation
      if SameText(TokList[1], 'LATERAL') then
        S.GwLatFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else if SameText(TokList[1], 'DEEP') then
        S.GwDeepFlowEqn := Trim(AnsiRightStr(Line, Length(Line)-N))
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;
  end;
end;

function ReadSnowpackData: Integer;
//-----------------------------------------------------------------------------
//  Reads snowpack data from a line of input.
//-----------------------------------------------------------------------------
var
  ID : String;
  P  : TSnowpack;
  I  : Integer;
  J  : Integer;
  Index: Integer;
begin
  Result := 0;
  if Ntoks < 8
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[1], SnowpackOptions, 7);
    if I < 0
    then Result := ErrMsg(KEYWORD_ERR, TokList[1])
    else begin

      // Check if snow pack ID is same as for previous line
      ID := TokList[0];
      if (ID = PrevID)
      then Index := PrevIndex
      else Index := Project.Lists[SNOWPACK].IndexOf(ID);

      // If starting input for a new snow pack then create it
      if Index < 0 then
      begin
        P := TSnowpack.Create;
        Project.Lists[SNOWPACK].AddObject(ID, P);
        Project.HasItems[SNOWPACK] := True;
        Index := Project.Lists[SNOWPACK].Count - 1;
        PrevID := ID;
        PrevIndex := Index;
      end
      else P := TSnowpack(Project.Lists[SNOWPACK].Objects[Index]);

      // Parse line depending on data type
      case I of

      // Plowable area
      0:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else
            begin
              for J := 1 to 6 do P.Data[1][J] := TokList[J+1];
              P.FracPlowable := TokList[8];
            end;
          end;

      // Impervious or Pervious area
      1,
      2:  begin
            if Ntoks < 9 then Result := ErrMsg(ITEMS_ERR, '')
            else for J := 1 to 7 do P.Data[I+1][J] := TokList[J+1];
          end;

      // Plowing parameters
      3:  begin
            for J := 1 to 6 do P.Plowing[J] := TokList[J+1];
            if Ntoks >= 9 then P.Plowing[7] := TokList[8];
          end;
      end;
    end;
  end;
end;


function ReadJunctionData: Integer;
//-----------------------------------------------------------------------------
//  Reads junction data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := JUNCTION;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[JUNCTION].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    if Ntoks > 2 then  aNode.Data[JUNCTION_MAX_DEPTH_INDEX]       := TokList[2];
    if Ntoks > 3 then  aNode.Data[JUNCTION_INIT_DEPTH_INDEX]      := TokList[3];
    if Ntoks > 4 then  aNode.Data[JUNCTION_SURCHARGE_DEPTH_INDEX] := TokList[4];
    if Ntoks > 5 then  aNode.Data[JUNCTION_PONDED_AREA_INDEX]     := TokList[5];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[JUNCTION].AddObject(ID, aNode);
    Project.HasItems[JUNCTION] := True;
    Result := 0;
  end;
end;


function ReadOutfallData: Integer;
//-----------------------------------------------------------------------------
//  Reads outfall data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  I    : Integer;
  N    : Integer;                                                              //(5.1.008)
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    N := 4;                                                                    //(5.1.008)
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := OUTFALL;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[OUTFALL].Data, aNode.Data);
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    I := Uutils.FindKeyWord(TokList[2], OutfallOptions, 4);
    if I < 0 then I := FREE_OUTFALL;
    if (I > NORMAL_OUTFALL) and (Ntoks >= 4) then
    begin
      case I of
      FIXED_OUTFALL:      aNode.Data[OUTFALL_FIXED_STAGE_INDEX] := TokList[3];
      TIDAL_OUTFALL:      aNode.Data[OUTFALL_TIDE_TABLE_INDEX] := TokList[3];
      TIMESERIES_OUTFALL: aNode.Data[OUTFALL_TIME_SERIES_INDEX] := TokList[3];
      end;
      N := 5;
      if Ntoks >= 5 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[4];    // (5.1.008)
    end
    else if Ntoks >= 4 then aNode.Data[OUTFALL_TIDE_GATE_INDEX] := TokList[3]; //(5.1.008)
    if Ntoks > N then aNode.Data[OUTFALL_ROUTETO_INDEX] := TokList[N];         //(5.1.008)
    aNode.Data[OUTFALL_TYPE_INDEX] := OutfallOptions[I];
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.Lists[OUTFALL].AddObject(ID, aNode);
    Project.HasItems[OUTFALL] := True;
    Result := 0;
  end;
end;

////  The following function was modified for release 5.1.007.  ////           //(5.1.007)

function ReadStorageData: Integer;
//-----------------------------------------------------------------------------
//  Reads storage unit data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;
  ID   : String;
  N    : Integer;
  X    : Single;
begin
  Result := 0;
  N := 6;
  if Ntoks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := STORAGE;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[STORAGE].Data, aNode.Data);
    Project.Lists[STORAGE].AddObject(ID, aNode);
    aNode.Data[NODE_INVERT_INDEX]        := TokList[1];
    aNode.Data[STORAGE_MAX_DEPTH_INDEX]  := TokList[2];
    aNode.Data[STORAGE_INIT_DEPTH_INDEX] := TokList[3];
    aNode.Data[STORAGE_GEOMETRY_INDEX]   := TokList[4];
    if CompareText(TokList[4], 'TABULAR') = 0 then
    begin
      aNode.Data[STORAGE_ATABLE_INDEX] := TokList[5];
    end
    else begin
      if Ntoks < 7
      then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        aNode.Data[STORAGE_ACOEFF_INDEX] := TokList[5];
        aNode.Data[STORAGE_AEXPON_INDEX] := TokList[6];
        if Ntoks >= 8 then aNode.Data[STORAGE_ACONST_INDEX] := TokList[7];
        N := 8;
      end;
    end;

    // Optional items
    if (Result = 0) and (Ntoks > N) then
    begin
      // Ponded area
      aNode.Data[STORAGE_PONDED_AREA_INDEX] := TokList[N];
      // Evaporation factor
      if Ntoks > N+1 then aNode.Data[STORAGE_EVAP_FACTOR_INDEX] := TokList[N+1];
      // Constant seepage rate
      if Ntoks = N+3 then
      begin
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+2];
      end
      // Green-Ampt seepage parameters
      else if Ntoks = N+5 then
      begin
        aNode.InfilData[STORAGE_SUCTION_INDEX] := TokList[N+2];
        aNode.InfilData[STORAGE_KSAT_INDEX] := TokList[N+3];
        aNode.InfilData[STORAGE_IMDMAX_INDEX] := TokList[N+4];
      end;
    end;
    Uutils.GetSingle(aNode.InfilData[STORAGE_KSAT_INDEX ], X);
    if (X > 0) then aNode.Data[STORAGE_SEEPAGE_INDEX] := 'YES'
    else aNode.Data[STORAGE_SEEPAGE_INDEX] := 'NO';
    aNode.Data[COMMENT_INDEX ] := Comment;
    Project.HasItems[STORAGE] := True;
  end;
end;


function ReadDividerData: Integer;
//-----------------------------------------------------------------------------
//  Reads flow divider data from a line of input.
//  (Corrected on 6/14/05)
//-----------------------------------------------------------------------------
var
  N, J : Integer;
  aNode: TNode;
  ID   : String;
begin
  if Ntoks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Result := 0;
    ID := TokList[0];
    aNode := TNode.Create;
    NodeList.AddObject(ID, aNode);
    aNode.Ntype := DIVIDER;
    aNode.X := MISSING;
    aNode.Y := MISSING;
    aNode.Zindex := -1;
    Uutils.CopyStringArray(Project.DefProp[DIVIDER].Data, aNode.Data);
    Project.Lists[DIVIDER].AddObject(ID, aNode);
    Project.HasItems[DIVIDER] := True;
    aNode.Data[COMMENT_INDEX ] := Comment;
    aNode.Data[NODE_INVERT_INDEX] := TokList[1];
    aNode.Data[DIVIDER_LINK_INDEX] := TokList[2];
    aNode.Data[DIVIDER_TYPE_INDEX] := TokList[3];
    N := 5;
    if SameText(TokList[3], 'OVERFLOW') then
    begin
      N := 4;
    end
    else if SameText(TokList[3], 'CUTOFF') then
    begin
      aNode.Data[DIVIDER_CUTOFF_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'TABULAR') then
    begin
      aNode.Data[DIVIDER_TABLE_INDEX] := TokList[4];
    end
    else if SameText(TokList[3], 'WEIR') and (Ntoks >= 7) then
    begin
      aNode.Data[DIVIDER_QMIN_INDEX]   := TokList[4];
      aNode.Data[DIVIDER_DMAX_INDEX]   := TokList[5];
      aNode.Data[DIVIDER_QCOEFF_INDEX] := TokList[6];
      N := 7;
    end
    else Result := ErrMsg(KEYWORD_ERR, TokList[3]);
    if (Result = 0) and (Ntoks > N) then
    begin
      for J := N to Ntoks-1 do
        aNode.Data[DIVIDER_MAX_DEPTH_INDEX + J - N] := TokList[J];
    end;
  end;
end;


function ReadConduitData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if Ntoks < 7 then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := CONDUIT;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[CONDUIT].Data, aLink.Data);
      Project.Lists[CONDUIT].AddObject(ID, aLink);
      Project.HasItems[CONDUIT] := True;
      aLink.Data[CONDUIT_LENGTH_INDEX]     := TokList[3];
      aLink.Data[CONDUIT_ROUGHNESS_INDEX]  := TokList[4];
      aLink.Data[CONDUIT_INLET_HT_INDEX]   := TokList[5];
      aLink.Data[CONDUIT_OUTLET_HT_INDEX]  := TokList[6];
      if Ntoks > 7 then aLink.Data[CONDUIT_INIT_FLOW_INDEX] := TokList[7];
      if Ntoks > 8 then aLink.Data[CONDUIT_MAX_FLOW_INDEX] := TokList[8];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadPumpData: Integer;
//-----------------------------------------------------------------------------
//  Reads pump data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := PUMP;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[PUMP].Data, aLink.Data);
      Project.Lists[PUMP].AddObject(ID, aLink);
      Project.HasItems[PUMP] := True;

      // Skip over PumpType if line has old format
      if Uutils.FindKeyWord(TokList[3], PumpTypes, 5) >= 0 then N := 4
      else N := 3;
      if Ntoks <= N then Result := ErrMsg(ITEMS_ERR, '')
      else
      begin
        aLink.Data[PUMP_CURVE_INDEX] := TokList[N];
        if nToks > N+1 then aLink.Data[PUMP_STATUS_INDEX] := TokList[N+1];
        if nToks > N+2 then aLink.Data[PUMP_STARTUP_INDEX] := TokList[N+2];
        if nToks > N+3 then aLink.Data[PUMP_SHUTOFF_INDEX] := TokList[N+3];
        aLink.Data[COMMENT_INDEX ] := Comment;
        Result := 0;
      end;
    end;
  end;
end;


function ReadOrificeData: Integer;
//-----------------------------------------------------------------------------
//  Reads orifice data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := ORIFICE;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[ORIFICE].Data, aLink.Data);
      Project.Lists[ORIFICE].AddObject(ID, aLink);
      Project.HasItems[ORIFICE] := True;
      aLink.Data[ORIFICE_TYPE_INDEX]      := TokList[3];
      aLink.Data[ORIFICE_BOTTOM_HT_INDEX] := TokList[4];
      aLink.Data[ORIFICE_COEFF_INDEX]     := TokList[5];
      if nToks >= 7
      then aLink.Data[ORIFICE_FLAPGATE_INDEX] := TokList[6];
      if nToks >= 8
      then aLink.Data[ORIFICE_ORATE_INDEX] := TokList[7];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadWeirData: Integer;
//-----------------------------------------------------------------------------
//  Reads weir data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := WEIR;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[WEIR].Data, aLink.Data);
      Project.Lists[WEIR].AddObject(ID, aLink);
      Project.HasItems[WEIR] := True;
      aLink.Data[WEIR_TYPE_INDEX]  := TokList[3];
      aLink.Data[WEIR_CREST_INDEX] := TokList[4];
      aLink.Data[WEIR_COEFF_INDEX] := TokList[5];

////  Following section modified for release 5.1.007.  ////                    //(5.1.007)
      if (nToks >= 7) and not SameText(TokList[6], '*') then
        aLink.Data[WEIR_FLAPGATE_INDEX] := TokList[6];
      if (nToks >= 8) and not SameText(TokList[7], '*') then
        aLink.Data[WEIR_CONTRACT_INDEX] := TokList[7];
      if (nToks >= 9) and not SameText(TokList[8], '*') then
        aLink.Data[WEIR_END_COEFF_INDEX] := TokList[8];
      if (nToks >= 10) and not SameText(TokList[9], '*') then
        aLink.Data[WEIR_SURCHARGE_INDEX] := TokList[9];

////  Following section added for release 5.1.010.                             //(5.1.010)
      if (nToks >= 11) and not SameText(TokList[10], '*') then
        aLink.Data[WEIR_ROAD_WIDTH_INDEX] := TokList[10];
      if (nToks >= 12) and not SameText(TokList[11], '*') then
        aLink.Data[WEIR_ROAD_SURF_INDEX] := TokList[11];
////

      aLink.Data[COMMENT_INDEX] := Comment;
      Result := 0;
    end;
  end;
end;


function ReadOutletData: Integer;
//-----------------------------------------------------------------------------
//  Reads outlet data from a line of input.
//-----------------------------------------------------------------------------
var
  aLink : TLink;
  aNode1: TNode;
  aNode2: TNode;
  ID    : String;
  N     : Integer;
begin
  if nToks < 6
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aNode1 := FindNode(TokList[1]);
    aNode2 := FindNode(TokList[2]);
    if (aNode1 = nil) then Result := ErrMsg(NODE_ERR, TokList[1])
    else if (aNode2 = nil) then Result := ErrMsg(NODE_ERR, TokList[2])
    else begin
      aLink := TLink.Create;
      LinkList.AddObject(ID, aLink);
      aLink.Ltype := OUTLET;
      aLink.Node1 := aNode1;
      aLink.Node2 := aNode2;
      aLink.Zindex := -1;
      Uutils.CopyStringArray(Project.DefProp[OUTLET].Data, aLink.Data);
      Project.Lists[OUTLET].AddObject(ID, aLink);
      Project.HasItems[OUTLET] := True;
      aLink.Data[OUTLET_CREST_INDEX] := TokList[3];

      //... added for backwards compatibility
      if SameText(TokList[4], 'TABULAR') then TokList[4] := 'TABULAR/DEPTH';
      if SameText(TokList[4], 'FUNCTIONAL') then TokList[4] := 'FUNCTIONAL/DEPTH';
      aLink.Data[OUTLET_TYPE_INDEX]  := TokList[4];

      if AnsiContainsText(TokList[4], 'TABULAR') then
      begin
        aLink.Data[OUTLET_QTABLE_INDEX] := TokList[5];
        N := 6;
      end
      else begin
        if Ntoks < 7 then
        begin
          Result := ErrMsg(ITEMS_ERR, '');
          Exit;
        end
        else
        begin
          aLink.Data[OUTLET_QCOEFF_INDEX] := TokList[5];
          aLink.Data[OUTLET_QEXPON_INDEX] := TokList[6];
          N := 7;
        end;
      end;
      if Ntoks > N then aLink.Data[OUTLET_FLAPGATE_INDEX] := TokList[N];
      aLink.Data[COMMENT_INDEX ] := Comment;
      Result := 0;
    end;
  end;
end;


function GetXsectShape(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Finds the code number corresponding to cross section shape S.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  for I := 0 to High(Dxsect.XsectShapes) do
  begin
    if CompareText(S, Dxsect.XsectShapes[I].Text[1]) = 0 then
    begin
      Result := I;
      Exit;
    end;
  end;
  Result := -1;
end;

////  New procedure added to release 5.1.008.  ////                            //(5.1.008)
procedure CheckForStdSize;
//-----------------------------------------------------------------------------
//  Converts from old format for standard size ellipse and arch pipes
//  to new format.
//-----------------------------------------------------------------------------
var
  J: Integer;
  X: Extended;
begin
// Old format had size code as 3rd token and 0 for 4th token
  J := StrToIntDef(TokList[2], 0);
  Uutils.GetExtended(TokList[3], X);
  if (J > 0) and (X = 0) then
  begin
  // New format has 5th token as size code
    TokList[4] := TokList[2];
    TokList[2] := '0';
    TokList[3] := '0';
  end;
end;

function ReadXsectionData: Integer;
//-----------------------------------------------------------------------------
//  Reads cross section data froma line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  ID    : String;
  aLink : TLink;
begin
  if nToks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil)
    then Result := ErrMsg(LINK_ERR, TokList[0])
    else begin
      I := GetXsectShape(TokList[1]);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else case aLink.Ltype of

      CONDUIT:
        begin
          aLink.Data[CONDUIT_SHAPE_INDEX] := TokList[1];
          if I = Dxsect.IRREG_SHAPE_INDEX then
          begin
            aLink.Data[CONDUIT_TSECT_INDEX] := TokList[2];
            aLink.Data[CONDUIT_GEOM1_INDEX] := '';                             //(5.1.008)
            Result := 0;
          end

          else begin
            if nToks < 6 then Result := ErrMsg(ITEMS_ERR, '')
            else begin
{
////  Added to release 5.1.008.  ////                                          //(5.1.008)
              if I in [Dxsect.HORIZ_ELLIPSE_SHAPE_INDEX,
                       Dxsect.VERT_ELLIPSE_SHAPE_INDEX,
                       Dxsect.ARCH_SHAPE_INDEX]
              then CheckForStdSize;
}
              aLink.Data[CONDUIT_GEOM1_INDEX] := TokList[2];
              aLink.Data[CONDUIT_GEOM2_INDEX] := TokList[3];
              aLink.Data[CONDUIT_GEOM3_INDEX] := TokList[4];
              aLink.Data[CONDUIT_GEOM4_INDEX] := TokList[5];
              if Ntoks > 6 then aLink.Data[CONDUIT_BARRELS_INDEX] := TokList[6];
              if Ntoks > 7 then aLink.Data[CONDUIT_CULVERT_INDEX] := TokList[7];
              if I = Dxsect.CUSTOM_SHAPE_INDEX then
                aLink.Data[CONDUIT_TSECT_INDEX] := TokList[3];
              Result := 0;
            end;
          end;
        end;

      ORIFICE:
        begin
          if not I in [0, 1] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[ORIFICE_SHAPE_INDEX]  := TokList[1];
            aLink.Data[ORIFICE_HEIGHT_INDEX] := TokList[2];
            aLink.Data[ORIFICE_WIDTH_INDEX]  := TokList[3];
            Result := 0;
          end;
        end;

      WEIR:
        begin
          if not I in [2, 3, 4] then Result := ErrMsg(XSECT_ERR, TokList[0])
          else begin
            aLink.Data[WEIR_SHAPE_INDEX]  := TokList[1];
            aLink.Data[WEIR_HEIGHT_INDEX] := TokList[2];
            aLink.Data[WEIR_WIDTH_INDEX]  := TokList[3];
            aLink.Data[WEIR_SLOPE_INDEX]  := TokList[4];
            Result := 0;
          end;
        end;

      else Result := 0;
      end;
    end;
  end;
end;


function ReadTransectData: Integer;
//-----------------------------------------------------------------------------
//  Reads transect data from a line of input.
//-----------------------------------------------------------------------------
var
  I     : Integer;
  K     : Integer;
  N     : Integer;
  ID    : String;
  Tsect : TTransect;
begin
  Result := 0;
  if SameText(TokList[0], 'NC') then
  begin
    if nToks < 4 then Result := ErrMsg(ITEMS_ERR, '')
    else for I := 1 to 3 do ManningsN[I] := TokList[I];
    TsectComment := Comment;
    Exit;
  end;

  if SameText(TokList[0], 'X1') then
  begin
    if nToks < 2 then Exit;
    ID := TokList[1];
    Tsect := TTransect.Create;

    if Length(Comment) > 0 then TsectComment := Comment;
    Tsect.Comment := TsectComment;

    Project.Lists[TRANSECT].AddObject(ID, Tsect);
    Project.HasItems[TRANSECT] := True;
    Tsect.Data[TRANSECT_N_LEFT]    := ManningsN[1];
    Tsect.Data[TRANSECT_N_RIGHT]   := ManningsN[2];
    Tsect.Data[TRANSECT_N_CHANNEL] := ManningsN[3];
    if nToks < 10 then Result := ErrMsg(ITEMS_ERR, '')
    else
    begin
      Tsect.Data[TRANSECT_X_LEFT] := TokList[3];
      Tsect.Data[TRANSECT_X_RIGHT] := TokList[4];
      Tsect.Data[TRANSECT_L_FACTOR] := TokList[7];
      Tsect.Data[TRANSECT_X_FACTOR] := TokList[8];
      Tsect.Data[TRANSECT_Y_FACTOR] := TokList[9];
    end;
    Exit;
  end;

  if SameText(TokList[0], 'GR') then
  begin
    N := Project.Lists[TRANSECT].Count;
    if N = 0 then Result := ErrMsg(TRANSECT_ERR, '')
    else begin
      Tsect := TTransect(Project.Lists[TRANSECT].Objects[N-1]);
      K := 1;
      while K + 1 < Ntoks do
      begin
        Tsect.Ydata.Add(TokList[K]);
        Tsect.Xdata.Add(TokList[K+1]);
        K := K + 2;
      end;
    end;
    Exit;
  end;
end;


function ReadLossData: Integer;
//-----------------------------------------------------------------------------
//  Reads conduit loss data from a line of input.
//-----------------------------------------------------------------------------
var
  ID    : String;
  aLink : TLink;
begin
  if nToks < 4
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    ID := TokList[0];
    aLink := FindLink(ID);
    if (aLink = nil) then Result := ErrMsg(LINK_ERR, ID)
    else if (aLink.Ltype <> CONDUIT) then Result := 0
    else begin
      aLink.Data[CONDUIT_ENTRY_LOSS_INDEX] := TokList[1];
      aLink.Data[CONDUIT_EXIT_LOSS_INDEX] := TokList[2];
      aLink.Data[CONDUIT_AVG_LOSS_INDEX] := TokList[3];
      if nToks >= 5
      then aLink.Data[CONDUIT_CHECK_VALVE_INDEX] := TokList[4];
      if nToks >= 6
      then aLink.Data[CONDUIT_SEEPAGE_INDEX] := TokList[5];
      Result := 0;
    end;
  end;
end;


function ReadPollutantData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant data from a line of input.
//-----------------------------------------------------------------------------
var
  ID      : String;
  aPollut : TPollutant;
  X       : Single;
begin
  if nToks < 5
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Add new pollutant to project
    ID := TokList[0];
    aPollut := TPollutant.Create;
    Uutils.CopyStringArray(Project.DefProp[POLLUTANT].Data, aPollut.Data);
    Project.Lists[POLLUTANT].AddObject(ID, aPollut);
    Project.HasItems[POLLUTANT] := True;

    // Parse units & concens.
    aPollut.Data[POLLUT_UNITS_INDEX] := TokList[1];
    aPollut.Data[POLLUT_RAIN_INDEX]  := TokList[2];
    aPollut.Data[POLLUT_GW_INDEX]    := TokList[3];

    // This is for old format
    if (Ntoks = 5)
    or ( (Ntoks = 7) and Uutils.GetSingle(TokList[6], X) ) then
    begin
      aPollut.Data[POLLUT_DECAY_INDEX] := TokList[4];
      if nToks >= 7 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[5];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[6];
      end;
    end

    // This is for new format
    else
    begin
      aPollut.Data[POLLUT_II_INDEX] := TokList[4];
      if Ntoks >= 6 then aPollut.Data[POLLUT_DECAY_INDEX] := TokList[5];
      if nToks >= 7 then aPollut.Data[POLLUT_SNOW_INDEX]  := TokList[6];
      if Ntoks >= 9 then
      begin
        aPollut.Data[POLLUT_COPOLLUT_INDEX] := TokList[7];
        aPollut.Data[POLLUT_FRACTION_INDEX] := TokList[8];
      end;
      if Ntoks >= 10 then aPollut.Data[POLLUT_DWF_INDEX] := TokList[9];
      if Ntoks >= 11 then aPollut.Data[POLLUT_INIT_INDEX] := TokList[10];
    end;
    Result := 0;
  end;
end;


function ReadLanduseData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use data from a line of input.
//-----------------------------------------------------------------------------
var
  ID           : String;
  aLanduse     : TLanduse;
  aNonPtSource : TNonpointSource;
  J            : Integer;
begin
    ID := TokList[0];
    aLanduse := TLanduse.Create;
    for J := 0 to Project.Lists[POLLUTANT].Count - 1 do
    begin
      aNonPtSource := TNonpointSource.Create;
      aLanduse.NonpointSources.AddObject(Project.Lists[POLLUTANT].Strings[J],
        aNonPtSource);
    end;
    Project.Lists[LANDUSE].AddObject(ID, aLanduse);
    Project.HasItems[LANDUSE] := True;
    if Ntoks > 1 then aLanduse.Data[LANDUSE_CLEANING_INDEX]  := TokList[1];
    if Ntoks > 2 then aLanduse.Data[LANDUSE_AVAILABLE_INDEX] := TokList[2];
    if Ntoks > 3 then aLanduse.Data[LANDUSE_LASTCLEAN_INDEX] := TokList[3];
    aLanduse.Data[COMMENT_INDEX ] := Comment;
    Result := 0;
end;


function ReadBuildupData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant buildup function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.BuildupData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadWashoffData: Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant washoff function data from a line of input.
//-----------------------------------------------------------------------------
var
  LanduseIndex   : Integer;
  PollutIndex    : Integer;
  aLanduse       : TLanduse;
  aNonpointSource: TNonpointSource;
  J              : Integer;
begin
  if nToks < 7
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    LanduseIndex := Project.Lists[LANDUSE].IndexOf(TokList[0]);
    PollutIndex := Project.Lists[POLLUTANT].IndexOf(TokList[1]);
    if (LanduseIndex < 0) then Result := ErrMsg(LANDUSE_ERR, TokList[0])
    else if (PollutIndex < 0) then Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      aLanduse := TLanduse(Project.Lists[LANDUSE].Objects[LanduseIndex]);
      aNonpointSource :=
        TNonpointSource(aLanduse.NonpointSources.Objects[PollutIndex]);
      for J := 2 to 6 do
        aNonpointSource.WashoffData[J-2] := TokList[J];
      Result := 0;
    end;
  end;
end;


function ReadCoverageData: Integer;
//-----------------------------------------------------------------------------
//  Reads land use coverage data from a line of input.
//-----------------------------------------------------------------------------
var
  MaxToks: Integer;
  X      : Single;
  S      : TSubcatch;
  S1     : String;
  I      : Integer;
begin
  Result := 0;
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else begin
    MaxToks := 3;
    while (MaxToks <= nToks) do
    begin
      if not Uutils.GetSingle(TokList[MaxToks-1], X) then
      begin
        Result := ErrMsg(NUMBER_ERR, TokList[MaxToks-1]);
        break;
      end;
      S1 := TokList[MaxToks-2];
      I := S.LandUses.IndexOfName(S1);
      S1 := TokList[MaxToks-2] + '=' + TokList[MaxToks-1];
      if I < 0
      then S.LandUses.Add(S1)
      else S.Landuses[I] := S1;
      MaxToks := MaxToks + 2;
    end;
    S.Data[SUBCATCH_LANDUSE_INDEX] := IntToStr(S.LandUses.Count);
  end;
end;


function ReadTreatmentData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads pollutant treatment function from a line of input.
//-----------------------------------------------------------------------------
var
  S     : String;
  aNode : TNode;
  I     : Integer;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0 then
      Result := ErrMsg(POLLUT_ERR, TokList[1])
    else begin
      I := aNode.Treatment.IndexOfName(TokList[1]);
      S := Copy(Line, Pos(TokList[1], Line)+Length(TokList[1]), Length(Line));
      S := TokList[1] + '=' + Trim(S);
      if I < 0 then
        aNode.Treatment.Add(S)
      else
        aNode.Treatment[I] := S;
      aNode.Data[NODE_TREAT_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadExInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads external inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  S     : array[1..7] of String;
  Inflow: String;
  I     : Integer;
  aNode : TNode;
begin

  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S[1] := TokList[1];                // Constituent name
      S[2] := TokList[2];                // Time Series name
      S[3] := 'FLOW';
      S[4] := '1.0';
      if not SameText(S[1], 'FLOW') then
      begin
        if nToks >= 4 then S[3] := TokList[3] else S[3] := 'CONCEN';
        if nToks >= 5 then S[4] := TokList[4] else S[4] := '1.0';
      end;
      if nToks >= 6 then S[5] := TokList[5] else S[5] := '1.0';
      if nToks >= 7 then S[6] := TokList[6] else S[6] := '';
      if nToks >= 8 then S[7] := TokList[7] else S[7] := '';
      Inflow := S[1] + '=' + S[2] + #13 + S[3] + #13 + S[4] + #13 +
                             S[5] + #13 + S[6] + #13 + S[7];
      I := aNode.DXInflow.IndexOfName(S[1]);
      if I < 0 then aNode.DXInflow.Add(Inflow)
      else aNode.DXInflow[I] := Inflow;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadDWInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads dry weather inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  M    : Integer;
  S    : String;
  S1   : String;
  I    : Integer;
  aNode: TNode;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      S1 := TokList[1];
      S := S1 + '=' + TokList[2];
      for M := 3 to Ntoks-1 do S := S + #13 + TokList[M];
      I := aNode.DWInflow.IndexOfName(S1);
      if I < 0
      then aNode.DWInflow.Add(S)
      else aNode.DWInflow[I] := S;
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadPatternData: Integer;
//-----------------------------------------------------------------------------
//  Reads time pattern data from a line of input.
//-----------------------------------------------------------------------------
var
  ID: String;
  Index: Integer;
  aPattern: TPattern;
  PatType: Integer;
  J: Integer;
begin
  if nToks < 2
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    J := 1;
    ID := TokList[0];
    Index := Project.Lists[PATTERN].IndexOf(ID);
    if Index < 0 then
    begin
      aPattern := TPattern.Create;
      aPattern.Comment := Comment;
      Project.Lists[PATTERN].AddObject(ID, aPattern);
      Project.HasItems[PATTERN] := True;
      PatType := Uutils.FindKeyWord(TokList[1], PatternTypes, 7);
      if PatType < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        exit;
      end;
      aPattern.PatternType := PatType;
      J := 2;
    end
    else aPattern := TPattern(Project.Lists[PATTERN].Objects[Index]);
    while (J < nToks) and (aPattern.Count <= High(aPattern.Data)) do
    begin
      aPattern.Data[aPattern.Count] := TokList[J];
      Inc(J);
      Inc(aPattern.Count);
    end;
    Result := 0;
  end;
end;


function ReadIIInflowData: Integer;
//-----------------------------------------------------------------------------
//  Reads RDII inflow data from a line of input.
//-----------------------------------------------------------------------------
var
  aNode: TNode;

begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    aNode := FindNode(TokList[0]);
    if (aNode = nil) then Result := ErrMsg(NODE_ERR, TokList[0])
    else begin
      aNode.IIInflow.Clear;
      aNode.IIInflow.Add(TokList[1]);
      aNode.IIInflow.Add(TokList[2]);
      aNode.Data[NODE_INFLOWS_INDEX] := 'YES';
      Result := 0;
    end;
  end;
end;


function ReadLoadData: Integer;
//-----------------------------------------------------------------------------
//  Reads initial pollutant loading data from a line of input.
//-----------------------------------------------------------------------------
var
  X  : Single;
  S  : TSubcatch;
  S1 : String;
  I  : Integer;

begin
  S := FindSubcatch(TokList[0]);
  if S = nil
  then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else if Project.Lists[POLLUTANT].IndexOf(TokList[1]) < 0
  then Result := ErrMsg(POLLUT_ERR, TokList[1])
  else if not Uutils.GetSingle(TokList[2], X)
  then Result := ErrMsg(NUMBER_ERR, TokList[2])
  else
  begin
      S1 := TokList[1] + '=' + TokList[2];
      I := S.Loadings.IndexOfName(TokList[1]);
      if I < 0
      then S.Loadings.Add(S1)
      else S.Loadings[I] := S1;
      S.Data[SUBCATCH_LOADING_INDEX] := 'YES';
      Result := 0;
  end;
end;


function ReadCurveData: Integer;
//-----------------------------------------------------------------------------
//  Reads curve data from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  K       : Integer;
  L       : Integer;
  M       : Integer;
  ObjType : Integer;
  ID      : String;
  aCurve  : TCurve;
begin
  // Check for too few tokens
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Check if curve ID is same as for previous line
    ID := TokList[0];
    if (ID = PrevID) then
    begin
      Index := PrevIndex;
      ObjType := CurveType;
    end
    else Project.FindCurve(ID, ObjType, Index);

    // Create new curve if ID not in data base
    K := 2;
    if Index < 0 then
    begin

      // Check for valid curve type keyword
      M := -1;
      for L := 0 to High(CurveTypeOptions) do
      begin
        if SameText(TokList[1], CurveTypeOptions[L]) then
        begin
          M := L;
          break;
        end;
      end;
      if M < 0 then
      begin
        Result := ErrMsg(KEYWORD_ERR, TokList[1]);
        Exit;
      end;

      // Convert curve type keyword index to a curve object category
      case M of
      0: ObjType := CONTROLCURVE;
      1: ObjType := DIVERSIONCURVE;
      2..5:
         ObjType := PUMPCURVE;
      6: ObjType := RATINGCURVE;
      7: ObjType := SHAPECURVE;
      8: ObjType := STORAGECURVE;
      9: ObjType := TIDALCURVE;
      end;

      // Create a new curve object
      aCurve := TCurve.Create;
      aCurve.Comment := Comment;
      aCurve.CurveType := TokList[1];
      if ObjType = PUMPCURVE
      then aCurve.CurveCode := M - 1
      else aCurve.CurveCode := 0;
      Project.Lists[ObjType].AddObject(ID, aCurve);
      Project.HasItems[ObjType] := True;
      Index := Project.Lists[ObjType].Count - 1;
      PrevID := ID;
      PrevIndex := Index;
      CurveType := ObjType;
      K := 3;
    end;

    // Add x,y values to the list maintained by the curve
    aCurve := TCurve(Project.Lists[ObjType].Objects[Index]);
    while K <= nToks-1 do
    begin
      aCurve.Xdata.Add(TokList[K-1]);
      aCurve.Ydata.Add(TokList[K]);
      K := K + 2;
    end;
    Result := 0;
  end;
end;


function ReadTimeseriesData: Integer;
//-----------------------------------------------------------------------------
//  Reads time series data from a line of input.
//-----------------------------------------------------------------------------
const
  NEEDS_DATE = 1;
  NEEDS_TIME = 2;
  NEEDS_VALUE = 3;
var
  Index     : Integer;
  State     : Integer;
  K         : Integer;
  ID        : String;
  StrDate   : String;
  aTseries  : TTimeseries;
begin
  // Check for too few tokens
  Result := -1;
  if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '');

  // Check if series ID is same as for previous line
  ID := TokList[0];
  if (ID = PrevID) then Index := PrevIndex
  else Index := Project.Lists[TIMESERIES].IndexOf(ID);

  // If starting input for a new series then create it
  if Index < 0 then
  begin
    aTseries := TTimeseries.Create;
    aTseries.Comment := Comment;
    Project.Lists[TIMESERIES].AddObject(ID,aTseries);
    Project.HasItems[TIMESERIES] := True;
    Index := Project.Lists[TIMESERIES].Count - 1;
    PrevID := ID;
    PrevIndex := Index;
  end;
  aTseries := TTimeseries(Project.Lists[TIMESERIES].Objects[Index]);

  // Check if external file name used
  if SameText(TokList[1], 'FILE') then
  begin
    aTseries.Filename := TokList[2];
    Result := 0;
    Exit;
  end;

  // Add values to the list maintained by the timeseries
  State := NEEDS_DATE;
  K := 1;
  while K < nToks do
  begin
    case State of

    NEEDS_DATE:
      begin
        try
          StrDate := Uutils.ConvertDate(TokList[K]);
          StrToDate(StrDate, MyFormatSettings);
          aTseries.Dates.Add(StrDate);
          Inc(K);
          if K >= nToks then break;
        except
          On EconvertError do aTseries.Dates.Add('');
        end;
        State := NEEDS_TIME;
      end;

    NEEDS_TIME:
      begin
        aTseries.Times.Add(TokList[K]);
        Inc(K);
        if K >= nToks then break;
        State := NEEDS_VALUE;
      end;

    NEEDS_VALUE:
      begin
        aTseries.Values.Add(TokList[K]);
        Result := 0;
        State := NEEDS_DATE;
        Inc(K);
      end;
    end;
  end;
  if Result = -1 then Result := ErrMsg(ITEMS_ERR, '');
end;


function ReadControlData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads a control rule statement from line of input.
//-----------------------------------------------------------------------------
begin
  Project.ControlRules.Add(Line);
  Result := 0;
end;


function ReadLidUsageData: Integer;
//-----------------------------------------------------------------------------
//  Reads LID usage data from line of input.
//-----------------------------------------------------------------------------
var
  aSubcatch: TSubcatch;
begin
  aSubcatch := FindSubcatch(TokList[0]);
  if aSubcatch = nil then Result := ErrMsg(SUBCATCH_ERR, TokList[0])
  else Result := Ulid.ReadLIDUsageData(aSubcatch, TokList, Ntoks);
end;


procedure ReadReportOption(Index: Integer);
begin
  if (Ntoks >= 2) and SameText(TokList[1], 'YES') then
    Project.Options.Data[Index] := 'YES'
  else
    Project.Options.Data[Index] := 'NO';
end;


function ReadReportData: Integer;
//-----------------------------------------------------------------------------
//  Reads reporting options from a line of input.
//-----------------------------------------------------------------------------
begin
  if SameText(TokList[0], 'CONTROLS')
  then ReadReportOption(REPORT_CONTROLS_INDEX)
  else if SameText(TokList[0], 'INPUT')
  then ReadReportOption(REPORT_INPUT_INDEX)
  else ReportingForm.Import(TokList, Ntoks);
  Result := 0;
end;


function ReadFileData: Integer;
//-----------------------------------------------------------------------------
//  Reads interface file usage from a line of input.
//-----------------------------------------------------------------------------
var
  Fname: String;
begin
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    Fname := TokList[2];
    if ExtractFilePath(Fname) = ''
    then Fname := ExtractFilePath(Uglobals.InputFileName) + Fname;
    Project.IfaceFiles.Add(TokList[0] + ' ' + TokList[1] + ' ' +
     '"' + Fname + '"');
    Result := 0;
  end;
end;


////  This function was added to release 5.1.007.  ////                        //(5.1.007)
function ReadAdjustmentData: Integer;
//-----------------------------------------------------------------------------
//  Reads climate adjustments from a line of input.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  Result := 0;
  if Ntoks < 2 then exit;
  if SameText(TokList[0], 'Temperature') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then TempAdjust[I] := ''
        else TempAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Evaporation') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 0.0) = 0.0
        then EvapAdjust[I] := ''
        else EvapAdjust[I] := TokList[I+1];
    end;
  end
  else if SameText(TokList[0], 'Rainfall') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then RainAdjust[I] := ''
        else RainAdjust[I] := TokList[I+1];
    end;
  end

////  Added to release 5.1.008.  ////                                          //(5.1.008)
  else if SameText(TokList[0], 'Conductivity') then
  begin
    if Ntoks < 13 then Result := ErrMsg(ITEMS_ERR, '')
    else with Project.Climatology do
    begin
      for I := 0 to 11 do
        if StrToFloatDef(TokList[I+1], 1.0) = 1.0
        then CondAdjust[I] := ''
        else CondAdjust[I] := TokList[I+1];
    end;
  end;
////////////////////////////////////////////////////////
end;


////  This function was added to release 5.1.011.  ////                        //(5.1.011)
function ReadEventData(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Reads hydraulic event data from a line of input.
//-----------------------------------------------------------------------------
begin
  Result := 0;
  if Length(Line) = 0 then exit;
  if LeftStr(Line,2) = ';;' then exit;
  Project.Events.Add(Line);
end;


function ReadOptionData: Integer;
//-----------------------------------------------------------------------------
//  Reads an analysis option from a line of input.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  Keyword : String;
  S : String;
  S2: String;
  I : Integer;
  X : Single;
  T : Extended;
begin
  // Check which keyword applies
  Result := 0;
  if Ntoks < 2 then exit;
  Keyword := TokList[0];
  if SameText(Keyword, 'TEMPDIR') then exit;
  Index := Uutils.FindKeyWord(Keyword, OptionLabels, 17);
  case Index of

    -1:  Result := ErrMsg(KEYWORD_ERR, Keyword);

    ROUTING_MODEL_INDEX:
    begin
      if SameText(TokList[1], 'NONE') then
      begin
         Project.Options.Data[IGNORE_ROUTING_INDEX] := 'YES';
         Exit;
      end;

      I := Uutils.FindKeyWord(TokList[1], OldRoutingOptions, 3);
      if I >= 0 then TokList[1] := RoutingOptions[I];
    end;

    START_DATE_INDEX, REPORT_START_DATE_INDEX, END_DATE_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      try
        StrToDate(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    START_TIME_INDEX, REPORT_START_TIME_INDEX, END_TIME_INDEX:
    begin
      S := TokList[1];
      try
        StrToTime(S, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    SWEEP_START_INDEX, SWEEP_END_INDEX:
    begin
      S := Uutils.ConvertDate(TokList[1]);
      S2 := S + '/1947';
      try
        StrToDate(S2, MyFormatSettings);
        TokList[1] := S;
      except
        on EConvertError do Result := ErrMsg(DATE_ERR, '');
      end;
    end;

    WET_STEP_INDEX, DRY_STEP_INDEX, REPORT_STEP_INDEX:
    begin
      S := TokList[1];
      if Uutils.StrHoursToTime(S) = -1 then Result := ErrMsg(TIMESTEP_ERR, '');
    end;

    ROUTING_STEP_INDEX:
    begin
      S := TokList[1];
      T := 0.0;
      if not Uutils.GetExtended(S, T) then
      begin
        T := Uutils.StrHoursToTime(S)*86400.;
        if T <= 0.0 then Result := ErrMsg(TIMESTEP_ERR, '')
        else TokList[1] := Format('%.0f',[T]);
      end;
    end;

    VARIABLE_STEP_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
        TokList[1] := IntToStr(Round(100.0*X))
      else
        TokList[1] := '0';
    end;

    INERTIAL_DAMPING_INDEX:
    begin
      if Uutils.GetSingle(TokList[1], X) then
      begin
        if X = 0 then TokList[1] := 'NONE'
        else TokList[1] := 'PARTIAL';
      end;
    end;

    // This option is now fixed to SWMM 4.
    COMPATIBILITY_INDEX:
    begin
      TokList[1] := '4';
    end;

    MIN_ROUTE_STEP_INDEX,                                                      //(5.1.008)
    LENGTHEN_STEP_INDEX,
    MIN_SURFAREA_INDEX,
    MIN_SLOPE_INDEX,
    MAX_TRIALS_INDEX,
    HEAD_TOL_INDEX,
    SYS_FLOW_TOL_INDEX,
    LAT_FLOW_TOL_INDEX:
    begin
      Uutils.GetSingle(TokList[1], X);
      if X <= 0 then TokList[1] := '0';
    end;

    NORMAL_FLOW_LTD_INDEX:
    begin
      if SameText(TokList[1], 'NO') or SameText(TokList[1], 'SLOPE')
      then TokList[1] := 'SLOPE'
      else if SameText(TokList[1], 'YES') or SameText(TokList[1], 'FROUDE')
      then TokList[1] := 'FROUDE'
      else if SameText(TokList[1], 'BOTH') then TokList[1] := 'BOTH'
      else Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

    FORCE_MAIN_EQN_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], ForceMainEqnOptions, 3);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := ForceMainEqnOptions[I];
    end;

    LINK_OFFSETS_INDEX:
    begin
      I := Uutils.FindKeyWord(TokList[1], LinkOffsetsOptions, 10);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else TokList[1] := LinkOffsetsOptions[I];
    end;

    IGNORE_RAINFALL_INDEX,
    IGNORE_SNOWMELT_INDEX,
    IGNORE_GRNDWTR_INDEX,
    IGNORE_ROUTING_INDEX,
    IGNORE_QUALITY_INDEX:
    begin
      if not SameText(TokList[1], 'YES') and not SameText(TokList[1], 'NO')
      then Result := ErrMsg(KEYWORD_ERR, TokList[1]);
    end;

////  Following section added to release 5.1.010.  ////                        //(5.1.010)
    NUM_THREADS_INDEX:
    begin
      I := StrToIntDef(TokList[1], 1);
      if I < 0 then I := 1;
      if (I = 0) or (I > Uutils.GetCPUs) then I := Uutils.GetCPUs;
      TokList[1] := IntToStr(I);
    end;
////

  end;
  if Result = 0 then Project.Options.Data[Index] := TokList[1];
end;


function ReadTagData: Integer;
//-----------------------------------------------------------------------------
//  Reads in tag data from a line of input.
//-----------------------------------------------------------------------------
const
  TagTypes: array[0..3] of PChar = ('Gage', 'Subcatch', 'Node', 'Link');
var
  I, J : Integer;
  aNode: TNode;
  aLink: TLink;
begin
  Result := 0;
  if Ntoks < 3
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    I := Uutils.FindKeyWord(TokList[0], TagTypes, 4);
    case I of

    -1: Result := ErrMsg(KEYWORD_ERR, TokList[0]);

     0: begin
          J := Project.Lists[RAINGAGE].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetGage(J) do Data[TAG_INDEX] := TokList[2];
        end;

     1: begin
          J := Project.Lists[SUBCATCH].IndexOf(TokList[1]);
          if J >= 0 then with Project.GetSubcatch(SUBCATCH, J) do
            Data[TAG_INDEX] := TokList[2];
        end;

     2: begin
          aNode := FindNode(TokList[1]);
          if (aNode <> nil) then aNode.Data[TAG_INDEX] := TokList[2];
        end;

     3: begin
          aLink := FindLink(TokList[1]);
          if (aLink <> nil) then aLink.Data[TAG_INDEX] := TokList[2];
        end;
     end;
  end;
end;


function ReadSymbolData: Integer;
//-----------------------------------------------------------------------------
//  Reads rain gage coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  J     : Integer;
  X, Y  : Extended;
  aGage : TRaingage;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the gage ID in the database
    J := Project.Lists[RAINGAGE].IndexOf(TokList[0]);

    // If gage exists then assign it X & Y coordinates
    if (J >= 0) then
    begin
      aGage := Project.GetGage(J);
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
          Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aGage.X := X;
        aGage.Y := Y;
      end;
    end;
  end;
end;


function ReadMapData: Integer;
//-----------------------------------------------------------------------------
//  Reads map dimensions data from a line of input.
//-----------------------------------------------------------------------------
var
  I       : Integer;
  Index   : Integer;
  Keyword : String;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, MapWords, 4);
  case Index of

    0:  // Map dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for I := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Dimensions do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
          MapExtentSet := True;
        end;
      end;
    end;

    1:  //Map units
    if Ntoks > 1 then
    begin
      I := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
      with MapForm.Map.Dimensions do
      begin
        if Units = muDegrees then Digits := MAXDEGDIGITS
        else Digits := Umap.DefMapDimensions.Digits;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadCoordData: Integer;
//-----------------------------------------------------------------------------
//  Reads node coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aNode : TNode;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the node ID in the database
    aNode := FindNode(TokList[0]);

    // If node exists then assign it X & Y coordinates
    if (aNode <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        aNode.X := X;
        aNode.Y := Y;
      end;
    end;
  end;
end;


function ReadVertexData: Integer;
//-----------------------------------------------------------------------------
//  Reads link vertex coordinate data from a line of input.
//-----------------------------------------------------------------------------
var
  X, Y  : Extended;
  aLink : TLink;

begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the link ID in the database
    aLink := FindLink(TokList[0]);;

    // If link exists then assign it X & Y coordinates
    if (aLink <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  aLink.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadPolygonData: Integer;
//-----------------------------------------------------------------------------
//  Reads polygon coordinates associated with subcatchment outlines.
//-----------------------------------------------------------------------------
var
  Index : Integer;
  X, Y  : Extended;
  S     : TSubcatch;
begin
  Result := 0;
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin

    // Locate the subcatchment ID in the database
    S := nil;
    Index := Project.Lists[SUBCATCH].IndexOf(TokList[0]);
    if Index >= 0 then S := Project.GetSubcatch(SUBCATCH, Index);

    // If subcatchment exists then add a new vertex to it
    if (S <> nil) then
    begin
      if not Uutils.GetExtended(TokList[1], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else  S.Vlist.Add(X, Y);
    end;
  end;
end;


function ReadLabelData: Integer;
//-----------------------------------------------------------------------------
//  Reads map label data from a line of input.
//-----------------------------------------------------------------------------
var
  Ntype    : Integer;
  Index    : Integer;
  X, Y     : Extended;
  S        : String;
  aMapLabel: TMapLabel;
begin
  if (Ntoks < 3)
  then Result := ErrMsg(ITEMS_ERR, '')
  else begin
    if not Uutils.GetExtended(TokList[0], X) then
        Result := ErrMsg(NUMBER_ERR, TokList[0])
    else if not Uutils.GetExtended(TokList[1], Y) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
    else begin
      S := TokList[2];
      aMapLabel := TMapLabel.Create;
      aMapLabel.X := X;
      aMapLabel.Y := Y;
      Project.Lists[MAPLABEL].AddObject(S, aMapLabel);
      Index := Project.Lists[MAPLABEL].Count - 1;
      aMapLabel.Text := PChar(Project.Lists[MAPLABEL].Strings[Index]);
      Project.HasItems[MAPLABEL] := True;
      if Ntoks >= 4 then
      begin
        if (Length(TokList[3]) > 0) and
          Project.FindNode(TokList[3], Ntype, Index) then
            aMapLabel.Anchor := Project.GetNode(Ntype, Index);
      end;
      if Ntoks >= 5 then aMapLabel.FontName := TokList[4];
      if Ntoks >= 6 then aMapLabel.FontSize := StrToInt(TokList[5]);
      if Ntoks >= 7 then
        if StrToInt(TokList[6]) = 1 then aMapLabel.FontBold := True;
      if Ntoks >= 8 then
        if StrToInt(TokList[7]) = 1 then aMapLabel.FontItalic := True;
      Result := 0;
    end;
  end;
end;


function ReadBackdropData: Integer;
//-----------------------------------------------------------------------------
//  Reads map backdrop image information from a line of input.
//-----------------------------------------------------------------------------
var
  Index   : Integer;
  Keyword : String;
  I       : Integer;
  X       : array[1..4] of Extended;
begin
  // Check which keyword applies
  Result := 0;
  Keyword := TokList[0];
  Index := Uutils.FindKeyWord(Keyword, BackdropWords, 4);
  case Index of

    0:  //Backdrop file
    if Ntoks > 1 then MapForm.Map.Backdrop.Filename := TokList[1];

    1:  // Backdrop dimensions
    begin
      if Ntoks < 5 then Result := ErrMsg(ITEMS_ERR, '')
      else begin
        for i := 1 to 4 do
          if not Uutils.GetExtended(TokList[I], X[I]) then
            Result := ErrMsg(NUMBER_ERR, TokList[I]);
        if Result = 0 then with MapForm.Map.Backdrop do
        begin
          LowerLeft.X := X[1];
          LowerLeft.Y := X[2];
          UpperRight.X := X[3];
          UpperRight.Y := X[4];
        end;
      end;
    end;

    2:  //Map units - deprecated
    if Ntoks > 1 then
    begin
      i := Uutils.FindKeyWord(Copy(TokList[1], 1, 1), MapUnits, 1);
      if I < 0 then Result := ErrMsg(KEYWORD_ERR, TokList[1])
      else MapForm.Map.Dimensions.Units := TMapUnits(I);
    end;

    3, 4:  //Backdrop offset or scaling -- deprecated
    begin
      if Ntoks < 3 then Result := ErrMsg(ITEMS_ERR, '')
      else if not Uutils.GetExtended(TokList[1], X[1]) then
        Result := ErrMsg(NUMBER_ERR, TokList[1])
      else if not Uutils.GetExtended(TokList[2], X[2]) then
        Result := ErrMsg(NUMBER_ERR, TokList[2])
      else
      begin
        if Index = 3 then
        begin
          BackdropX := X[1];
          BackdropY := X[2];
        end
        else
        begin
          BackdropW := X[1];
          BackdropH := X[2];
        end;
      end;
    end;

    else Result := ErrMsg(KEYWORD_ERR, Keyword);
  end;
end;


function ReadProfileData: Integer;
//-----------------------------------------------------------------------------
//  Reads profile plot data from a line of input.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
  S   : String;
begin
  Result := 0;
  if (Ntoks < 2) then Exit;

  // Locate the profile name in the database
  I := Project.ProfileNames.IndexOf(TokList[0]);

  // If profile does not exist then create it
  if (I < 0) then
  begin
    Project.ProfileNames.Add(TokList[0]);
    I := Project.ProfileNames.Count-1;
    S := '';
    Project.ProfileLinks.Add(S);
  end
  else S := Project.ProfileLinks[I] + #13;

  // Add each remaining token to the list of links in the profile
  S := S + TokList[1];
  for J := 2 to Ntoks-1 do
    S := S + #13 + TokList[J];
  Project.ProfileLinks[I] := S;
end;


procedure SetMapDimensions;
//-----------------------------------------------------------------------------
// Determines map dimensions based on range of object coordinates.
//-----------------------------------------------------------------------------
begin
  with MapForm.Map do
  begin
    // Dimensions not provided in [MAP] section
    if not MapExtentSet then
    begin

      // Dimensions were provided in [BACKDROP] section
      if (Backdrop.LowerLeft.X <> Backdrop.UpperRight.X) then
      begin

        // Interpret these as map dimensions
        Dimensions.LowerLeft  := Backdrop.LowerLeft;
        Dimensions.UpperRight := Backdrop.UpperRight;

        // Compute backdrop dimensions from Offset and Scale values
        Backdrop.LowerLeft.X  := BackdropX;
        Backdrop.LowerLeft.Y  := BackdropY - BackdropH;
        Backdrop.UpperRight.X := BackdropX + BackdropW;
        Backdrop.UpperRight.Y := BackdropY;
      end

      // No dimensions of any kind provided
      else with Dimensions do
        Ucoords.GetCoordExtents(LowerLeft.X, LowerLeft.Y,
                                UpperRight.X, UpperRight.Y);
    end;
  end;
end;


function ParseInpLine(S: String): Integer;
//-----------------------------------------------------------------------------
//  Parses current input line depending on current section of input file.
//-----------------------------------------------------------------------------
begin
  case Section of
    1:    Result := ReadOptionData;
    2:    Result := ReadRaingageData;
    3:    Result := ReadHydrographData;
    4:    Result := ReadEvaporationData;
    5:    Result := ReadSubcatchmentData;
    6:    Result := ReadSubareaData;
    7:    Result := ReadInfiltrationData;
    8:    Result := ReadAquiferData;
    9:    Result := ReadGroundwaterData;
    10:   Result := ReadJunctionData;
    11:   Result := ReadOutfallData;
    12:   Result := ReadStorageData;
    13:   Result := ReadDividerData;
    14:   Result := ReadConduitData;
    15:   Result := ReadPumpData;
    16:   Result := ReadOrificeData;
    17:   Result := ReadWeirData;
    18:   Result := ReadOutletData;
    19:   Result := ReadXsectionData;
    20:   Result := ReadTransectData;
    21:   Result := ReadLossData;
    //22: ReadControlData called directly from ReadFile
    23:   Result := ReadPollutantData;
    24:   Result := ReadLanduseData;
    25:   Result := ReadBuildupData;
    26:   Result := ReadWashoffData;
    27:   Result := ReadCoverageData;
    28:   Result := ReadExInflowData;
    29:   Result := ReadDWInflowData;
    30:   Result := ReadPatternData;
    31:   Result := ReadIIInflowData;
    32:   Result := ReadLoadData;
    33:   Result := ReadCurveData;
    34:   Result := ReadTimeseriesData;
    35:   Result := ReadReportData;
    36:   Result := ReadFileData;
    37:   Result := ReadMapData;
    38:   Result := ReadCoordData;
    39:   Result := ReadVertexdata;
    40:   Result := ReadPolygonData;
    41:   Result := ReadSymbolData;
    42:   Result := ReadLabelData;
    43:   Result := ReadBackdropData;
    44:   Result := ReadProfileData;
    45:   Result := ReadCurveData;
    46:   Result := ReadTemperatureData;
    47:   Result := ReadSnowpackData;
    48:   Result := ReadTreatmentData(S);
    49:   Result := ReadTagData;
    50:   Result := Ulid.ReadLidData(TokList, Ntoks);
    51:   Result := ReadLidUsageData;
    52:   Result := ReadGroundwaterFlowEqn(S);
    53:   Result := ReadAdjustmentData;                                        //(5.1.007)
    else  Result := 0;
  end;
end;


function ParseImportLine(Line: String): Integer;
//-----------------------------------------------------------------------------
//  Processes line of input from imported scenario or map file.
//  (Not currently used.)
//-----------------------------------------------------------------------------
begin
  Result := 0;
end;


procedure StripComment(const Line: String; var S: String);
//-----------------------------------------------------------------------------
//  Strips comment (text following a ';') from a line of input.
//  Ignores comment if it begins with ';;'.
//-----------------------------------------------------------------------------
var
  P: Integer;
  N: Integer;
  C: String;
begin
  C := '';
  S := Trim(Line);
  P := Pos(';', S);
  if P > 0 then
  begin
    N := Length(S);
    C := Copy(S, P+1, N);
    if (Length(C) >= 1) and (C[1] <> ';') then
    begin
      if Length(Comment) > 0 then Comment := Comment + #13;
      Comment := Comment + C;
    end;
    Delete(S, P, N);
  end;
end;


function FindNewSection(const S: String): Integer;
//-----------------------------------------------------------------------------
//  Checks if S matches any of the section heading key words.
//-----------------------------------------------------------------------------
var
  K: Integer;
begin
  for K := 0 to High(SectionWords) do
  begin
    if Pos(SectionWords[K], S) = 1 then
    begin
      Result := K;
      Exit;
    end;
  end;
  Result := -1;
end;


function StartNewSection(S: String): Integer;
//-----------------------------------------------------------------------------
//  Begins reading a new section of the input file.
//-----------------------------------------------------------------------------
var
  K : Integer;

begin
  // Determine which new section to begin
  K := FindNewSection(UpperCase(S));
  if (K >= 0) then
  begin

    //Update section code
    Section := K;
    PrevID := '';
    Result := 0;
  end
  else Result := ErrMsg(KEYWORD_ERR, S);
  Comment := '';
end;


function ReadFile(var F: Textfile; const Fsize: Int64):Boolean;
//-----------------------------------------------------------------------------
//  Reads each line of a SWMM input file.
//-----------------------------------------------------------------------------
var
  Err         : Integer;
  ByteCount   : Integer;
  StepCount   : Integer;
  StepSize    : Integer;
  S           : String;
begin
  Result := True;
  ErrCount := 0;
  LineCount := 0;
  Comment := '';

  // Initialize progress meter settings
  StepCount := MainForm.ProgressBar.Max div MainForm.ProgressBar.Step;
  StepSize := Fsize div StepCount;
  if StepSize < 1000 then StepSize := 0;
  ByteCount := 0;

  // Read each line of input file
  Reset(F);
  while not Eof(F) do
  begin
    Err := 0;
    Readln(F, Line);
    Inc(LineCount);
    if StepSize > 0 then
    begin
      Inc(ByteCount, Length(Line));
      MainForm.UpdateProgressBar(ByteCount, StepSize);
    end;

    // Strip out trailing spaces, control characters & comment
    Line := TrimRight(Line);
    StripComment(Line, S);

    // Check if line begins a new input section
    if (Pos('[', S) = 1) then Err := StartNewSection(S)
    else
    begin

////  Following code section modified for release 5.1.011.  ////               //(5.1.011)
      // Check if line contains project title/notes
      if (Section = 0) and (Length(Line) > 0) then
      begin
        if LeftStr(Line,2) <> ';;' then Err := ReadTitleData(Line);
      end

      // Check if line contains a control rule clause
      else if (Section = 22) then Err := ReadControlData(Line)

      // Check if line contains an event start/end dates
      else if (Section = 54) then Err := ReadEventData(Trim(Line))

      // If in some section, then process the input line
      else
      begin
        // Break line into string tokens and parse their contents
        Uutils.Tokenize(S, TokList, Ntoks);
        if (Ntoks > 0) and (Section >= 0) then
        begin
          Err := ParseInpLine(S);
          Comment := '';
        end

        // No current section -- file was probably not an EPA-SWMM file
        else if (Ntoks > 0) then
        begin
          Result := False;
          Exit;
        end;
      end;
    end;
////

    // Increment error count
    if Err > 0 then Inc(ErrCount);
  end;  //End of file.

  if ErrCount > MAX_ERRORS then ErrList.Add(
    IntToStr(ErrCount-MAX_ERRORS) + TXT_MORE_ERRORS);
end;


procedure DisplayInpErrForm(const Fname: String);
//-----------------------------------------------------------------------------
//  Displays Status Report form that lists any error messages.
//-----------------------------------------------------------------------------
begin
  SysUtils.DeleteFile((TempReportFile));
  TempReportFile := Uutils.GetTempFile(TempDir,'swmm');
  ErrList.Insert(0, TXT_ERROR_REPORT + Fname + #13);
  ErrList.SaveToFile(TempReportFile);
  MainForm.MnuReportStatusClick(MainForm);
end;


procedure ReverseVertexLists;
//-----------------------------------------------------------------------------
//  Reverses list of vertex points for each link in the project.
//-----------------------------------------------------------------------------
var
  I, J: Integer;
begin
  for I := 0 to MAXCLASS do
  begin
    if not Project.IsLink(I) then continue;
    for J := 0 to Project.Lists[I].Count-1 do
      if Project.GetLink(I, J).Vlist <> nil then
        Project.GetLink(I, J).Vlist.Reverse;
  end;
end;


procedure SetSubcatchCentroids;
//-----------------------------------------------------------------------------
//  Determines the centroid of each subcatchment polygon.
//-----------------------------------------------------------------------------
var
  I : Integer;
begin
  for I := 0 to Project.Lists[SUBCATCH].Count - 1 do
  begin
    Project.GetSubcatch(SUBCATCH, I).SetCentroid;
  end;
end;


procedure SetIDPtrs;
//-----------------------------------------------------------------------------
//  Makes pointers to ID strings the ID property of objects.
//-----------------------------------------------------------------------------
var
  I, J : Integer;
  C : TSubcatch;
  S : String;

begin
  for I := 0 to MAXCLASS do
  begin
    if I = RAINGAGE then with Project.Lists[RAINGAGE] do
    begin
      for J := 0 to Count-1 do TRaingage(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsSubcatch(I) then with Project.Lists[SUBCATCH] do
    begin
      for J := 0 to Count-1 do
      begin
        C := TSubcatch(Objects[J]);
        C.ID := PChar(Strings[J]);
        S := Trim(C.Data[SUBCATCH_OUTLET_INDEX]);
        C.OutSubcatch := FindSubcatch(S);
        if C.OutSubcatch = nil then C.OutNode := FindNode(S);
      end;
    end
    else if Project.IsNode(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TNode(Objects[J]).ID := PChar(Strings[J]);
    end
    else if Project.IsLink(I) then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do TLink(Objects[J]).ID := PChar(Strings[J]);
    end
    else if I = TRANSECT then with Project.Lists[I] do
    begin
      for J := 0 to Count-1 do
      begin
        TTransect(Objects[J]).CheckData;
        TTransect(Objects[J]).SetMaxDepth;
        Project.SetTransectConduitDepth(Strings[J],
          TTransect(Objects[J]).Data[TRANSECT_MAX_DEPTH]);
      end;
    end
    else continue;
  end;
end;


function ReadInpFile(const Fname: String):Boolean;
//-----------------------------------------------------------------------------
//  Reads SWMM input data from a text file.
//-----------------------------------------------------------------------------
var
  F : Textfile;
begin
  // Try to open the file
  Result := False;
  AssignFile(F,Fname);
  {$I-}
  Reset(F);
  {$I+}
  if (IOResult = 0) then
  begin

    // Create stringlists
    Screen.Cursor := crHourGlass;
    MapExtentSet := False;
    ErrList := TStringList.Create;
    TokList := TStringList.Create;
    SubcatchList := TStringList.Create;
    NodeList := TStringList.Create;
    LinkList := TStringList.Create;
    FileType := ftInput;
    InpFile := Fname;
    try

      // Read the file
      MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);
      SubcatchList.Sorted := True;
      NodeList.Sorted := True;
      LinkList.Sorted := True;
      Section := -1;
      Result := ReadFile(F, Uutils.GetFileSize(Fname));
      if (Result = True) then
      begin
        // Establish pointers to ID names
        SetIDPtrs;
      end;

    finally
      // Free the stringlists
      SubcatchList.Free;
      NodeList.Free;
      LinkList.Free;
      TokList.Free;
      MainForm.PageSetupDialog.Header.Text := Project.Title;
      MainForm.HideProgressBar;
      Screen.Cursor := crDefault;

      // Display errors if found & set map dimensions
      if Result = True then
      begin
        if ErrList.Count > 0 then DisplayInpErrForm(Fname);
        SetSubcatchCentroids;
        SetMapDimensions;
      end;
      ErrList.Free;
    end;
  end;

  // Close the input file
  CloseFile(F);
end;


procedure ClearDefaultDates;
//-----------------------------------------------------------------------------
//  Clears project's date/time settings.
//-----------------------------------------------------------------------------
begin
  with Project.Options do
  begin
    Data[START_DATE_INDEX]        := '';
    Data[START_TIME_INDEX]        := '';
    Data[REPORT_START_DATE_INDEX] := '';
    Data[REPORT_START_TIME_INDEX] := '';
    Data[END_DATE_INDEX]          := '';
    Data[END_TIME_INDEX]          := '';
  end;
end;


procedure SetDefaultDates;
//-----------------------------------------------------------------------------
//  Sets default values for project's date/time settings.
//-----------------------------------------------------------------------------
var
  StartTime: TDateTime;
  StartDate: TDateTime;
  T: TDateTime;
  D: TDateTime;
begin
  with Project.Options do
  begin

  // Process starting date/time
    try
      StartDate := StrToDate(Data[START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do StartDate := Date;
    end;
    StartTime := Uutils.StrHoursToTime(Data[START_TIME_INDEX]);
    if StartTime < 0 then StartTime := 0;
    D := StartDate + StartTime;
    Data[START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process reporting start date/time
    try
      D := StrToDate(Data[REPORT_START_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[REPORT_START_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[REPORT_START_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[REPORT_START_TIME_INDEX] := TimeToStr(D, MyFormatSettings);

  // Process ending date/time
    try
      D := StrToDate(Data[END_DATE_INDEX], MyFormatSettings);
    except
      On EConvertError do D := StartDate;
    end;
    T := Uutils.StrHoursToTime(Data[END_TIME_INDEX]);
    if T < 0 then T := StartTime;
    D := D + T;
    Data[END_DATE_INDEX] := DateToStr(D, MyFormatSettings);
    Data[END_TIME_INDEX] := TimeToStr(D, MyFormatSettings);
  end;
end;


function OpenProject(const Fname: String): TInputFileType;
//-----------------------------------------------------------------------------
//  Reads in project data from a file.
//-----------------------------------------------------------------------------
begin
  // Show progress meter
  ClearDefaultDates;
  Screen.Cursor := crHourGlass;
  MainForm.ShowProgressBar(MSG_READING_PROJECT_DATA);

  // Use default map dimensions and backdrop settings
  MapForm.Map.Dimensions := DefMapDimensions;
  MapForm.Map.Backdrop := DefMapBackdrop;

  // Do the following for non-temporary input files
  if not SameText(Fname, Uglobals.TempInputFile) then
  begin

    // Create a backup file
    if AutoBackup
    then CopyFile(PChar(Fname), PChar(ChangeFileExt(Fname, '.bak')), FALSE);

    // Retrieve project defaults from .INI file
    if CompareText(ExtractFileExt(Fname), '.ini') <> 0
    then Uinifile.ReadProjIniFile(ChangeFileExt(Fname, '.ini'));
  end;

  // Read and parse each line of input file
  Result := iftNone;
  if ReadInpFile(Fname) then Result := iftINP;

  // Finish processing the input data
  if Result <> iftNone then
  begin
    SetDefaultDates;                   // set any missing analysis dates
    Uglobals.RegisterCalibData;        // register calibration data files
    Uupdate.UpdateUnits;               // update choice of unit system
    Uupdate.UpdateDefOptions;          // update default analysis options
    Uupdate.UpdateLinkHints;           // update hints used for offsets
    Project.GetControlRuleNames;       // store control rule names in a list
  end;

  // Hide progress meter.
  MainForm.HideProgressBar;
  Screen.Cursor := crDefault;
end;

end.le names in a list
  end;

  // Hide progress meter.
  MainForm.HideProgressBar;
  Screen.Cursor := crDefault;
end;

end.
  if Result <> iftNone then
  begin
    SetDefaultDates;                   // set any missing analysis dates
    Uglobals.RegisterCalibData;        // register calibration data files
    Uupdate.UpdateUnits;               // update choice of unit system
    Uupdate.UpdateDefOptions;          // update default analysis options
    Uupdate.UpdateLinkHints;           // update hints used for offsets
    Project.GetControlRuleNames;       // store control rule names in a list
  end;

  // Hide progress meter.
  MainForm.HideProgressBar;
  Screen.Cursor := crDefault;
end;

end.

AI Rivers of Wisdom about ICM SWMM

Here's the text "Rivers of Wisdom" formatted with one sentence per line: [Verse 1] 🌊 Beneath the ancient oak, where shadows p...