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.

#SWMM5 Delphi Pascal unit that manages the list of external tools made available through the Tools item on the Main Menu.

unit Utools;

{-------------------------------------------------------------------}
{                    Unit:    Utools.pas                            }
{                    Project: EPA SMM                               }
{                    Version: 5.1                                   }
{                    Date:    12/2/13     (5.1.000)                 }
{                             08/19/14    (5.1.007)                 }
{                             03/19/15    (5.1.008)                 }
{                    Author:  L. Rossman                            }
{                                                                   }
{   Delphi Pascal unit that manages the list of external tools      }
{   made available through the Tools item on the Main Menu.         }
{-------------------------------------------------------------------}

interface

uses
  Classes, Dialogs, Menus, SysUtils, IniFiles, Windows, StrUtils,
  Forms, Uglobals;

// Data structure that records info for each external SWMM tool
type
  TTool = class(TObject)
    ExeName:     String;
    DirName:     String;
    Params:      String;
    DisableSWMM: Boolean;
    UpdateSWMM:  Boolean;
    MnuItem:     TMenuItem;
  end;

const

  // Name of INI file where tool info is stored
  TOOLSFILE = 'swmm5tools.ini';

  // Names used to designate specific project attributes when used
  // as arguments in the command that launches a tool
  Macros: array[0..5] of String =
    ('$PROJDIR',        // Current project directory
     '$SWMMDIR',        // SWMM's program directory
     '$INPFILE',        // Text file containing current project data
     '$RPTFILE',        // Text file to contain a status report
     '$OUTFILE',        // Binary file to contain analysis results
     '$RIFFILE');       // SWMM runoff interface file

var
  ToolList: TStrings;   // Collection of registered tools

procedure OpenToolList;
procedure CloseToolList;
procedure UpdateTool(I: Integer; S1, S2, S3, S4: String; Flag1: Boolean;
          Flag2: Boolean);
procedure DeleteTool(I: Integer);
procedure ExchangeTools(const I1: Integer; const I2: Integer);
function  GetToolName(I: Integer): String;
procedure RunTool(S: String);
function  GetTmpInpFileName: String;

implementation

uses
  Fmain, Fmap, Fproped, Ubrowser, Uimport, Uexport, Uoutput, Uutils;

var
  Tool: TTool;


procedure ReadToolsIniFile;
//-----------------------------------------------------------------------------
//  Reads the collection of tools that were last registered by the user
//  from the swmm5tools.ini file.
//-----------------------------------------------------------------------------
var
  I: Integer;
  Sections: TStringList;
  S1, S2, S3, S4: String;
  Flag1: Boolean;
  Flag2: Boolean;
begin
  // Info on each tool is written to its own section of the ini file
  Sections := TStringlist.Create;
  with TIniFile.Create(IniFileDir + TOOLSFILE) do
  try
    // Read all sections of the ini file (this is a Delphi function)
    ReadSections(Sections);

    // Parse the info for each tool from its particular section
    for I := 0 to Sections.Count-1 do
    begin
      S1 := Sections[I];                     // This is the tool's name
      S2 := ReadString(S1, 'ExeName', '');
      S3 := ReadString(S1, 'DirName', '');
      S4 := ReadString(S1, 'Params', '');
      Flag1 := ReadBool(S1, 'Disabled', False);
      Flag2 := ReadBool(S1, 'Update', False);

      // Add this tool to the collection (-1 as first argument to
      // UpdateTool means we are adding rather than updating a tool)
      if Length(S2) > 0
      then UpdateTool(-1, S1, S2, S3, S4, Flag1, Flag2);
    end;
  finally
    Free;
    Sections.Free;
  end;
end;


procedure WriteToolsIniFile;
//-----------------------------------------------------------------------------
//  Writes the current collection of registered tools to the swmm5tools.ini
//  file.
//-----------------------------------------------------------------------------
var
  I: Integer;
  S1: String;
begin
  with TMemIniFile.Create(IniFileDir + TOOLSFILE) do
  try
    Clear;
    for I := 0 to ToolList.Count-1 do
    begin
      S1 := ToolList[I];
      Tool := TTool(ToolList.Objects[I]);
      WriteString(S1, 'ExeName', Tool.ExeName);
      WriteString(S1, 'DirName', Tool.DirName);
      WriteString(S1, 'Params', Tool.Params);
      WriteBool(S1, 'Disabled', Tool.DisableSWMM);
      WriteBool(S1, 'Update', Tool.UpdateSWMM);
    end;
    UpdateFile;
  finally
    Free;
  end;
