Wednesday, August 16, 2017

#SWMM 5 LID 185 message

Here are the soil layer rules for LID’s in SWMM5....  If any of these are wrong you will get an Error 185 message

Soil Porosity > Field Capacity > Wilting Point

    //... check soil layer parameters
    if ( LidProcs[j].soil.thickness >= 0.0; 0.0 )
    {
        if ( LidProcs[j].soil.porosity      <= 0.0 
        ||   LidProcs[j].soil.fieldCap      >= LidProcs[j].soil.porosity
        ||   LidProcs[j].soil.wiltPoint    >= LidProcs[j].soil.fieldCap
        ||   LidProcs[j].soil.kSat            <= 0.0
        ||   LidProcs[j].soil.kSlope       <= 0.0 )


Monday, August 7, 2017

EPA is happy to announce the publication of Volume II of the #SWMM Reference Manual (Hydraulics)

Hello Everyone (from the @CHI SWMM List Server),

EPA is happy to announce the publication of Volume II of the SWMM Reference Manual (Hydraulics).  It can be viewed and downloaded from the EPA SWMM web page:
https://www.epa.gov/water-research/storm-water-management-model-swmm#documents

This completes the full set of reference manuals for SWMM 5 that also includes Volume I (Hydrology) and Volume III (Water Quality and LID).  We hope that these manuals allow SWMM users to gain a better understanding of the computational methods and techniques used by the program.

These volumes were completed by Dr. Lewis A. Rossman, EPA Emeritus.  In his extraordinary career, he has written three remarkable EPA public domain programs:  SWMM, the National Stormwater Calculator (https://www.epa.gov/water-research/national-stormwater-calculator which is based on SWMM),  and EPANET (https://www.epa.gov/water-research/epanet).   Since 2010, SWMM and EPANET have been downloaded at least 215,000 and 370,000 times and are the basis of many multi-billion, multi-year water infrastructure upgrades.  The codes are easy-to-use, relatively bug-free, and well-documented.  This last volume of the reference manual completes the documentation of SWMM.

Thanks, Lew, and thanks to all of you for your continued interest and support of SWMM.

Michelle Simon. Ph.D., P.E.
EPA Office of Research and Development
Water Systems Division
Associate Director of Science
Cincinnati, OH 45268

Sunday, July 30, 2017

This is how node interface files works in #SWMM5, Caveats and Tips

The routine readNewIfaceValues reads a line interface flows in SWMM5.  It is a string parser, finds tokens and creates dates, times and flows from the tokens.  It is important to have the correct format for your line else the tokens will not be correctly converted to integers and doubles. Further, it is important to have more than one date/time for each node as the SWMM5 engine interpolates the flow values for each node during the simulation.  One time value or time values out of the simulation date/times will result in no flows.

void readNewIfaceValues()
//
//  Input:   none
//  Output:  none
//  Purpose: reads data from inflows interface file for next date.
//
{
    int    i, j;
    char*  s;
    int    yr = 0, mon = 0, day = 0,
                hr = 0, min = 0, sec = 0;   // year, month, day, hour, minute, second
    char   line[MAXLINE+1];            // line from interface file

    // --- read a line for each interface node
    NewIfaceDate = NO_DATE;
    for (i=0; i<NumIfaceNodes; i++)
    {
        if ( feof(Finflows.file) ) return;
        fgets(line, MAXLINE, Finflows.file);

        // --- parse date & time from line
        if ( strtok(line, SEPSTR) == NULL ) return;
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        yr  = atoi(s);
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        mon = atoi(s);
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        day = atoi(s);
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        hr  = atoi(s);
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        min = atoi(s);
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        sec = atoi(s);

        // --- parse flow value
        s = strtok(NULL, SEPSTR);
        if ( s == NULL ) return;
        NewIfaceValues[i][0] = atof(s) / Qcf[IfaceFlowUnits];

        // --- parse pollutant values
        for (j=1; j<=NumIfacePolluts; j++)
        {
            s = strtok(NULL, SEPSTR);
            if ( s == NULL ) return;
            NewIfaceValues[i][j] = atof(s);
        }

    }

Wednesday, July 26, 2017

A Visual Studio Compiler for #SWMM5 Note

A Visual Studio Compiler for #SWMM5 note.  If you have many many copies of the SWMM5 code on your PC you can rename the Visual Studio project files from the default SWMM5 names to a more meaningful version name (Figure 2) and using the DLL Properties/General/Output file change the SWMM5.DLL creation directory (Figure 1). 

Figure 1.  DLL Properties/General/Output file change the SWMM5.DLL creation directory

Figure 2.  Visual Studio project files from the default SWMM5 names to a more meaningful version name 

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...