end;


function GetNewTool: TTool;
//-----------------------------------------------------------------------------
//  Creates a new tool and its associated menu item under the Main Menu's
//  Tools menu.
//-----------------------------------------------------------------------------
begin
  Tool := TTool.Create;
  Tool.MnuItem := TMenuItem.Create(MainForm);
  Tool.MnuItem.OnClick := MainForm.ToolItemClick;
  MainForm.MnuTools.Add(Tool.MnuItem);
  Result := Tool;
end;


procedure OpenToolList;
//-----------------------------------------------------------------------------
//  Creates a collection of tools and populates it with the tools
//  registered at the end of the user's last session.
//-----------------------------------------------------------------------------
begin
  ToolList := TStringlist.Create;
  ReadToolsIniFile;
end;


procedure CloseToolList;
//-----------------------------------------------------------------------------
//  Saves the current collection of tools to file and then deletes them.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  WriteToolsIniFile;
  with ToolList do
  try
   for I := (Count - 1) downto 0 do DeleteTool(I);
  finally
    ToolList.Free;
  end;
end;


function GetToolName(I: Integer): String;
//-----------------------------------------------------------------------------
//  Retrieves the name of the I-th tool in the tools collection.
//-----------------------------------------------------------------------------
begin
  if (I < 0) or (I >= ToolList.Count)
  then Result := ''
  else Result := ToolList[I];
end;


procedure UpdateTool(I: Integer; S1, S2, S3, S4: String; Flag1: Boolean;
  Flag2: Boolean);
//-----------------------------------------------------------------------------
//  Updates the properties of the tool whose index is I (if I is -1 then a
//  new tool is created and added to the tools collection).
//-----------------------------------------------------------------------------
begin
  if I < 0 then Tool := GetNewTool
  else Tool := TTool(ToolList.Objects[I]);
  Tool.ExeName := S2;
  Tool.DirName := S3;
  Tool.Params := S4;
  Tool.DisableSWMM := Flag1;
  Tool.UpdateSWMM := Flag2;
  Tool.MnuItem.Caption := S1;
  if I < 0 then ToolList.AddObject(S1, Tool)
  else ToolList[I] := S1;
end;


procedure DeleteTool(I: Integer);
//-----------------------------------------------------------------------------
//  Deletes tool I from the tools collection.
//-----------------------------------------------------------------------------
begin
  Tool := TTool(ToolList.Objects[I]);
  MainForm.MnuTools.Remove(Tool.MnuItem);
  Tool.MnuItem.Free;
  Tool.Free;
  ToolList.Delete(I);
end;


procedure ExchangeTools(const I1: Integer; const I2: Integer);
//-----------------------------------------------------------------------------
//  Exchanges the positions of tools I1 and I2 in the tools collection.
//-----------------------------------------------------------------------------
var
  I: Integer;
begin
  ToolList.Exchange(I1, I2);
  for I := 0 to ToolList.Count-1 do
  begin
    Tool := TTool(ToolList.Objects[I]);
    MainForm.MnuTools.Remove(Tool.MnuItem);
  end;
  for I := 0 to ToolList.Count-1 do
  begin
    Tool := TTool(ToolList.Objects[I]);
    MainForm.MnuTools.Add(Tool.MnuItem);
  end;
end;


function GetDir: String;
//-----------------------------------------------------------------------------
//  Replaces a directory macro with the actual directory name (which can
//  either be the current project directory or the SWMM program directory).
//-----------------------------------------------------------------------------
var
  S: String;
begin
  S := Tool.DirName;
  S := AnsiReplaceText(S, Macros[0], ProjectDir);
  S := AnsiReplaceText(S, Macros[1], EpaSwmmDir);
  Result := S;
end;


function GetTmpInpFileName: String;
//-----------------------------------------------------------------------------
//  Replaces the SWMM input file name macro with the name of a temporary
//  file to which the current project's input data has been written.
//-----------------------------------------------------------------------------
var
  S: TStringlist;
begin
  // Create a new temporary input file
  SysUtils.DeleteFile(Uglobals.TempInputFile);
  Uglobals.TempInputFile  := Uutils.GetTempFile(Uglobals.TempDir, 'swmm');

  // Export the current SWMM project data to this file
  S := TStringlist.Create;
  try
    Uexport.ExportProject(S);
    S.AddStrings(Project.Options.Report);
    Uexport.ExportTags(S);
    Uexport.ExportMap(S);
    Uexport.ExportProfiles(S);
    S.SaveToFile(TempInputFile);
  finally
    S.Free;
  end;
  Result := Uglobals.TempInputFile;
end;


function GetTmpRptFileName: String;
//-----------------------------------------------------------------------------
//  Replaces the SWMM report file name macro with the name of a temporary
//  report file.
//-----------------------------------------------------------------------------
begin
  if not FileExists(Uglobals.TempReportFile)
  then Uglobals.TempReportFile := Uutils.GetTempFile(Uglobals.TempDir, 'swmm');
  Result := Uglobals.TempReportFile;
end;


function GetTmpOutFileName: String;
//-----------------------------------------------------------------------------
//  Replaces the SWMM output file name macro with the name of a temporary
//  output file.
//-----------------------------------------------------------------------------
begin
  if not FileExists(Uglobals.TempOutputFile)
  then Uglobals.TempOutputFile := Uutils.GetTempFile(Uglobals.TempDir, 'swmm');
  Result := Uglobals.TempOutputFile;
end;


function GetRIFFileName: String;
//-----------------------------------------------------------------------------
//  Replaces the runoff interface file name macro with the name of
//  the current runoff interface file (or a blank string if no such file
//  exists).
//-----------------------------------------------------------------------------
var
  I: Integer;
  N: Integer;
  S: String;
  Tokens: TStringlist;
begin
  Result := '';
  Tokens := TStringlist.Create;
  try
    for I := 0 to Project.IfaceFiles.Count-1 do
    begin
      S := Project.IfaceFiles[I];
      Uutils.Tokenize(S, Tokens, N);
      if N >= 3 then
      begin
        if SameText(Tokens[1], 'RUNOFF') then Result := Tokens[2];
      end;
    end;
  finally
    Tokens.Free;
  end;
end;


function GetParams: String;
//-----------------------------------------------------------------------------
//  Replaces any macros appearing in a tool's command line parameters
//  with their actual names.
//-----------------------------------------------------------------------------
var
  S: String;
begin
  S := Tool.Params;
  S := AnsiReplaceText(S, Macros[0], Uglobals.ProjectDir);
  S := AnsiReplaceText(S, Macros[1], Uglobals.EpaSwmmDir);
  S := AnsiReplaceText(S, Macros[2], GetTmpInpFileName);
  S := AnsiReplaceText(S, Macros[3], GetTmpRptFileName);
  S := AnsiReplaceText(S, Macros[4], GetTmpOutFileName);
  S := AnsiReplaceText(S, Macros[5], GetRIFFileName);
  Result := S;
end;


procedure UpdateSWMMInput(const OutputUpdated: Boolean);
//-----------------------------------------------------------------------------
//  Replaces the current project data with that contained in the temporary
//  input file that was updated by a particular tool.
//-----------------------------------------------------------------------------
begin
  // Prepare the GUI to receive an updated set of project data
  PropEditForm.Hide;
  Project.Clear;
  Project.InitCurrentItems;
  Ubrowser.InitDataPage;

  // Import the project data contained in the temporary input file
  Uglobals.InputFileType := Uimport.OpenProject(Uglobals.TempInputFile);
  Uglobals.HasChanged := True;
  Uglobals.UpdateFlag := True;

  // Refresh any existing simulation results if the tool did
  // not update these as well
  if Uglobals.RunFlag and not OutputUpdated then
  begin
    Ubrowser.InitMapPage;
    MainForm.RefreshResults;
  end
  else MapForm.RedrawMap;
  MainForm.ShowRunStatus;
end;


procedure UpdateSWMMOutput;
//-----------------------------------------------------------------------------
//  Allows a new set of simulation results that were generated by the
//  tool to be displayed by the program's user interface.
//-----------------------------------------------------------------------------
begin
  Uoutput.ClearOutput;
  Ubrowser.InitMapPage;
  if GetFileSize(Uglobals.TempReportFile) <= 0
  then Uglobals.RunStatus := rsFailed
  else Uglobals.RunStatus := Uoutput.CheckRunStatus(Uglobals.TempOutputFile);
  if Uglobals.RunStatus in [rsSuccess, rsWarning]
  then Uglobals.RunFlag := True
  else Uglobals.RunFlag := False;
  if Uglobals.RunStatus = rsError
  then MainForm.MnuReportStatusClick(MainForm);
  MainForm.RefreshResults;
  MainForm.RefreshForms;
end;


procedure RunTool(S: String);
//-----------------------------------------------------------------------------
//  Launches the tool whose name is S.
//-----------------------------------------------------------------------------
var
  I, R: Integer;
  CmdLine: String;
  DirName: String;
  Params:  String;
  OutputUpdated: Boolean;
  OldInpTimeStamp: TDateTime;
  NewInpTimeStamp: TDateTime;
begin
  //Remove any menu accelerator key symbol from the tool's name
  Delete(S, Pos('&', S), 1);

  // Locate the index of the tool in the tool collection
  I := ToolList.IndexOf(S);
  if (I < 0)
  then Uutils.MsgDlg(S + ' is not a registered tool.', mtWarning, [mbOK])      //(5.1.008)
  else
  begin

    // Generate the command line for launching the tool
    Tool := TTool(ToolList.Objects[I]);
    DirName := GetDir;
    Params  := GetParams;
    CmdLine := Tool.ExeName + ' ' + Params;

    // Save SWMM input file's time stamp
    if AnsiContainsText(Params, Uglobals.TempInputFile) then
      FileAge(Uglobals.TempInputFile, OldInpTimeStamp)
    else OldInpTimeStamp := 0;

    // Launch the tool and disable SWMM if called for
    try
      if Tool.DisableSWMM then MainForm.Hide;
      R := Uutils.WinExecAndWait(CmdLine, DirName, SW_SHOWNORMAL,
                                 Tool.DisableSWMM);
    finally
      MainForm.Show;
    end;
    if R < 0 then
    begin
      Uutils.MsgDlg('Could not launch ' + ToolList[I], mtWarning, [mbOK]);     //(5.1.008)
      Exit;                                                                    //(5.1.007)
    end;

    // Update SWMM if called for
    if Tool.UpdateSWMM then
    begin

      // See if tool updates the simulation results
      OutputUpdated := False;
      if AnsiContainsText(Params, Uglobals.TempOutputFile)
      then OutputUpdated := True;

      // Update project's input data if called for
      if AnsiContainsText(Params, Uglobals.TempInputFile) then
      begin
        FileAge(Uglobals.TempInputFile, NewInpTimeStamp);
        if (OldInpTimeStamp > 0)
        and (OldInpTimeStamp < NewInpTimeStamp)
        then UpdateSWMMInput(OutputUpdated);
      end;

      // Update simulation results if called for
      if OutputUpdated then UpdateSWMMOutput;
    end;

  end;
end;

end.

#SWMM5 Delphi Pascal unit that retrieves a time series of measured observation data from a calibration file for use in a time series plot

unit Ucalib;

{-------------------------------------------------------------------}
{                    Unit:    Ucalib.pas                            }
{                    Project: EPA SWMM                              }
{                    Version: 5.1                                   }
{                    Date:    12/2/13      (5.1.000)                }
{                             03/19/15     (5.1.008)                }
{                    Author:  L. Rossman                            }
{                                                                   }
{   Delphi Pascal unit that retrieves a time series of measured     }
{   observation data from a calibration file for use in a time      }
{   series plot.                                                    }
{-------------------------------------------------------------------}

//  Mapping of view variable indexes to calibration file +indexes:
//  Variable                    Calibration File Index
//  ------------                ----------------------
//  Subcatchment RUNOFF          1
//  Subcatchment SNOWDEPTH       10
//  Subcatchment GW_FLOW         8
//  Subcatchment GW_ELEV         9
//  Subcatchment SUBCATCHQUAL    2
//  Node NODEDEPTH               3
//  Node LATFLOW                 6
//  Node OVERFLOW                7
//  Node NODEQUAL                5
//  Link FLOW                    4
//  Link DEPTH                   11
//  Link VELOCITY                12

interface

uses
  Classes, SysUtils, TeEngine, Series, Chart, Uglobals, Uutils, Dialogs;

procedure GetCalibData(VarIndex: Integer; ObjType: Integer; LocID: String;
  const aStartDate: TDateTime; const aEndDate: TDateTime;
  const aTimeFactor: Double;  const aDateTimeDisplay: Boolean;
  ChartSeries: TChartSeries);

implementation

var
  Toklist  : TStringList;
  StartDate: TDateTime;
  EndDate  : TDateTime;
  TimeFactor: Double;
  DateTimeDisplay: Boolean;
  SimStartDate: TDateTime;

procedure GetCalibFileIndex(VarIndex: Integer; ObjType: Integer;
  var FileIndex: Integer; var VarOffset: Integer);
begin
  FileIndex := 0;
  VarOffset := 0;

  // Variable is for a subcatchment
  if (ObjType = SUBCATCHMENTS) then
  begin
    if VarIndex = RUNOFF    then FileIndex := 1;
    if VarIndex = SNOWDEPTH then FileIndex := 10;
    if VarIndex = GW_FLOW   then FileIndex := 8;
    if VarIndex = GW_ELEV   then FileIndex := 9;
    if VarIndex >= SUBCATCHQUAL then
    begin
      FileIndex := 2;
      VarOffset := VarIndex - SUBCATCHQUAL;
    end;
  end

  // Variable is for a node
  else if (ObjType = NODES) then
  begin
    if VarIndex = NODEDEPTH then FileIndex := 3;
    if VarIndex = LATFLOW   then FileIndex := 6;
    if VarIndex = OVERFLOW  then FileIndex := 7;
    if VarIndex >= NODEQUAL then
    begin
      FileIndex := 5;
      VarOffset := VarIndex - NODEQUAL;
    end;
  end

  // Variable is for a link
  else if (ObjType = LINKS) then
  begin
    if VarIndex = FLOW then FileIndex := 4;
    if VarIndex = LINKDEPTH then FileIndex := 11;
    if VarIndex = VELOCITY  then FileIndex := 12;
  end;
end;

function AddCalibDataPoint(ChartSeries: TChartSeries; const DataTok: Integer): Boolean;
//-----------------------------------------------------------------------------
// Adds a calibration data value to the chart data series where
// DataTok = index of the token that contains the measurement value.
//-----------------------------------------------------------------------------
var
  Value: Extended;
  Days : Extended;
  Hours: TDateTime;
  X    : TDateTime;
  aDate: String;
begin
  try
    // Assume the data measurement time is a date/time value
    // (If its not, an exception will occur which is handled below).
    Result := False;
    aDate := TokList[0];
    Days := StrToDate(Uutils.ConvertDate(aDate), Uglobals.MyFormatSettings);
    Hours := Uutils.StrHoursToTime(TokList[1]);
    if Hours < 0 then Exit;
    X := Days + Hours;
  except
    // An exception occurs if measurement time is actually elapsed days/hours
    on EconvertError do
    begin
      Result := False;
      if not Uutils.GetExtended(TokList[0], Days) then Exit;
      Hours := Uutils.StrHoursToTime(TokList[1]);
      if Hours < 0 then Exit;

      // Adjust date to simulation start date.
      X := SimStartDate + Days + Hours;

    end;
  end;

  // Check that measurement time falls within plotting interval
  if (X < StartDate) or (X > EndDate) then Exit;

  // If the chart is not using date/time for X-axis,
  // convert measurement time to elapsed time if need be

  //  Adjust date to simulation start date.
  if not DateTimeDisplay then X := (X - SimStartDate)*TimeFactor;

  Result := True;

  // If the DataTok token contains a valid value then add
  // that value and its time to the calibration point series
  if Uutils.GetExtended(TokList[DataTok], Value)
  then with ChartSeries as TPointSeries do AddXY(X, Value, '', clTeeColor);
end;

procedure GetCalibData(VarIndex: Integer; ObjType: Integer; LocID: String;
  const aStartDate: TDateTime; const aEndDate: TDateTime;
  const aTimeFactor: Double;  const aDateTimeDisplay: Boolean;
  ChartSeries: TChartSeries);
var
  FileIndex: Integer;             //Index of calibration file
  VarOffset: Integer;             //Offset of variable in file
  P        : Integer;
  Ntoks    : Integer;
  DataTok  : Integer;
  UseData  : Boolean;
  F        : TextFile;
  Fname    : String;
  Line     : String;
  S        : String;
begin
  // Determine which calibration file corresponds to the variable of interest
  GetCalibFileIndex(VarIndex, ObjType, FileIndex, VarOffset);
  if FileIndex = 0 then Exit;

  // Check if the calibration file for the variable of interest
  // has data for the location of interest
  if (Pos('"'+LocID+'"', CalibData[FileIndex].Locations) = 0) then Exit;

  // Save shared variables
  StartDate := aStartDate;
  EndDate := aEndDate;
  TimeFactor := aTimeFactor;
  DateTimeDisplay := aDateTimeDisplay;

  // Compute simulation start date which is one reporting
  // period before the first reporting date.
  SimStartDate := StartDateTime - DeltaDateTime;

////  Following code segment re-written for release 5.1.008.  ////             //(5.1.008)
  // Try to open the calibration data file
  Fname := CalibData[FileIndex].FileName;
  if FileExists(Fname) then
  try
    // Create a list of string tokens
    Toklist := TStringList.Create;
    AssignFile(F,Fname);
    {$I-}
    Reset(F);
    {$I+}
    if IOResult = 0 then
    begin

      // Data token index is 2 + index of measurement variable
      DataTok := 2 + VarOffset;
      UseData := False;

      // Read lines from file until reach end of file
      while not EOF(F) do
      begin
        Readln(F, Line);
        S := Line;

        // Remove any comment & tokenize the line
        P := Pos(';', S);
        if (P > 0) then Delete(S, P, 256);
        Uutils.Tokenize(S, Toklist, Ntoks);

        // A single entry marks start of data for a new location.
        // If this is the location of interest, set UseData to True
        if (Ntoks = 1) then
        begin
          if (Toklist[0] = LocID) then
            UseData := True
          else
            UseData := False;
        end

        // If line has enough items then add data point to data lists
        else if (Ntoks > DataTok) and (UseData) then
        begin
          AddCalibDataPoint(ChartSeries, DataTok);
        end;
      end;
    end;

    // After finished reading the file, free the token list.
  finally
    Toklist.Free;
    CloseFile(F);
  end;
////////////////////////////////////////////////////////////////////////
end;

end.

Friday, July 21, 2017

Contents of EPANET2.ZIP

Contents of EPANET2.ZIP

=======================
This archive contains the source code for the EPANET 2
network hydraulic and water quality solver. The solver
provides functions for simulating the extended period
hydraulic and water quality behavior of water distribution
system pipe networks. It is written in ANSI-compatible C
and can be compiled into either a Windows Dynamic Link
Library of functions or into a command-line executable.

The archived code is set up for compilation as a DLL.
To compile it as a command line (or console) application
simply comment out the "#define DLL" macro statement at
the top of EPANET.C and un-comment the "#define CLE" macro.

The DLL version of the solver (epanet2.dll) is used with
the EPANET 2 user interface executable (epanet2w.exe) to
form a complete Windows modeling package. It also serves
as the function library for the EPANET Programmer's Toolkit,
allowing developers to construct their own customized pipe
network analysis applications.

The following C-code files are included in this archive:
    EPANET.C  -- main module providing supervisory control
    INPUT1.C  -- controls processing of input data
    INPUT2.C  -- reads data from input file
    INPUT3.C  -- parses individual lines of input data
    INPFILE.C -- saves modified input data to a text file
    RULES.C   -- implements rule-based control of piping system
    HYDRAUL.C -- computes extended period hydraulic behavior
    QUALITY.C -- tracks transport & fate of water quality
    OUTPUT.C  -- handles transfer of data to and from binary files
    REPORT.C  -- handles reporting of results to text file
    SMATRIX.C -- sparse matrix linear equation solver routines
    MEMPOOL.C -- memory pool management routines
    HASH.C    -- hash table routines

Also included are the following header files:
    TOOLKIT.H  -- function prototypes of exported DLL functions
    FUNCS.H    -- prototypes of all other functions
    TYPES.H    -- declaration of global constants and data structures
    VARS.H     -- declaration of global variables
    HASH.H     -- header file for hash table routines
    MEMPOOL.H  -- header file for memory pool routines
    ENUMSTXT.H -- string constants for enumerated types
    TEXT.H     -- declaration of all other string constants

The comments at the top of each file lists the date when the latest
update was made, and these updates can be located in the code by
searching for comments with the phrase "/*** Updated" or with the
release number (e.g., 2.00.12) in them.

Other useful documentation that can be consulted includes the EPANET
Programmers Toolkit Help file and the EPANET Version 2 Users Manual.

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