unit erg_code;

interface

uses erg_base, erg_types, classes;

{!
  \brief a global variable stating the program version
  }
  
const codegenVersion='1.4';

var outputLanguage: String;

procedure generateCode(inputfile, outputfile: String; coloredElements:Boolean=true);

implementation

uses sysUtils;

var LinesRead: Integer;
    InputBuffer: Array[1..65536] of char;


//! Adds spaces in the end of a string until it has a defined length
{!
  \private
  \param s input string to enlarge
  \param minLength minimum length of the output string (integer)
  \return s_enlarged string with minimum length of minLength and trim(s_enlarged)==trim(s)
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Enlarges a string.
}
function makeLonger(s: String; minLength: Integer):String;
// will append blanks until desired length is reached
var s1: String;
begin
  s1:=s;
  while length(s1)<minLength do
    s1:=s1+' ';
  result:=s1;
end;

//! Convert a mathematical formula into the format for a chosen programming languate
{!
  \private
  \param s mathematical formula as string (is overwritten; =output)
  \param name optional prefix for temporary variables (string)
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Converts a formula for the use in a certain programming language. '^' is
  converted into '**' if we have a Fortran file. The language is determined
  from the variable outputLanguage.

  Reads global variables:
  - outputLanguage
}
procedure convertToOutputLanguage(var s: String; name: String='');
var
  i, pos1, pos2, pos3: Integer;
  paranthesisCount: Integer;
begin
  if lowercase(outputLanguage)='matlab' then   //convert from Fortran to Matlab
  begin
    s:=StringReplace(s,'**','^',[rfReplaceAll,rfIgnoreCase]);
  end;
  if lowercase(outputLanguage)='ferret' then   //convert from Fortran to Ferret
  begin
    s:=StringReplace(s,'**','^',[rfReplaceAll,rfIgnoreCase]);
    //remove '-' at beginning of expression
    s:=StringReplace(s,'(-','(0-',[rfReplaceAll,rfIgnoreCase]);
    s:=StringReplace(s,'( -','(0-',[rfReplaceAll,rfIgnoreCase]);
    if copy(s,1,1)='-' then s:='0'+s;
    //replace functions which do not exist in ferret, as it is complicated to define them
    while pos('sqrt(',lowercase(s))>0 do
    begin
      //replace sqrt function by ((...)^0.5)
      //seek the corresponding ')'
      i:=pos('sqrt(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('sqrt(',lowercase(s));
      s:=copy(s,1,pos1-1)+'(('+copy(s,pos1+5,pos3-(pos1+5))+')^0.5)'+copy(s,pos3+1,length(s));
    end;
    while pos('power(',lowercase(s))>0 do
    begin
      //replace power function by ((...)^(...))
      //seek the corresponding ',' and ')'
      i:=pos('power(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=',') then //this is the comma between base and exponent
          pos2:=i;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('power(',lowercase(s));
      s:=copy(s,1,pos1-1)+'(('+copy(s,pos1+6,pos2-(pos1+6))+')^('+copy(s,pos2+1,pos3-(pos2+1))+'))'+copy(s,pos3+1,length(s));
    end;
    while pos('theta(',lowercase(s))>0 do
    begin
      //replace theta function by ((...) gt 0)
      //seek the corresponding ')'
      i:=pos('theta(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('theta(',lowercase(s));
      s:=copy(s,1,pos1-1)+'(('+copy(s,pos1+6,pos3-(pos1+6))+') gt 0)'+copy(s,pos3+1,length(s));
    end;
    while pos('tanh(',lowercase(s))>0 do
    begin
      //replace tanh function by (sinh(...)/cosh(...))
      //seek the corresponding ')'
      i:=pos('tanh(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('tanh(',lowercase(s));
      s:=copy(s,1,pos1-1)+'(sinh('+copy(s,pos1+5,pos3-(pos1+5))+')/cosh('+copy(s,pos1+5,pos3-(pos1+5))+'))'+copy(s,pos3+1,length(s));
    end;
    while pos('sinh(',lowercase(s))>0 do
    begin
      //replace sinh function by ((exp(...)-exp(0-(...)))/2)
      //seek the corresponding ')'
      i:=pos('sinh(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('sinh(',lowercase(s));
      s:=copy(s,1,pos1-1)+'((exp('+copy(s,pos1+5,pos3-(pos1+5))+')-exp(0-('+copy(s,pos1+5,pos3-(pos1+5))+')))/2)'+copy(s,pos3+1,length(s));
    end;
    while pos('cosh(',lowercase(s))>0 do
    begin
      //replace cosh function by ((exp(...)+exp(0-(...)))/2)
      //seek the corresponding ')'
      i:=pos('cosh(',lowercase(s));
      paranthesisCount:=0;
      while i<length(s) do
      begin
        i:=i+1;
        if copy(s,i,1)='(' then
          paranthesisCount:=paranthesisCount+1;
        if (paranthesisCount=1) and (copy(s,i,1)=')') then //this is the closing paranthesis
        begin
          pos3:=i;
          break;
        end
        else if copy(s,i,1)=')' then paranthesisCount:=paranthesisCount-1;
      end;
      pos1:=pos('cosh(',lowercase(s));
      s:=copy(s,1,pos1-1)+'((exp('+copy(s,pos1+5,pos3-(pos1+5))+')+exp(0-('+copy(s,pos1+5,pos3-(pos1+5))+')))/2)'+copy(s,pos3+1,length(s));
    end;
    if name <> '' then  //for auxiliaries, 'temp1', ..., 'temp9' must be replaced by '<name>_temp1', ..., '<name>_temp9'
    begin
      s:=StringReplace(s,'temp1',name+'_temp1',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp2',name+'_temp2',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp3',name+'_temp3',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp4',name+'_temp4',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp5',name+'_temp5',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp6',name+'_temp6',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp7',name+'_temp7',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp8',name+'_temp8',[rfReplaceAll,rfIgnoreCase]);
      s:=StringReplace(s,'temp9',name+'_temp9',[rfReplaceAll,rfIgnoreCase]);
    end;
  end;
end;

//! Splits a condition string into two arrays containing the right hande side and left hand side of the contion
{!
   \private
   \param s condition string of the format "cond1=val1, cond2=val2, cond3/=val3, ..."
   \param lhs string array with the left hand side of the conditions "('cond1=', 'cond2=', 'cond3/=', ...)"
   \param rhs string array with the right hand side of the conditions "(val1, val2, val3, ...)"
   \return lhs and rhs
   }
procedure SplitConditions(s: String; var lhs: TStringList; var rhs: TStringList);
var s1, s2: String;
    p: Integer;
begin
  s1:=s;
  lhs.Clear; rhs.Clear;
  while s1<>'' do
  begin
    p:=pos(';',s1);
    if p<1 then p:=length(s1)+1;
    s2:=copy(s1,1,p-1);
    s1:=copy(s1,p+1,length(s1));
    p:=pos('=',s2);
    if p>0 then
    begin
      lhs.Add(trim(copy(s2,1,p)));
      rhs.Add(trim(copy(s2,p+1,length(s2))));
    end
    else
    begin
      lhs.Add(trim(s2));
      rhs.Add('');
    end;
  end;
end;

//! will append ".0" if no dot is in the string, if possible before an "E"
{!
  \private
  \param s string representing a number, to which '.0' should be appended
  \return s with appended '.0' if s did not contain a '.'
  }
function appendDot(s: String):String;
//will append ".0" if no dot is in the string, if possible before an "E"
begin
  if pos(copy(s,1,1),'-0123456789')>0 then
  begin
    if pos('.',s)<1 then
      if pos('E',s)<1 then
        if pos('e',s)<1 then
          result:=s+'.0'
        else
          result:=copy(s,1,pos('e',s)-1)+'.0'+copy(s,pos('e',s),length(s))
      else
        result:=copy(s,1,pos('E',s)-1)+'.0'+copy(s,pos('E',s),length(s))
    else
      result:=s;
  end
  else
    result:=s;
end;

//! Replace a tag representing a number +/-X
{!
  \private
  \param s string one line of the template file
  \param tag string begin of a tag in the format '<numAbcTracer' (no '>')
  \param value integer of the unmodified tag
  \author Daniel Neumann, daniel.neumann@io-warnemuende.de

  There might be the demand that tags representing the number (of a group) of
  tracers should be incremented or decremented by 1, 2 or another integer
  number. The basic tag looks <numMovingTracers> and a tag for incrementing it
  by 2 looks <numMovingTracers+2>, whereas a tag for decrementing it by 3 looks
  like <numMovingTracers-3>. In previous version, these increment and decrement
  tags were hard-coded. Now this functions resolves it.
 }
function StringReplaceTagPlusMinus(s: String; tag: String; val: integer):String;
var stPos, enPos, sLen, tLen: Integer;
begin
  stPos:=pos(tag,s);
  tLen:=length(tag);
  sLen:=length(s);
  if (sLen > tLen) and (stPos > 0) then
  begin
    if copy(s,stPos+tLen,1)='>' then
      s:=StringReplace(s,tag+'>',IntToStr(val),[rfReplaceAll, rfIgnoreCase])
    else
    begin
      enPos:=stPos+tLen;
      while copy(s,enPos,1)<>'>' do
        enPos:=enPos+1;
      s:=StringReplace(s,copy(s,stPos,enPos-stPos+1),IntToStr(val+StrToInt(copy(s,stPos+tLen,enPos-stPos-tLen))),[rfReplaceAll, rfIgnoreCase]);
    end;
  end;
  result:=s;
end;



//******************* TimeTendencies ************************************//

//! Tests whether a process fullfills the conditions
{!
  \private
  \param p integer index of the process
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke

  Possible conditions tested:
  - vertloc: WAT (whole water column), SED (sediment only), SUR (surface only), FIS (fish=like)
  - isflat: deprecated, see vertloc
  - isinporewater:

  Calls
  - SplitConditions().
                                                                    }
function RateCondition(p: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if ((lhs[j]='isflat=') or (lhs[j]='vertloc=')) and (processes[p].vertLoc<>StrToIntVertLoc(rhs[j])) then isOk:=false;
    if ((lhs[j]='isflat/=') or (lhs[j]='vertloc/=')) and (processes[p].vertLoc=StrToIntVertLoc(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater=') and (processes[p].myIsInPorewater<>StrToInt(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater/=') and (processes[p].myIsInPorewater=StrToInt(rhs[j])) then isOk:=false;
  end;
  result:=isOk;
end;

//! Creates rates / timeTendencies for the current tracer
{!
  \private
  \param s string: line with <timeTendencies ... > tag
  \param Q input text file
  \param Z output text file
  \param t integer index of the parent tracer
  \param origLines StringList containing the lines of the tracer configuration file
  \param l current line number index (integer) in the tracer configuration file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<timeTendencies ...>' is in the template.

  Supports the content tags
  - <timeTendency>
  - <timeTendency2d>
  - <description>

  No sub-loop tags are in order.

  Calls
  - RateCondition()
  - convertToOutputLanguage()

  TODO
                                                                    }
procedure RateLoop(s: String; var Q: TextFile; var Z: TextFile; t: Integer; var origLines: TStringList; var l: Integer);
// execute the loop <timeTendencies> </timeTendencies> over all processes that impose creation or destruction rates on the tracer t.
const epsilon='0.00000000001';
var conditions: String;
    i, j, tt, tti, ttimax, ll: Integer;
    tfactor: String;
    loopFinished: Boolean;
    ratestring, ratestring2d: String;
begin
 DecimalSeparator:='.';
 conditions:=lowercase(copy(trim(s),17,length(trim(s))-17));
 if tracers[t].isCombined=1 then
   ttimax:=length(tracers[t].contents)-1
 else
   ttimax:=0;
 for tti:=0 to ttimax do
 begin
  if tracers[t].isCombined=1 then
    tt:=tracers[t].contents[tti].myElementNum;
  //output loop
  for i:=0 to length(processes)-1 do
  begin
   if processes[i].isActive=1 then
   begin
    if RateCondition(i,conditions) then    //check if conditions for the rates apply
    begin
      for j:=0 to length(processes[i].output)-1 do   //all products of this process are checked
      begin
        if tracers[t].isCombined=0 then              //standard tracer, the default case
        begin
          if processes[i].output[j].myTracerNum=t then   //the process i produces tracer t
          begin
            ll:=l+1;
            loopFinished:=false;
            while not loopFinished do
            begin
              s:=origLines[ll];
              if ll=origLines.Count-1 then LoopFinished:=true;
              if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
              else
              begin
                if (processes[i].output[j].amount='1') or (processes[i].output[j].amount='1.0') then
                  ratestring:='+ '+processes[i].name
                else
                  ratestring:='+ ('+processes[i].name+')*('+processes[i].output[j].amount+')';
                //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                ratestring2d:=ratestring;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                  ratestring:=ratestring+'*'+cellheightTimesDensity;
                convertToOutputLanguage(ratestring);
                s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<description>',processes[i].description,[rfReplaceAll, rfIgnoreCase]);
                if pos('<nonewline>',lowercase(s))>0 then
                begin
                  s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                  write(Z,s);
                end
                else
                  writeln(Z,s);
              end;
              ll:=ll+1;
            end;
          end;
        end
        else  //combined tracer, that means, it contains several (active or inactive) other tracers, but no elements
        begin
          tfactor:=tracers[t].contents[tti].amount;    //scaling factor to convert between t concentration and tt concentration
          if processes[i].output[j].myTracerNum=tt then  //the process i procuces tracer tt
          begin
            ll:=l+1;
            loopFinished:=false;
            while not loopFinished do
            begin
              s:=origLines[ll];
              if ll=origLines.Count-1 then LoopFinished:=true;
              if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
              else
              begin
                if (processes[i].output[j].amount='1') or (processes[i].output[j].amount='1.0') then
                  ratestring:='+ ('+tfactor+')*('+processes[i].name+')'
                else
                  ratestring:='+ ('+tfactor+')*('+processes[i].name+')*('+processes[i].output[j].amount+')';
                //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                ratestring2d:=ratestring;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                  ratestring:=ratestring+'*'+cellheightTimesDensity;
                convertToOutputLanguage(ratestring);
                s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<description>',processes[i].description+' (produces '+tracers[tt].name+')',[rfReplaceAll, rfIgnoreCase]);
                if pos('<nonewline>',lowercase(s))>0 then
                begin
                  s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                  write(Z,s);
                end
                else
                  writeln(Z,s);
              end;
              ll:=ll+1;
            end;
          end;
        end;
      end;
    end;
   end;
  end;
  //input loop
  for i:=0 to length(processes)-1 do
  begin
   if processes[i].isActive=1 then
   begin
    if RateCondition(i,conditions) then
    begin
      if tracers[t].myChildOf<0 then                 //the tracer is not such a one that was automatically generated by splitting the colored elements
      begin
        if tracers[t].isCombined=0 then              //standard tracer, the default case
        begin
          for j:=0 to length(processes[i].input)-1 do
          begin
            if processes[i].input[j].myTracerNum=t then
            begin
              ll:=l+1;
              loopFinished:=false;
              while not loopFinished do
              begin
                s:=origLines[ll];
                if ll=origLines.Count-1 then LoopFinished:=true;
                if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
                else
                begin
                  if (processes[i].input[j].amount='1') or (processes[i].input[j].amount='1.0') then
                    ratestring:='- '+processes[i].name
                  else
                    ratestring:='- ('+processes[i].name+')*('+processes[i].input[j].amount+')';
                  //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                  ratestring2d:=ratestring;
                  if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                    ratestring:=ratestring+'/'+cellheightTimesDensity;
                  if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                    ratestring:=ratestring+'/'+cellheightTimesDensity;
                  if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                    ratestring:=ratestring+'*'+cellheightTimesDensity;
                  convertToOutputLanguage(ratestring);
                  s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                  s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                  s:=StringReplace(s,'<description>',processes[i].description,[rfReplaceAll, rfIgnoreCase]);
                  if pos('<nonewline>',lowercase(s))>0 then
                  begin
                    s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                    write(Z,s);
                  end
                  else
                    writeln(Z,s);
                end;
                ll:=ll+1;
              end;
            end;
          end;
        end
        else
        begin
          tfactor:=tracers[t].contents[tti].amount;    //scaling factor to convert between t concentration and tt concentration
          for j:=0 to length(processes[i].input)-1 do
          begin
            if processes[i].input[j].myTracerNum=tt then //the process i consumes tracer tt
            begin
              ll:=l+1;
              loopFinished:=false;
              while not loopFinished do
              begin
                s:=origLines[ll];
                if ll=origLines.Count-1 then LoopFinished:=true;
                if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
                else
                begin
                  if (processes[i].input[j].amount='1') or (processes[i].input[j].amount='1.0') then
                    ratestring:='- ('+tfactor+')*('+processes[i].name+')'
                  else
                    ratestring:='- ('+tfactor+')*('+processes[i].name+')*('+processes[i].input[j].amount+')';
                  //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                  ratestring2d:=ratestring;
                  if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                    ratestring:=ratestring+'/'+cellheightTimesDensity;
                  if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                    ratestring:=ratestring+'/'+cellheightTimesDensity;
                  if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                    ratestring:=ratestring+'*'+cellheightTimesDensity;
                  convertToOutputLanguage(ratestring);
                  s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                  s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                  s:=StringReplace(s,'<description>',processes[i].description+' (consumes '+tracers[tt].name+')',[rfReplaceAll, rfIgnoreCase]);
                  if pos('<nonewline>',lowercase(s))>0 then
                  begin
                    s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                    write(Z,s);
                  end
                  else
                    writeln(Z,s);
                end;
                ll:=ll+1;
              end;
            end;
          end;
        end;
      end
      else //This tracer was automatically generated during splitting of
           //So, only its mother is used as process precursor.
           //Still, it is also destroyed when the mother tracer is taken up by some process.
      begin
        for j:=0 to length(processes[i].input)-1 do
        begin
          if processes[i].input[j].myTracerNum=tracers[t].myChildOf then
          begin
            ll:=l+1;
            loopFinished:=false;
            while not loopFinished do
            begin
              s:=origLines[ll];
              if ll=origLines.Count-1 then LoopFinished:=true;
              if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
              else
              begin
                if (processes[i].input[j].amount='1') or (processes[i].input[j].amount='1.0') then
                  ratestring:='- '+processes[i].name
                else
                  ratestring:='- ('+processes[i].name+')*('+processes[i].input[j].amount+')';
                //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                ratestring2d:=ratestring;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                  ratestring:=ratestring+'*'+cellheightTimesDensity;
                convertToOutputLanguage(ratestring);
                ratestring:=ratestring+'*max(0.0,min(1.0,'+tracers[t].name+'/max('+epsilon+','
                                            +tracers[tracers[t].myChildOf].name+')))';
                ratestring2d:=ratestring2d+'*max(0.0,min(1.0,'+tracers[t].name+'/max('+epsilon+','
                                            +tracers[tracers[t].myChildOf].name+')))';
                  //This factor is to make sure that the colored tracer is taken up with the same relative rate as the mother tracer.
                s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<description>',processes[i].description,[rfReplaceAll, rfIgnoreCase]);
                if pos('<nonewline>',lowercase(s))>0 then
                begin
                  s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                  write(Z,s);
                end
                else
                  writeln(Z,s);
              end;
              ll:=ll+1;
            end;
          end;
        end;
        //still, this tracer may occur as "input" in one case: The repainting of "none element = color"
        for j:=0 to length(processes[i].input)-1 do
        begin
          if processes[i].input[j].myTracerNum=t then
          begin
            ll:=l+1;
            loopFinished:=false;
            while not loopFinished do
            begin
              s:=origLines[ll];
              if ll=origLines.Count-1 then LoopFinished:=true;
              if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true
              else
              begin
                if (processes[i].input[j].amount='1') or (processes[i].input[j].amount='1.0') then
                  ratestring:='- '+processes[i].name
                else
                  ratestring:='- ('+processes[i].name+')*('+processes[i].input[j].amount+')';
                //flat processes have rates in [mmol/m**2/day)], non-flat tracers have unit [mmol/m**3]
                ratestring2d:=ratestring;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=1) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=0) and (processes[i].vertLoc=2) then
                  ratestring:=ratestring+'/'+cellheightTimesDensity;
                if (tracers[t].vertLoc=3) and (processes[i].vertLoc=0) then
                  ratestring:=ratestring+'*'+cellheightTimesDensity;
                convertToOutputLanguage(ratestring);
                s:=StringReplace(s,'<timeTendency>',makeLonger(ratestring,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<timeTendency2d>',makeLonger(ratestring2d,30),[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<description>',processes[i].description,[rfReplaceAll, rfIgnoreCase]);
                if pos('<nonewline>',lowercase(s))>0 then
                begin
                  s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                  write(Z,s);
                end
                else
                  writeln(Z,s);
              end;
              ll:=ll+1;
            end;
          end;
        end;
      end;
    end;
   end;
  end;
 end;
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,16))='</timetendencies' then LoopFinished:=true;
        ll:=ll+1;
      end;
      l:=ll-1;
end;


//******************* children *************************************//

//! Tests whether a child fullfills the conditions
{!
  \private
  \param p integer index of the tracer/child
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Forwards p and c to RateCondition().

  Calls
  - RateCondition().
                                                                    }
function ChildCondition(p: Integer; c: String):Boolean;
begin
  result:=RateCondition(p, c);
end;

//! Iterates Children of a certain tracer
{!
  \private
  \param s string: line with <children ... > tag
  \param Q input text file
  \param Z output text file
  \param t integer index of the parent tracer
  \param origLines StringList containing the lines of the tracer configuration file
  \param l current line number index (integer) in the tracer configuration file
  \param e integer index of the basic element
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<children ...>' is in the template.

  Supports the content tags
  - <childName>
  - <trimChildName>
  - <name>
  - <trimName>

  No sub-loop tags are in order.

  Calls
  - ChildCondition()

  TODO
                                                                    }
procedure ChildLoop(s: String; var Q: TextFile; var Z: TextFile; t: Integer; var origLines: TStringList; var l: Integer; const e: Integer = -1);
var conditions: String;
    i1,ll, i2, i3: Integer;
    loopFinished: Boolean;
    processTracer: Boolean;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),11,length(trim(s))-11));
  //output loop
  for i1:=0 to length(tracers)-1 do
   if tracers[i1].isActive=1 then
   if tracers[i1].myChildOf=t then
   {!
      At least one element of the tracer needs to have the element e (NOT THE
      COLORED ELEMENT!) or e is set to -1: Then we process this tracer.
      Otherwise not.
      }
   begin
     processTracer:=false;
     if (e=-1) then
       processTracer:=true
     else
       for i2:=0 to length(tracers[i1].contents)-1 do
         if (elements[tracers[i1].contents[i2].myElementNum].parentElement=e) then
           processTracer:=true;

     if processTracer then
     begin
      if ChildCondition(i1,conditions) then
      begin
        ll:=l+1;
        loopFinished:=false;
        while not loopFinished do
        begin
          s:=origLines[ll];
          if ll=origLines.Count-1 then LoopFinished:=true;
          if lowercase(copy(trim(s),1,10))='</children' then LoopFinished:=true
          else
          begin
                s:=StringReplace(s,'<childName>',tracers[i1].name,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<name>',tracers[t].name,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<trimChildName>',tracers[i1].name,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<trimName>',tracers[t].name,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<tracerAbove>',tracers[i1].tracerAbove,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<tracerBelow>',tracers[i1].tracerBelow,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<childLongname>',tracers[i1].longname,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<longname>',tracers[t].longname,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<childUnit>',tracers[i1].outputUnit,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<unit>',tracers[t].outputUnit,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<childStandardname>',tracers[i1].stdname_prefix+tracers[i1].longname+tracers[i1].stdname_suffix,[rfReplaceAll, rfIgnoreCase]);
                s:=StringReplace(s,'<standardname>',tracers[t].stdname_prefix+tracers[t].longname+tracers[t].stdname_suffix,[rfReplaceAll, rfIgnoreCase]);
                if pos('<nonewline>',lowercase(s))>0 then
                begin
                  s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                  write(Z,s);
                end
                else
                  writeln(Z,s);
          end;
          ll:=ll+1;
        end;
      end;
     end;
   end;
  ll:=l+1;
  loopFinished:=false;
  while not loopFinished do
  begin
    s:=origLines[ll];
    if ll=origLines.Count-1 then LoopFinished:=true;
    if lowercase(copy(trim(s),1,10))='</children' then LoopFinished:=true;
    ll:=ll+1;
  end;
  l:=ll-1;
end;


//******************* limitations ********************************************//

//! Creates limitations of the current tracer
{!
  \private
  \param s string: line with <limitations ... > tag
  \param Q input text file
  \param Z output text file
  \param t integer index of the parent tracer
  \param origLines StringList containing the lines of the tracer configuration file
  \param l current line number index (integer) in the tracer configuration file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<limitations ...>' is in the template.

  Supports the content tags
  - <name> (name of limition; not name of tracer)
  - <trimName>
  - <formula>

  No sub-loop tags are in order.

  Calls:
  - convertToOutputLanguage()

  TODO
                                                                    }
procedure LimitationLoop(s: String; var Q: TextFile; var Z: TextFile; t: Integer; var origLines: TStringList; var l: Integer);
// execute the loop <limitations> </limitations> over all limitation functions that depend on the tracer t.
var
  i,ll: Integer;
  loopFinished: Boolean;
  myFormula: String;
begin
  for i:=0 to length(limitations)-1 do
  begin
    if limitations[i].myTracerNum=t then
    begin
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,13))='</limitations' then LoopFinished:=true
        else
        begin
          if limitations[i].limitationType='HARD' then       // theta function
            myFormula:='theta('+limitations[i].tracer+'-'+limitations[i].value+')'
          else if limitations[i].limitationType='MM' then    // Michaelis-Menten
            myFormula:=limitations[i].tracer+'/('+limitations[i].tracer+'+'+limitations[i].value+')'
          else if limitations[i].limitationType='MMQ' then   // quadratic Michaelis-Menten
            myFormula:=limitations[i].tracer+'*'+limitations[i].tracer+'/('
                       +limitations[i].tracer+'*'+limitations[i].tracer+'+'+limitations[i].value+'*'+limitations[i].value+')'
          else if limitations[i].limitationType='IV' then    // Ivlev
            myFormula:='1.0-exp(-'+limitations[i].tracer+'/'+limitations[i].value+')'
          else if limitations[i].limitationType='IVQ' then   // quadratic Ivlev
            myFormula:='1.0-exp(-'+limitations[i].tracer+'**2/'+limitations[i].value+'**2)'
          else if limitations[i].limitationType='LIN' then   // linear
            myFormula:='min('+limitations[i].tracer+'/'+limitations[i].value+',1.0)'
          else if limitations[i].limitationType='TANH' then   // linear
            myFormula:='tanh('+limitations[i].tracer+'/'+limitations[i].value+')';
          convertToOutputLanguage(myFormula);
          s:=StringReplace(s,'<name>',makeLonger('lim_'+limitations[i].tracer+'_'+IntToStr(i),20),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimName>','lim_'+limitations[i].tracer+'_'+IntToStr(i),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<formula>',myFormula,[rfReplaceAll, rfIgnoreCase]);
          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
        end;
        ll:=ll+1;
      end;
    end;
  end;
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,13))='</limitations' then LoopFinished:=true;
        ll:=ll+1;
      end;
      l:=ll-1;
end;

//******************* containing tracers (of cElements) **********************//

//! Tests whether a tracer fullfills the conditions
{!
  \private
  \param i integer index of the tracer
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - vertloc: WAT (whole water column), SED (sediment only), SUR (surface only), FIS (fish=like)
  - isflat: deprecated, see vertloc
  - isinporewater:
  - vertspeed: has given vertical speed

  Calls
  - SplitConditions().
                                                                    }
function ctCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if ((lhs[j]='isflat=') or (lhs[j]='vertloc=')) and (tracers[i].vertLoc<>StrToIntVertLoc(rhs[j])) then isOk:=false;
    if ((lhs[j]='isflat/=') or (lhs[j]='vertloc/='))and (tracers[i].vertLoc=StrToIntVertLoc(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater=') and (IntToStr(tracers[i].isInPorewater)<>rhs[j]) then isOk:=false;
    if (lhs[j]='isinporewater/=') and (IntToStr(tracers[i].isInPorewater)=rhs[j]) then isOk:=false;    
    if (lhs[j]='vertspeed=') and (tracers[i].vertSpeed <> rhs[j]) then isOk:=false;
    if (lhs[j]='vertspeed/=') and (tracers[i].vertSpeed = rhs[j]) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all tracers with defined color and element
{!
  \private
  \param s string: line with <containingtracers ... > tag
  \param Q input text file
  \param Z output text file
  \param ce integer index of the colored element
  \param origLines StringList containing the lines of the tracer configuration file
  \param l current line number index (integer) in the tracer configuration file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<containingtracers ...>' is in the template.

  Supports the content tags
  - <ctNumFlat> internal index of a flat tracer
  - <ctIndexNum> internal index of a tracer
  - <ct> tracer name
  - <cpI> tracer's parents
  - <ctAmount> tracer's amount
  - <ctNumMoving> internal index of a moving tracer

  No sub-loop tags are in order.

  Calls
  - ctCondition()

  Loops through all tracers with same element and color as defined by ce.
                                                                    }
procedure ctLoop(s: String; var Q: TextFile; var Z: TextFile; ce: Integer; var origLines: TStringList; var l: Integer);
var conditions: String;
    i,ll: Integer;
    loopFinished: Boolean;
    Current3dTracerNum, CurrentFlatTracerNum, CurrentMovingTracerNum: Integer;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),20,length(trim(s))-20));
  Current3dTracerNum:=0; CurrentFlatTracerNum:=0; CurrentMovingTracerNum:=0;
  for i:=0 to length(tracers)-1 do
  begin
   if tracers[i].vertLoc=0 then Current3dTracerNum:=Current3dTracerNum+1
                          else CurrentFlatTracerNum:=CurrentFlatTracerNum+1;
   if (tracers[i].vertLoc=0) and (tracers[i].vertSpeed<>'0') then
     CurrentMovingTracerNum:=CurrentMovingTracerNum+1;

   if pos('_with_'+cElements[ce].color+'_'+cElements[ce].element,tracers[i].name)>0 then
   begin
    if ctCondition(i,conditions) then
    begin
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,19))='</containingtracers' then
          LoopFinished:=true
        else
        begin
              s:=StringReplace(s,'<ctNumFlat>',IntToStr(currentFlatTracerNum),[rfReplaceAll, rfIgnoreCase]);
              if tracers[i].vertLoc=0 then
                s:=StringReplace(s,'<ctIndexNum>',IntToStr(current3dTracerNum),[rfReplaceAll, rfIgnoreCase])
              else
                s:=StringReplace(s,'<ctIndexNum>',IntToStr(currentFlatTracerNum),[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ct>',tracers[i].name,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<cpI>',tracers[i].childOf,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctAmount>',tracers[i].contents[0].amount,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctNumMoving>',IntToStr(CurrentMovingTracerNum),[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctName>',makeLonger(tracers[i].name,20),[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctTrimName>',tracers[i].name,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctLongname>',tracers[i].longname,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctUnit>',tracers[i].outputUnit,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctStandardname>',tracers[i].stdname_prefix+tracers[i].longname+tracers[i].stdname_suffix,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctChildof>',tracers[i].childof,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctAbove>',tracers[i].tracerAbove,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctBelow>',tracers[i].tracerBelow,[rfReplaceAll, rfIgnoreCase]);
              s:=StringReplace(s,'<ctDescription>',tracers[i].description,[rfReplaceAll, rfIgnoreCase]);
              if pos('<nonewline>',lowercase(s))>0 then
              begin
                s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
                write(Z,s);
              end
              else
                writeln(Z,s);
        end;
        ll:=ll+1;
      end;
    end;
   end;
  end;
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,19))='</containingtracers' then LoopFinished:=true;
        ll:=ll+1;
      end;
      l:=ll-1;
end;

//******************* Constants ************************************//

//! Tests whether a constant fullfills the conditions
{!
  \private
  \param i integer index of the constant
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - name:
  - dependson:
  - variation:

  Calls
  - SplitConditions().
                                                                    }
function ConstantCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if (lhs[j]='name=') and (lowercase(constants[i].name)<>rhs[j]) then isOk:=false;
    if (lhs[j]='name/=') and (lowercase(constants[i].name)=rhs[j]) then isOk:=false;
    if (lhs[j]='dependson=') and (lowercase(constants[i].dependsOn)<>rhs[j]) then isOk:=false;
    if (lhs[j]='dependson/=') and (lowercase(constants[i].dependsOn)=rhs[j]) then isOk:=false;
    if (lhs[j]='variation=') and ((constants[i].minval<-0.9e20) xor (rhs[j]='0')) then isOk:=false; // the xor becomes true if left and right () do not match, left means "constant does not vary", right means "constant shall not vary".
    if (lhs[j]='variation/=') and ((constants[i].minval<-0.9e20) xor (rhs[j]='1')) then isOk:=false; //if a constant has no variation, then its minimum value is -1.0e20
    if (lhs[j]='isselected=') and ((constants[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
    if (lhs[j]='isselected/=') and not ((constants[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all constants
{!
  \private
  \param s string: line with <constants ... > tag
  \param Q input text file
  \param Z output text file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<constants ...>' is in the template.

  Supports the content tags
  - <name>
  - <trimName>
  - <value>
  - <minval>
  - <maxval>
  - <varphase>
  - <numVarying>
  - <description>
  - <comment>

  No sub-loop tags are in order.

  Calls
  - ConstantCondition()
  - convertToOutputLanguage()
                                                                    }
procedure ConstantLoop(s: String; var Q: TextFile; var Z: TextFile);
var conditions: String;
    i: Integer;
    s1: String;
    myLinesRead: Integer;
    myValue, myMinval, myMaxval: String;
    origLines: TStringList;
    l: Integer;
    myNumVarying: Integer;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),12,length(trim(s))-12));
  myLinesRead:=0;
  s1:='';
  origLines:=TStringList.Create;
  while not (lowercase(copy(trim(s1),1,11))='</constants') and not EOF(Q) do
  begin
    readln(Q,s1);
    myLinesRead:=myLinesRead+1;
    origLines.Add(s1);
  end;
  origLines.Delete(OrigLines.Count-1);
  myNumVarying:=modelinfos.numPhysicalParametersVarying;
  for i:=0 to length(constants)-1 do
  begin
    if constants[i].minval>-0.9e20 then myNumVarying:=myNumVarying+1;
    if ConstantCondition(i,conditions) then
    begin
      myValue:=makeLonger(AppendDot(FloatToStr(constants[i].value)),8);
      myMinval:=makeLonger(AppendDot(FloatToStr(constants[i].minval)),8);
      myMaxval:=makeLonger(AppendDot(FloatToStr(constants[i].maxval)),8);
      convertToOutputLanguage(myValue);
      for l:=0 to origLines.count-1 do
      begin
          s:=origLines[l];
          s:=StringReplace(s,'<name>',makeLonger(constants[i].name,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimName>',constants[i].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<value>',myValue,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<minval>',myMinval,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<maxval>',myMaxval,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<varphase>',IntToStr(constants[i].varphase),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<numVarying>',IntToStr(myNumVarying),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<description>',constants[i].description,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<comment>',constants[i].comment,[rfReplaceAll, rfIgnoreCase]);
          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
      end;
    end;
  end;
  LinesRead:=LinesRead+MyLinesRead;
  OrigLines.Free;
end;


//******************* Elements ************************************//

//! Tests whether an element fullfills the conditions
{!
  \private
  \param i integer index of the element
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Daniel Neumann, daniel.neumann@io-warnemuende.de

  Possible conditions tested:
  - element: the name of the uncolored element
  - hascoloredcopies: 0 or 1; 1: colored versions of this element exist

  Calls
  - SplitConditions().
                                                                    }
function ElementCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if ((lhs[j]='element=') and (lowercase(elements[i].name)<>rhs[j])) then isOk:=false;
    if ((lhs[j]='element/=') and (lowercase(elements[i].name)=rhs[j])) then isOk:=false;
    if ((lhs[j]='hascoloredcopies=') and (IntToStr(elements[i].hasColoredCopies)<>rhs[j])) then isOk:=false;
    if ((lhs[j]='hascoloredcopies/=') and (IntToStr(elements[i].hasColoredCopies)=rhs[j])) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all elements (the uncolored ones) of the current tracer
{!
  \private
  \param s string: line with <elements ... > tag
  \param Q input text file
  \param Z output text file
  \param t integer index of the parent tracer
  \param origLines StringList containing the lines of the tracer configuration file
  \param l current line number index (integer) in the tracer configuration file
  \author Daniel Neumann, daniel.neumann@io-warnemuende.de

  Is called when the tag '<elements ...>' is in the template.

  Supports the content tags
  - <element>: uncolored name of the element
  - <trimElement>: trimed version of <element>
  - <name>: Name of the tracer of the parent tracer-loop
  - <trimName>: trimed version of <name>

  The following sub-loop tags are in order:
  - <timetendencies ...>: TODO
  - <children ...>: only children of t, which represent amount of element e (all colors)
  - <limitations ...>: no changes in the behaviour compared to a direct call from within the <tracer>-loop

  Calls
  - ElementCondition()
  - ChildLoop() on <children ...> (optional)
  - RateLoop() on <timetendencies ...> (optional)
  - LimitationLoop() on <limitations ...> (optional)

  TODO: Reasons for introducing <elements>
                                                                    }
procedure ElementLoop(s: String; var Q: TextFile; var Z: TextFile; t: Integer; var origLines: TStringList; var l: Integer);
var conditions: String;
    e: Integer;
    i,ll: Integer;
    loopFinished: Boolean;
begin
  DecimalSeparator:='.';
  //
  conditions:=lowercase(copy(trim(s),11,length(trim(s))-11));
  for i:=0 to length(tracers[t].contents)-1 do
  begin
    e:=elements[tracers[t].contents[i].myElementNum].parentElement;
    if ElementCondition(e,conditions) then
    begin
      ll:=l+1;
      loopFinished:=false;
      while not loopFinished do
      begin
        s:=origLines[ll];
        if ll=origLines.Count-1 then LoopFinished:=true;
        if lowercase(copy(trim(s),1,10))='</elements' then LoopFinished:=true
        else if lowercase(copy(trim(s),1,15))='<timetendencies' then
          RateLoop(s,Q,Z,t,origLines,ll)
        else if lowercase(copy(trim(s),1,9))='<children' then
          ChildLoop(s,Q,Z,t,origLines,ll,e)
          // ChildLoop(s,Q,Z,t,origLines,ll,-1)
        else if lowercase(copy(trim(s),1,12))='<limitations' then
          LimitationLoop(s,Q,Z,t,origLines,ll)
        else
        begin
          s:=StringReplace(s,'<element>',elements[e].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimElement>',elements[e].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<name>',tracers[t].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimName>',tracers[t].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<description>',tracers[t].description,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<elementDescription>',elements[e].description,[rfReplaceAll, rfIgnoreCase]);
          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
        end;
        ll:=ll+1;
      end;
    end;
  end;

  // Iterate the whole section in case that the conditions
  // were never TRUE for any element above.
  ll:=l+1;
  loopFinished:=false;
  while not loopFinished do
  begin
    s:=origLines[ll];
    if ll=origLines.Count-1 then LoopFinished:=true;
    if lowercase(copy(trim(s),1,10))='</elements' then LoopFinished:=true;
    ll:=ll+1;
  end;
  l:=ll-1;

end;


//******************* Tracers ************************************//

//! Tests whether a tracer has rates / time tendencies
{!
  \private
  \param t integer index of a tracer
  \return true/false: "has rates" / "has no rates"
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de
  }
function hasRates(t: Integer):Boolean;
var p, i, k, tt: Integer;
    found: Boolean;
begin
  found:=false;
  if tracers[t].myChildOf < 0 then
  begin
    if tracers[t].isCombined = 0 then
    begin
      for p:=0 to length(processes)-1 do
      begin
        for i:=0 to length(processes[p].input)-1 do
          if processes[p].input[i].myTracerNum=t then found:=true;
        for i:=0 to length(processes[p].output)-1 do
          if processes[p].output[i].myTracerNum=t then found:=true;
      end;
    end
    else //combined tracer
    begin
      for k:=0 to length(tracers[t].contents)-1 do
      begin
        tt:=tracers[t].contents[k].myElementNum;   //the real tracer is tt
        for p:=0 to length(processes)-1 do
        begin
          for i:=0 to length(processes[p].input)-1 do
            if processes[p].input[i].myTracerNum=tt then found:=true;
          for i:=0 to length(processes[p].output)-1 do
            if processes[p].output[i].myTracerNum=tt then found:=true;
        end;
      end;
    end;
  end
  else        //colored tracer, so only mother tracers are used as process input
    for p:=0 to length(processes)-1 do
    begin
      for i:=0 to length(processes[p].input)-1 do
        if processes[p].input[i].myTracerNum=tracers[t].myChildOf then found:=true;
      for i:=0 to length(processes[p].output)-1 do
        if processes[p].output[i].myTracerNum=t then found:=true;
    end;
  result:=found;
end;

//! Tests whether a flat tracer has rates / time tendencies
{!
  \private
  \param t integer index of a tracer
  \param sf vertical location (WAT, SED, SUR, FIS)
  \return true/false: "has rates" / "has no rates"
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de
  }
function hasRatesFlat(t: Integer; sf: String):Boolean;
var p, i, k, tt: Integer;
    found: Boolean;
    f: Integer;
begin
  if lowercase(sf)='wat' then f:=0
  else if lowercase(sf)='sed' then f:=1
  else if lowercase(sf)='sur' then f:=2
  else if lowercase(sf)='fis' then f:=3
  else f:=StrToInt(sf);
  found:=false;
  if tracers[t].myChildOf < 0 then
  begin
    if tracers[t].isCombined = 0 then
    begin
      for p:=0 to length(processes)-1 do
      begin
        if processes[p].vertLoc=f then
        begin
          for i:=0 to length(processes[p].input)-1 do
            if processes[p].input[i].myTracerNum=t then found:=true;
          for i:=0 to length(processes[p].output)-1 do
            if processes[p].output[i].myTracerNum=t then found:=true;
        end;
      end;
    end
    else
    begin //combined tracer
      for k:=0 to length(tracers[t].contents)-1 do
      begin
        tt:=tracers[t].contents[k].myElementNum;   //the real tracer is tt
        for p:=0 to length(processes)-1 do
        begin
          if processes[p].vertLoc=f then
          begin
            for i:=0 to length(processes[p].input)-1 do
              if processes[p].input[i].myTracerNum=tt then found:=true;
            for i:=0 to length(processes[p].output)-1 do
              if processes[p].output[i].myTracerNum=tt then found:=true;
          end;
        end;
      end;
    end;
  end
  else  //colored tracer, so only mother tracers are used as process input
    for p:=0 to length(processes)-1 do
    begin
      if processes[p].vertLoc=f then
      begin
        for i:=0 to length(processes[p].input)-1 do
          if processes[p].input[i].myTracerNum=tracers[t].myChildOf then found:=true;
        for i:=0 to length(processes[p].output)-1 do
          if processes[p].output[i].myTracerNum=t then found:=true;
      end;
    end;
  result:=found;
end;

//! Tests whether a tracer fullfills the conditions
{!
  \private
  \param i integer index of the tracer
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - vertloc: WAT (whole water column), SED (sediment only), SUR (surface only), FIS (fish=like)
  - isflat: deprecated, see vertloc
  - isinporewater:
  - a lot more ... (TODO)

  Calls
  - SplitConditions().
                                                                    }
function TracerCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j, k: Integer;
    myHasCETotal, myHasCEAged: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  //check if this is a colored tracer which has a cElement with isTracer / isAging = 1
  myHasCETotal:=0; myHasCEAged:=0;
  for j:=0 to length(cElements)-1 do
    if pos('_with_'+cElements[j].color+'_'+cElements[j].element,tracers[i].name)>0 then
    begin
      if cElements[j].myIsTracer=1 then myHasCeTotal:=1;
      if cElements[j].myIsAging=1  then myHasCeAged:=1;
    end;

  for j:=0 to lhs.Count-1 do
  begin
    if (lhs[j]='name=') and (lowercase(tracers[i].name)<>rhs[j]) then isOk:=false;
    if (lhs[j]='name/=') and (lowercase(tracers[i].name)=rhs[j]) then isOk:=false;
    if (lhs[j]='vertspeed=') and (lowercase(tracers[i].vertSpeed)<>rhs[j]) then isOk:=false;
    if (lhs[j]='vertspeed/=') and (lowercase(tracers[i].vertSpeed)=rhs[j]) then isOk:=false;
    if (lhs[j]='vertdiff=') and (lowercase(tracers[i].vertDiff)<>rhs[j]) then isOk:=false;
    if (lhs[j]='vertdiff/=') and (lowercase(tracers[i].vertDiff)=rhs[j]) then isOk:=false;
    if (lhs[j]='moldiff=') and (lowercase(tracers[i].molDiff)<>rhs[j]) then isOk:=false;
    if (lhs[j]='moldiff/=') and (lowercase(tracers[i].molDiff)=rhs[j]) then isOk:=false;
    if (lhs[j]='opacity=') and (lowercase(tracers[i].opacity)<>rhs[j]) then isOk:=false;
    if (lhs[j]='opacity/=') and (lowercase(tracers[i].opacity)=rhs[j]) then isOk:=false;
    if ((lhs[j]='isflat=') or (lhs[j]='vertloc=')) and (tracers[i].vertLoc<>StrToIntVertLoc(rhs[j])) then isOk:=false;
    if ((lhs[j]='isflat/=') or (lhs[j]='vertloc/=')) and (tracers[i].vertLoc=StrToIntVertLoc(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater=') and (IntToStr(tracers[i].isInPorewater)<>rhs[j]) then isOk:=false;
    if (lhs[j]='isinporewater/=') and (IntToStr(tracers[i].isInPorewater)=rhs[j]) then isOk:=false;
    if (lhs[j]='ispositive=') and (IntToStr(tracers[i].isPositive)<>rhs[j]) then isOk:=false;
    if (lhs[j]='ispositive/=') and (IntToStr(tracers[i].isPositive)=rhs[j]) then isOk:=false;
    if (lhs[j]='ismixed=') and (IntToStr(tracers[i].isMixed)<>rhs[j]) then isOk:=false;
    if (lhs[j]='ismixed/=') and (IntToStr(tracers[i].isMixed)=rhs[j]) then isOk:=false;
    if (lhs[j]='isselected=') and ((tracers[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
    if (lhs[j]='isselected/=') and not ((tracers[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
    if (lhs[j]='childof=') and (lowercase(tracers[i].childOf)<>rhs[j]) then isOk:=false;
    if (lhs[j]='childof/=') and (lowercase(tracers[i].childOf)=rhs[j]) then isOk:=false;
    if (lhs[j]='hastimetendencies') and not hasRates(i) then isOk:=false;
    if (lhs[j]='hastimetendenciesvertloc=') and not hasRatesFlat(i,rhs[j]) then isOk:=false;
    if (lhs[j]='hastimetendenciesvertloc/=') and hasRatesFlat(i,rhs[j]) then isOk:=false;
    if (lhs[j]='atmosdep=') and (IntToStr(tracers[i].atmosDep)<>rhs[j]) then isOk:=false;
    if (lhs[j]='atmosdep/=') and (IntToStr(tracers[i].atmosDep)=rhs[j]) then isOk:=false;
    if (lhs[j]='riverdep=') and (IntToStr(tracers[i].riverDep)<>rhs[j]) then isOk:=false;
    if (lhs[j]='riverdep/=') and (IntToStr(tracers[i].riverDep)=rhs[j]) then isOk:=false;
    if (lhs[j]='isoutput=') and ( ((modelinfos.debugMode=0) and (tracers[i].isOutput<>StrToInt(rhs[j])))
                                or((modelinfos.debugMode=1)  and (1<>StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='isoutput/=') and ( ((modelinfos.debugMode=0) and (tracers[i].isOutput=StrToInt(rhs[j])))
                                or ((modelinfos.debugMode=1) and (1=StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='useinitvalue=') and (IntToStr(tracers[i].useInitValue)<>rhs[j]) then isOk:=false;
    if (lhs[j]='useinitvalue/=') and (IntToStr(tracers[i].useInitValue)=rhs[j]) then isOk:=false;
    if (lhs[j]='initvalue=') and (tracers[i].initValue<>rhs[j]) then isOk:=false;
    if (lhs[j]='initvalue/=') and (tracers[i].initValue=rhs[j]) then isOk:=false;
    if (lhs[j]='calcbeforezintegral=') and (IntToStr(tracers[i].myCalcBeforeZIntegral)<>rhs[j]) then isOk:=false;
    if (lhs[j]='calcbeforezintegral/=') and (IntToStr(tracers[i].myCalcBeforeZIntegral)=rhs[j]) then isOk:=false;
    if (lhs[j]='hascetotal') and (myHasCeTotal<>1) then isOk:=false;
    if (lhs[j]='hasceaged') and (myHasCeAged<>1) then isOk:=false;
    if (lhs[j]='solubility=') and (lowercase(tracers[i].solubility)<>rhs[j]) then isOk:=false;
    if (lhs[j]='solubility/=') and (lowercase(tracers[i].solubility)=rhs[j]) then isOk:=false;
    if (lhs[j]='tracerabove=') and (lowercase(tracers[i].tracerAbove)<>rhs[j]) then isOk:=false;
    if (lhs[j]='tracerabove/=') and (lowercase(tracers[i].tracerAbove)=rhs[j]) then isOk:=false;
    if (lhs[j]='tracerbelow=') and (lowercase(tracers[i].tracerBelow)<>rhs[j]) then isOk:=false;
    if (lhs[j]='tracerbelow/=') and (lowercase(tracers[i].tracerBelow)=rhs[j]) then isOk:=false;
    if (lhs[j]='hasabove=') and ( ( (rhs[j]='1') and (tracers[i].tracerAbove='none') )
                                or ( (rhs[j]='0') and (tracers[i].tracerAbove<>'none') ) ) then isOk:=false;
    if (lhs[j]='hasabove/=') and ( ( (rhs[j]='1') and (tracers[i].tracerAbove<>'none') )
                                or ( (rhs[j]='0') and (tracers[i].tracerAbove='none') ) ) then isOk:=false;
    if (lhs[j]='hasbelow=') and ( ( (rhs[j]='1') and (tracers[i].tracerBelow='none') )
                                or ( (rhs[j]='0') and (tracers[i].tracerBelow<>'none') ) ) then isOk:=false;
    if (lhs[j]='hasbelow/=') and ( ( (rhs[j]='1') and (tracers[i].tracerBelow<>'none') )
                                or ( (rhs[j]='0') and (tracers[i].tracerBelow='none') ) ) then isOk:=false;
    if (lhs[j]='longname=') and (lowercase(tracers[i].longname)<>rhs[j]) then isOk:=false;
    if (lhs[j]='longname/=') and (lowercase(tracers[i].longname)=rhs[j]) then isOk:=false;
    if (lhs[j]='unit=') and (lowercase(tracers[i].outputUnit)<>rhs[j]) then isOk:=false;
    if (lhs[j]='unit/=') and (lowercase(tracers[i].outputUnit)=rhs[j]) then isOk:=false;
    if (lhs[j]='color=') and (pos('_'+rhs[j]+'_',tracers[i].name)=0) then isOk:=false;
    if (lhs[j]='color/') and (pos('_'+rhs[j]+'_',tracers[i].name)>0) then isOk:=false;
    if (lhs[j]='contents=') and isOk=true then
    begin
      isOk:=false;
      for k:=0 to length(tracers[i].contents)-1 do
        if lowercase(tracers[i].contents[k].element)=rhs[j] then isOk:=true;
    end;
    if (lhs[j]='contents/=') and isOk=true then
    begin
      for k:=0 to length(tracers[i].contents)-1 do
        if lowercase(tracers[i].contents[k].element)=rhs[j] then isOk:=false;
    end;
    if (lhs[j]='solubility/=') and (lowercase(tracers[i].solubility)=rhs[j]) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all tracers
{!
  \private
  \param s string: line with <tracers ... > tag
  \param Q input text file
  \param Z output text file
  \param backward
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<tracers ...>' is in the template.

  Supports the content tags
  - a lot of ... (TODO)

  The following sub-loop tags are in order:
  - <timetendencies>
  - <children>
  - <limitations>
  - <elements>

  Calls
  - TracerCondition()
  - RateLoop()
  - ChildLoop()
  - LimitationLoop()
                                                                    }
procedure TracerLoop(s: String; var Q: TextFile; var Z: TextFile; backward: Boolean=false);
var conditions: String;
    i, j, ii, n, ifs: Integer;
    s1: String;
    myLinesRead:                  Integer;
    myNumFlat, myNum3D:           integer;
    myNumSur, myNumSed, myNumFis: integer;
    myNumRiverDep, myNumAtmosDep: Integer;
    myNumMoving:                  Integer;
    myNumTracer:                  Integer;
    myCETotalIndex: String;
    myCEAgedIndex:  String;
    myCETotalName:  String;
    myCEAgedName:   String;
    myCEAmount:     String;
    myChildOfNumMoving, myChildOfNumMovingTemp: Integer;
    parentI: Integer;
    origLines: TStringList;
    l: Integer;
begin
  DecimalSeparator:='.';
  if backward then
    conditions:=lowercase(copy(trim(s),18,length(trim(s))-18))
  else
    conditions:=lowercase(copy(trim(s),10,length(trim(s))-10));
  myLinesRead:=0;
  s1:='';
  origLines:=TStringList.Create;
  while not ((lowercase(copy(trim(s1),1,9))='</tracers') or (lowercase(copy(trim(s1),1,17))='</backwardtracers')) and not EOF(Q) do
  begin
    readln(Q,s1);
    myLinesRead:=myLinesRead+1;
    origLines.Add(s1);
  end;
  origLines.Delete(OrigLines.Count-1);
  for ii:=0 to length(Tracers)-1 do
  begin
   if backward then i:=length(tracers)-1-ii
   else i:=ii;
   if tracers[i].isActive=1 then
   begin
    myNumFlat:=0;
    myNumMoving:=0;
    myNum3D:=0;
    myNumSed:=0;
    myNumSur:=0;
    myNumFis:=0;
    myNumRiverDep:=0;
    myNumAtmosDep:=0;
    myNumTracer:=0;
    for n:=0 to i do
    begin
     if tracers[n].isActive=1 then
     begin
      if tracers[n].vertLoc <> 0 then myNumFlat:=myNumFlat+1;
      if tracers[n].vertLoc = 0 then myNum3D:=myNum3D+1;
      if tracers[n].vertLoc = 1 then myNumSed:=myNumSed+1;
      if tracers[n].vertLoc = 2 then myNumSur:=myNumSur+1;
      if tracers[n].vertLoc = 3 then myNumFis:=myNumFis+1;
      if tracers[n].riverDep=1 then myNumRiverDep:=myNumRiverDep+1;
      if tracers[n].atmosDep=1 then myNumAtmosDep:=myNumAtmosDep+1;
      if tracers[n].vertSpeed <> '0' then myNumMoving:=myNumMoving+1;
      myNumTracer:=myNumTracer+1;
     end;
    end;
    if TracerCondition(i,conditions) then
    begin
      myCETotalIndex:='-1';
      myCEAgedIndex:='-1';
      myCETotalName:='';
      myCEAgedName:='';
      myCEAmount:='0.0';
      for j:=0 to length(cElements)-1 do
        if pos('_with_'+cElements[j].color+'_'+cElements[j].element,tracers[i].name)>0 then
        begin
          if cElements[j].myIsTracer=1 then
          begin
            myCETotalIndex:='idx_total_'+cElements[j].color+'_'+cElements[j].element;
            myCETotalName:='total_'+cElements[j].color+'_'+cElements[j].element;
          end
          else
            myCETotalIndex:='-1';
          if cElements[j].myIsAging=1 then
          begin
            myCEAgedIndex:='idx_aged_'+cElements[j].color+'_'+cElements[j].element;
            myCEAgedName:='aged_'+cElements[j].color+'_'+cElements[j].element;
          end
          else
            myCETotalIndex:='-1';
          myCEAmount := tracers[i].contents[0].amount;
          myChildOfNumMovingTemp:=0;
          for parentI:=0 to length(tracers)-1 do
          begin
            if tracers[parentI].vertSpeed <> '0' then
              myChildOfNumMovingTemp:=myChildOfNumMovingTemp+1;
            if lowercase(tracers[parentI].name)=lowercase(tracers[i].childOf) then
              myChildOfNumMoving:=myChildOfNumMovingTemp;
          end;
        end;
      ifs:=0;
      l:=0;
      while l<=origLines.count-1 do
      begin
        s:=origLines[l];
        if ifs>0 then
        begin
          if lowercase(copy(trim(s),1,3))='<if' then
            ifs:=ifs+1
          else if lowercase(copy(trim(s),1,4))='</if' then
            ifs:=ifs-1;
        end
        else if lowercase(copy(trim(s),1,15))='<timetendencies' then
          RateLoop(s,Q,Z,i,origLines,l)
        else if lowercase(copy(trim(s),1,9))='<children' then
          ChildLoop(s,Q,Z,i,origLines,l)
        else if lowercase(copy(trim(s),1,12))='<limitations' then
          LimitationLoop(s,Q,Z,i,origLines,l)
        else if lowercase(copy(trim(s),1,9))='<elements' then
          ElementLoop(s,Q,Z,i,origLines,l)
        else if lowercase(copy(trim(s),1,3))='<if' then
        begin
          if tracerCondition(i,lowercase(copy(trim(s),5,length(trim(s))-5)))=false then
          begin //<if> condition is not fulfilled, so search for the corresponding </if>
            ifs:=1;
          end;
        end
        else if lowercase(copy(trim(s),1,4))='</if' then
          //do nothing, ignore this line
        else
        begin
          s:=StringReplace(s,'<trimName>',Tracers[i].name,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<name>',makeLonger(Tracers[i].name,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<description>',Tracers[i].description,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<comment>',Tracers[i].comment,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<vertSpeed>',AppendDot(Tracers[i].vertSpeed),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<vertDiff>',AppendDot(Tracers[i].vertDiff),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<molDiff>',Tracers[i].molDiff,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<vertSpeedValue>',AppendDot(FloatToStr(Tracers[i].myVertSpeedValue)),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<-vertSpeedValue>',AppendDot(FloatToStr(-Tracers[i].myVertSpeedValue)),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<vertSpeedValueMperS>',AppendDot(FloatToStr(Tracers[i].myVertSpeedValue/24.0/3600.0)),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<-vertSpeedValueMperS>',AppendDot(FloatToStr(-Tracers[i].myVertSpeedValue/24.0/3600.0)),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<opacity>',AppendDot(Tracers[i].opacity),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<childof>',Tracers[i].childOf,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<childOfNumMoving>',IntToStr(myChildOfNumMoving),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<solubility>',tracers[i].solubility,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<schmidtNumber>',tracers[i].schmidtNumber,[rfReplaceAll, rfIgnoreCase]);
          if tracers[i].gasName='' then
            s:=StringReplace(s,'<gasName>',tracers[i].name,[rfReplaceAll, rfIgnoreCase])
          else
            s:=StringReplace(s,'<gasName>',tracers[i].gasName,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<molarMass>',tracers[i].molarMass,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<initValue>',tracers[i].initValue,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplaceTagPlusMinus(s,'<numFlat',myNumFlat);
          s:=StringReplaceTagPlusMinus(s,'<num3D',myNum3D);
          s:=StringReplaceTagPlusMinus(s,'<numRiverDep',myNumRiverDep);
          s:=StringReplaceTagPlusMinus(s,'<numMoving',myNumMoving);
          s:=StringReplaceTagPlusMinus(s,'<numTracers',myNumTracer);
          s:=StringReplaceTagPlusMinus(s,'<numAtmosDep',myNumAtmosDep);
          s:=StringReplaceTagPlusMinus(s,'<numWat',myNum3D);
          s:=StringReplaceTagPlusMinus(s,'<numSur',myNumSur);
          s:=StringReplaceTagPlusMinus(s,'<numSed',myNumSed);
          s:=StringReplaceTagPlusMinus(s,'<numFis',myNumFis);
          s:=StringReplace(s,'<ceTotalIndex>',myCETotalIndex,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<ceAgedIndex>',myCEAgedIndex,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<ceTotalName>',myCETotalName,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<ceAgedName>',myCEAgedName,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<ceAmount>',myCEAmount,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<atmosDep>',IntToStr(Tracers[i].atmosDep),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<riverDep>',IntToStr(Tracers[i].riverDep),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<tracerAbove>',Tracers[i].tracerAbove,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<tracerBelow>',Tracers[i].tracerBelow,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<longname>',Tracers[i].longname,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<unit>',Tracers[i].outputUnit,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<standardname>',Tracers[i].stdname_prefix+Tracers[i].longname+Tracers[i].stdname_suffix,[rfReplaceAll, rfIgnoreCase]);
          for n:=0 to length(tracers[i].contents)-1 do
            s:=StringReplace(s,'<contentOf'+tracers[i].contents[n].element+'>',AppendDot(FloatToStr(tracers[i].contents[n].myAmount)),[rfReplaceAll, rfIgnoreCase]);
          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
        end;
        l:=l+1;
      end;
    end;
   end;
  end;
  LinesRead:=LinesRead+MyLinesRead;
  OrigLines.Free;
end;

//******************* Auxiliaries ************************************//

//! Tests whether an auxiliary fullfills the conditions
{!
  \private
  \param i integer index of the auxiliary
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - vertloc: WAT (whole water column), SED (sediment only), SUR (surface only), FIS (fish=like)
  - isflat: deprecated, see vertloc
  - a lot more ... (TODO)

  Calls
  - SplitConditions().
                                                                    }
function AuxiliaryCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if (lhs[j]='name=') and (lowercase(auxiliaries[i].name)<>rhs[j]) then isOk:=false;
    if (lhs[j]='name/=') and (lowercase(auxiliaries[i].name)=rhs[j]) then isOk:=false;
    if (lhs[j]='calcafterprocesses=') and (IntToStr(auxiliaries[i].calcAfterProcesses)<>rhs[j]) then isOk:=false;
    if (lhs[j]='calcafterprocesses/=') and (IntToStr(auxiliaries[i].calcAfterProcesses)=rhs[j]) then isOk:=false;
    if (lhs[j]='calcbeforezintegral=') and (IntToStr(auxiliaries[i].myCalcBeforeZIntegral)<>rhs[j]) then isOk:=false;
    if (lhs[j]='calcbeforezintegral/=') and (IntToStr(auxiliaries[i].myCalcBeforeZIntegral)=rhs[j]) then isOk:=false;
    if (lhs[j]='iterations=') and (IntToStr(auxiliaries[i].iterations)<>rhs[j]) then isOk:=false;
    if (lhs[j]='iterations/=') and (IntToStr(auxiliaries[i].iterations)=rhs[j]) then isOk:=false;
    if ((lhs[j]='isflat=') or (lhs[j]='vertloc=')) and (auxiliaries[i].vertLoc<>StrToIntVertLoc(rhs[j])) then isOk:=false;
    if ((lhs[j]='isflat/=') or (lhs[j]='vertloc/=')) and (auxiliaries[i].vertLoc=StrToIntVertLoc(rhs[j])) then isOk:=false;
    if (lhs[j]='isoutput=') and ( ((modelinfos.debugMode=0) and (auxiliaries[i].isOutput<>StrToInt(rhs[j])))
                                or((modelinfos.debugMode=1)  and (1<>StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='isoutput/=') and ( ((modelinfos.debugMode=0) and (auxiliaries[i].isOutput=StrToInt(rhs[j])))
                                or ((modelinfos.debugMode=1) and (1=StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='isusedelsewhere=') and (IntToStr(auxiliaries[i].isUsedElsewhere)<>rhs[j]) then isOk:=false;
    if (lhs[j]='isusedelsewhere/=') and (IntToStr(auxiliaries[i].isUsedElsewhere)=rhs[j]) then isOk:=false;
    if (lhs[j]='iszgradient=') and (IntToStr(auxiliaries[i].isZGradient)<>rhs[j]) then isOk:=false;
    if (lhs[j]='iszgradient/=') and (IntToStr(auxiliaries[i].isZGradient)=rhs[j]) then isOk:=false;
    if (lhs[j]='iszintegral=') and (IntToStr(auxiliaries[i].isZIntegral)<>rhs[j]) then isOk:=false;
    if (lhs[j]='iszintegral/=') and (IntToStr(auxiliaries[i].isZIntegral)=rhs[j]) then isOk:=false;
    if (lhs[j]='isselected=') and ((auxiliaries[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
    if (lhs[j]='isselected/=') and not ((auxiliaries[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all auxiliaries
{!
  \private
  \param s string: line with <auxiliaries ... > tag
  \param Q input text file
  \param Z output text file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<auxiliaries ...>' is in the template.

  Supports the content tags
  - <temp[k]> (with a integer k)
  - <formula>
  - <description>
  - <comment>
  - <iterations>
  - <iterinit>

  No sub-loop tags are in order.

  Calls
  - AuxiliaryCondition()
  - convertToOutputLanguage()
                                                                    }
procedure AuxiliaryLoop(s: String; var Q: TextFile; var Z: TextFile);
var conditions: String;
    i, j, k: Integer;
    s1: String;
    myLinesRead: Integer;
    myFormula: String;
    mytemp: Array[1..9] of String;
    deleteLine: Boolean;
    origLines: TStringList;
    l: Integer;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),14,length(trim(s))-14));
  myLinesRead:=0;
  s1:='';
  origLines:=TStringList.Create;
  while not (lowercase(copy(trim(s1),1,13))='</auxiliaries') and not EOF(Q) do
  begin
    readln(Q,s1);
    myLinesRead:=myLinesRead+1;
    origLines.Add(s1);
  end;
  origLines.Delete(OrigLines.Count-1);  
  for i:=0 to length(Auxiliaries)-1 do
  begin
    if AuxiliaryCondition(i,conditions) then
    begin
      myformula:=Auxiliaries[i].formula;
      convertToOutputLanguage(myformula,auxiliaries[i].name);
      for j:=1 to 9 do
      begin
        mytemp[j]:=Auxiliaries[i].temp[j];
        convertToOutputLanguage(mytemp[j],auxiliaries[i].name);
      end;
      for l:=0 to origLines.count-1 do
      begin
          s:=origLines[l];
          deleteLine:=false;
          s:=StringReplace(s,'<name>',makeLonger(Auxiliaries[i].name,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimName>',Auxiliaries[i].name,[rfReplaceAll, rfIgnoreCase]);
          //if <tempX> appears in line, but tempX='', then delete this line
          for k:=1 to 9 do
            if (myTemp[k]='') and (pos('<temp'+IntToStr(k)+'>',lowercase(s))>0) then
              deleteLine:=true
            else
              s:=StringReplace(s,'<temp'+IntToStr(k)+'>',makeLonger(myTemp[k],30),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<formula>',makeLonger(myFormula,30),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<description>',Auxiliaries[i].description,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<comment>',Auxiliaries[i].comment,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<iterations>',IntToStr(Auxiliaries[i].iterations),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<iterinit>',Auxiliaries[i].iterInit,[rfReplaceAll, rfIgnoreCase]);
          if deleteLine=false then
            if pos('<nonewline>',lowercase(s))>0 then
            begin
              s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
              write(Z,s);
            end
            else
              writeln(Z,s);
      end;
    end;
  end;
  LinesRead:=LinesRead+MyLinesRead;
  OrigLines.Free;  
end;

//******************* Processes ************************************//

//! Tests whether a process fullfills the conditions
{!
  \private
  \param i integer index of the process
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - vertloc: WAT (whole water column), SED (sediment only), SUR (surface only), FIS (fish=like)
  - isflat: deprecated, see vertloc
  - isinporewater:
  - name:
  - isOutput:
  - processtype:

  Calls
  - SplitConditions().
                                                                    }
function ProcessCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if (lhs[j]='name=') and (lowercase(processes[i].name)<>rhs[j]) then isOk:=false;
    if (lhs[j]='name/=') and (lowercase(processes[i].name)=rhs[j]) then isOk:=false;
    if ((lhs[j]='isflat=') or (lhs[j]='vertloc=')) and (processes[i].vertLoc<>StrToIntVertLoc(rhs[j])) then isOk:=false;
    if ((lhs[j]='isflat/=') or (lhs[j]='vertloc/=')) and (processes[i].vertLoc=StrToIntVertLoc(rhs[j])) then isOk:=false;
    if (lhs[j]='isoutput=') and ( ((modelinfos.debugMode=0) and (processes[i].isOutput<>StrToInt(rhs[j])))
                                or((modelinfos.debugMode=1)  and (1<>StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='isoutput/=') and ( ((modelinfos.debugMode=0) and (processes[i].isOutput=StrToInt(rhs[j])))
                                or ((modelinfos.debugMode=1) and (1=StrToInt(rhs[j]))) ) then isOk:=false;
    if (lhs[j]='isstiff=') and (processes[i].myIsStiff<>StrToInt(rhs[j])) then isOk:=false;
    if (lhs[j]='isstiff/=') and (processes[i].myIsStiff=StrToInt(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater=') and (processes[i].myIsInPorewater<>StrToInt(rhs[j])) then isOk:=false;
    if (lhs[j]='isinporewater/=') and (processes[i].myIsInPorewater=StrToInt(rhs[j])) then isOk:=false;
    if (lhs[j]='processtype=') and (lowercase(processes[i].processType)<>rhs[j]) then isOk:=false;
    if (lhs[j]='processtype/=') and (lowercase(processes[i].processType)=rhs[j]) then isOk:=false;
    if (lhs[j]='isselected=') and ((processes[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
    if (lhs[j]='isselected/=') and not ((processes[i].myIsSelected) xor (rhs[j]='1')) then isOk:=false;
  end;
  result:=isOk;
end;

//! Iterates all processes
{!
  \private
  \param s string: line with <processes ... > tag
  \param Q input text file
  \param Z output text file
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<processes ...>' is in the template.

  Supports the content tags
  - <name>',makeLonger(Processes[i].name,15),[rfReplaceAll, rfIgnoreCase]);
  - <trimName>',Processes[i].name,[rfReplaceAll, rfIgnoreCase]);
  - <description>',Processes[i].description,[rfReplaceAll, rfIgnoreCase]);
  - <turnover>',makeLonger(myRate,30),[rfReplaceAll, rfIgnoreCase]);
  - <comment>',Processes[i].comment,[rfReplaceAll, rfIgnoreCase]);
  - <processtype>',Processes[i].processType,[rfReplaceAll, rfIgnoreCase]);
  - <stifffactor>',myStiffFactor,[rfReplaceAll, rfIgnoreCase]);
  - <stifftracer>',myStiffTracer,[rfReplaceAll, rfIgnoreCase]);

  No sub-loop tags are in order.

  Calls
  - ProcessCondition()
  - convertToOutputLanguage()
                                                                    }
procedure ProcessLoop(s: String; var Q: TextFile; var Z: TextFile);
var conditions: String;
    i: Integer;
    s1: String;
    myLinesRead: Integer;
    myRate, myStiffFactor, myStiffTracer: String;
    origLines: TStringList;
    l: Integer;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),12,length(trim(s))-12));
  myLinesRead:=0;
  s1:='';
  origLines:=TStringList.Create;
  while not (lowercase(copy(trim(s1),1,11))='</processes') and not EOF(Q) do
  begin
    readln(Q,s1);
    myLinesRead:=myLinesRead+1;
    origLines.Add(s1);
  end;
  origLines.Delete(OrigLines.Count-1);
  for i:=0 to length(Processes)-1 do
  begin
   if processes[i].isActive=1 then
   begin
    if ProcessCondition(i,conditions) then
    begin
      myRate:=Processes[i].turnover;
      convertToOutputLanguage(myRate);
      myStiffFactor:=processes[i].myStiffFactor;
      convertToOutputLanguage(myStiffFactor);
      if processes[i].myStiffTracerNum>=0 then
        myStiffTracer:=tracers[processes[i].myStiffTracerNum].name
      else
        myStiffTracer:='';
      for l:=0 to origLines.count-1 do
      begin
          s:=origLines[l];
          s:=StringReplace(s,'<name>',makeLonger(Processes[i].name,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<trimName>',Processes[i].name,[rfReplaceAll, rfIgnoreCase]);          
          s:=StringReplace(s,'<description>',Processes[i].description,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<turnover>',makeLonger(myRate,30),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<comment>',Processes[i].comment,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<processtype>',Processes[i].processType,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<stifffactor>',myStiffFactor,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<stifftracer>',myStiffTracer,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<equation>',GetInputOutputEquation(Processes[i]),[rfReplaceAll, rfIgnoreCase]);
          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
      end;
    end;
   end;
  end;
  LinesRead:=LinesRead+MyLinesRead;
  OrigLines.Free;
end;

//******************* cElements ************************************//

//! Tests whether a colored element fullfills the conditions
{!
  \private
  \param i integer index of the colored element (cElement)
  \param c String containing the conditions
  \return true/false depending on whether the conditions are fullfilled or not
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Possible conditions tested:
  - istracer:
  - isaging:

  Calls
  - SplitConditions().
                                                                    }
function cElementCondition(i: Integer; c: String):Boolean;
var isOk: Boolean;
    lhs,rhs: TStringList;
    j: Integer;
begin
  lhs:=TStringList.Create; rhs:=TStringList.Create;
  isOk:=true;
  splitConditions(c,lhs,rhs);
  for j:=0 to lhs.Count-1 do
  begin
    if (lhs[j]='istracer=') and (IntToStr(cElements[i].myIsTracer)<>rhs[j]) then isOk:=false;
    if (lhs[j]='istracer/=') and (IntToStr(cElements[i].myIsTracer)=rhs[j]) then isOk:=false;
    if (lhs[j]='isaging=') and (IntToStr(cElements[i].myIsAging)<>rhs[j]) then isOk:=false;
    if (lhs[j]='isaging/=') and (IntToStr(cElements[i].myIsAging)=rhs[j]) then isOk:=false;
    if (lhs[j]='element=') and (lowercase(cElements[i].element)<>lowercase(rhs[j])) then isOk:=false;
    if (lhs[j]='element/=') and (lowercase(cElements[i].element)=lowercase(rhs[j])) then isOk:=false;
    if (lhs[j]='color=') and (cElements[i].color<>rhs[j]) then isOk:=false;
    if (lhs[j]='color/=') and (cElements[i].color=rhs[j]) then isOk:=false;
    if (lhs[j]='atmosdep=') and (IntToStr(cElements[i].atmosDep)<>rhs[j]) then isOk:=false;
    if (lhs[j]='atmosdep/=') and (IntToStr(cElements[i].atmosDep)=rhs[j]) then isOk:=false;
    if (lhs[j]='riverdep=') and (IntToStr(cElements[i].riverDep)<>rhs[j]) then isOk:=false;
    if (lhs[j]='riverdep/=') and (IntToStr(cElements[i].riverDep)=rhs[j]) then isOk:=false;
  end;
  result:=isOk;
end;

//! Creates rates / timeTendencies for the current tracer
{!
  \private
  \param s string: line with <celements ... > tag
  \param Q input text file
  \param Z output text file
  \param active Boolean stating whether the loop is performed (true) or not (false)
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Is called when the tag '<celements ...>' is in the template.

  Supports the content tags
  - <total> "total_COLOR_ELEMENT"
  - <totalTop> "total_COLOR_ELEMENT_at_top"
  - <totalBottom> "total_COLOR_ELEMENT_at_bottom"
  - <totalBottomNumFlat>
  - <aged> "aged_COLOR_ELEMENT"
  - <agedTop> "aged_COLOR_ELEMENT_at_top"
  - <agedBottom> "aged_COLOR_ELEMENT_at_bottom"
  - <agedBottomNumFlat>
  - <totalIndex> "idx_total_COLOR_ELEMENT"
  - <totalIndexTop> "idx_flat_total_COLOR_ELEMENT_at_top"
  - <totalIndexBottom> "idx_flat_total_COLOR_ELEMENT_at_bottom"
  - <agedIndex> "idx_aged_COLOR_ELEMENT"
  - <agedIndexTop> "idx_flat_aged_COLOR_ELEMENT_at_top"
  - <agedIndexBottom> "idx_flat_aged_COLOR_ELEMENT_at_top"
  - <totalIndexNum> index number
  - <totalIndexTopNum> index number
  - <totalIndexBottomNum> index number
  - <agedIndexNum> index number
  - <agedIndexTopNum> index number
  - <agedIndexBottomNum> index number

  The following sub-loop tags are in order:
  - <containingtracers>

  Calls
  - cElementCondition()
  - makeLonger()
                                                                    }
procedure cElementLoop(s: String; var Q: TextFile; var Z: TextFile; active: Boolean=true);
var conditions: String;
    i, j: Integer;
    s1: String;
    myLinesRead: Integer;
    totalIndexNum, totalIndexTopNum, totalIndexBottomNum: Integer;
    agedIndexNum, agedIndexTopNum, agedIndexBottomNum: Integer;
    Current3dTracerNum, CurrentFlatTracerNum: Integer;
    origLines: TStringList;
    l: Integer;
begin
  DecimalSeparator:='.';
  conditions:=lowercase(copy(trim(s),12,length(trim(s))-12));
  myLinesRead:=0;
  s1:='';
  origLines:=TStringList.Create;
  while not (lowercase(copy(trim(s1),1,11))='</celements') and not EOF(Q) do
  begin
    readln(Q,s1);
    myLinesRead:=myLinesRead+1;
    origLines.Add(s1);
  end;
  origLines.Delete(OrigLines.Count-1);
  for i:=0 to length(cElements)-1 do
  begin
    if cElementCondition(i,conditions) and active then
    begin
      totalIndexNum:=0; totalIndexTopNum:=0; totalIndexBottomNum:=0;
      agedIndexNum:=0; agedIndexTopNum:=0; agedIndexBottomNum:=0;
      Current3dTracerNum:=0; CurrentFlatTracerNum:=0;
      for j:=0 to length(tracers)-1 do
      begin
        if tracers[j].isActive>0 then
          if tracers[j].vertLoc=0 then Current3dTracerNum:=Current3dTracerNum+1
                                 else CurrentFlatTracerNum:=CurrentFlatTracerNum+1;
        if tracers[j].name='total_'+cElements[i].color+'_'+cElements[i].element then
          totalIndexNum:=Current3dTracerNum;
        if tracers[j].name='total_'+cElements[i].color+'_'+cElements[i].element+'_at_top' then
          totalIndexTopNum:=CurrentFlatTracerNum;
        if tracers[j].name='total_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom' then
          totalIndexBottomNum:=CurrentFlatTracerNum;
        if tracers[j].name='aged_'+cElements[i].color+'_'+cElements[i].element then
          agedIndexNum:=Current3dTracerNum;
        if tracers[j].name='aged_'+cElements[i].color+'_'+cElements[i].element+'_at_top' then
          agedIndexTopNum:=CurrentFlatTracerNum;
        if tracers[j].name='aged_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom' then
          agedIndexBottomNum:=CurrentFlatTracerNum;
      end;
      l:=0;
      while l<=origLines.count-1 do
      begin
        s:=origLines[l];
        if lowercase(copy(trim(s),1,18))='<containingtracers' then
          ctLoop(s,Q,Z,i,origLines,l)
        else
        begin
          s:=StringReplace(s,'<total>',makeLonger('total_'+cElements[i].color+'_'+cElements[i].element,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalTop>',makeLonger('total_'+cElements[i].color+'_'+cElements[i].element+'_at_top',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalBottom>',makeLonger('total_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalBottomNumFlat>',IntToStr(totalIndexBottomNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<aged>',makeLonger('aged_'+cElements[i].color+'_'+cElements[i].element,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedTop>',makeLonger('aged_'+cElements[i].color+'_'+cElements[i].element+'_at_top',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedBottom>',makeLonger('aged_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedBottomNumFlat>',IntToStr(agedIndexBottomNum),[rfReplaceAll, rfIgnoreCase]);

          s:=StringReplace(s,'<totalIndex>',makeLonger('idx_total_'+cElements[i].color+'_'+cElements[i].element,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalIndexTop>',makeLonger('idx_flat_total_'+cElements[i].color+'_'+cElements[i].element+'_at_top',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalIndexBottom>',makeLonger('idx_flat_total_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndex>',makeLonger('idx_aged_'+cElements[i].color+'_'+cElements[i].element,15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndexTop>',makeLonger('idx_flat_aged_'+cElements[i].color+'_'+cElements[i].element+'_at_top',15),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndexBottom>',makeLonger('idx_flat_aged_'+cElements[i].color+'_'+cElements[i].element+'_at_bottom',15),[rfReplaceAll, rfIgnoreCase]);

          s:=StringReplace(s,'<totalIndexNum>',IntToStr(totalIndexNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalIndexTopNum>',IntToStr(totalIndexTopNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<totalIndexBottomNum>',IntToStr(totalIndexBottomNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndexNum>',IntToStr(agedIndexNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndexTopNum>',IntToStr(agedIndexTopNum),[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<agedIndexBottomNum>',IntToStr(agedIndexBottomNum),[rfReplaceAll, rfIgnoreCase]);

          s:=StringReplace(s,'<element>',cElements[i].element,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<color>',cElements[i].color,[rfReplaceAll, rfIgnoreCase]);
          s:=StringReplace(s,'<description>',cElements[i].description,[rfReplaceAll, rfIgnoreCase]);

          if pos('<nonewline>',lowercase(s))>0 then
          begin
            s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
            write(Z,s);
          end
          else
            writeln(Z,s);
        end;
        l:=l+1;
      end;
    end;
  end;
  LinesRead:=LinesRead+MyLinesRead;
  origLines.Free;
end;

//*********************** Main routine *********************************//

//! Main routine: Processes a file and replaces tags
{!
  \public
  \param inputfile a string representing the name of the input file
  \param outputfile a string representing the name of the output file
  \param coloredElements boolean stating whether 'split colored elements' was ticked in the cgt GUI [true]
  \author Hagen Radtke, hagen.radtke@io-warnemuende.de

  Called from within \ref TForm1.BitBtn1Click()

  Supports the content tags:
  - <name>
  - <trimName>
  - <version>
  - <description>
  - <author>
  - <contact>
  - <codegen_version>
  - <now>
  - <numFlatTracers>
  - <numFlatTracers+X>
  - <numFlatTracers-X>
  - <num3DTracers>
  - <num3DTracers+X>
  - <num3DTracers-X>
  - <numRiverDepTracers>
  - <numRiverDepTracers+X>
  - <numRiverDepTracers-X>
  - <numMovingTracers>
  - <numMovingTracers+X>
  - <numMovingTracers-X>
  - <maxIterations>
  - </ifParam>
  - </ifNoParam>

  The following sub-loops are in order:
  - <constants ...>
  - <tracers ...>
  - <backwardtracers ...>
  - <auxiliaries>
  - <processes>
  - <celements>

  Calls
  - optional: ConstantLoop()
  - optional: TracerLoop()
  - optional: TracerLoop()
  - optional: AuxiliaryLoop()
  - optional: ProcessLoop()
  - optional: cElementLoop()
  - optional: StringReplaceTagPlusMinus()

  Uses global variables:
  - \ref codegenVersion

  If a XML file is processed, the tags are enclosed by [] instead of <>.
  }
procedure generateCode(inputfile, outputfile: String; coloredElements:Boolean=true);
var Q, Z: TextFile;
    s: String;
    nowstring: AnsiString;
    myNumFlatTracers, myNum3DTracers:                  Integer;
    myNumSedTracers, myNumSurTracers, myNumFisTracers: Integer;
    myNumRiverDepTracers, myNumAtmosDepTracers:        Integer;
    myNumMovingTracers: Integer;
    i: Integer;
    myinputfile, myoutputfile: String;
begin
  //first, do some initialization to e.g. know how many tracers are active
  DateTimeToString(nowstring,'yyyy-mmm-dd hh":"mm',now);
  myNumFlatTracers:=0;
  myNumSedTracers:=0;
  myNumSurTracers:=0;
  myNumFisTracers:=0;
  myNum3DTracers:=0;
  myNumRiverDepTracers:=0;
  myNumAtmosDepTracers:=0;
  myNumMovingTracers:=0;
  for i:=0 to length(tracers)-1 do
    if tracers[i].isActive=1 then
    begin
      if tracers[i].vertLoc<>0 then myNumFlatTracers:=myNumFlatTracers+1
                              else myNum3DTracers:=myNum3DTracers+1;
      if tracers[i].vertLoc=1 then myNumSedTracers:=myNumSedTracers+1;
      if tracers[i].vertLoc=2 then myNumSurTracers:=myNumSurTracers+1;
      if tracers[i].vertLoc=3 then myNumFisTracers:=myNumFisTracers+1;
      if tracers[i].riverDep=1 then myNumRiverDepTracers:=myNumRiverDepTracers+1;
      if tracers[i].atmosDep=1 then myNumAtmosDepTracers:=myNumAtmosDepTracers+1;
    end;
  for i:=0 to length(tracers)-1 do
    if tracers[i].isActive=1 then
    begin
      if tracers[i].vertSpeed<>'0' then myNumMovingTracers:=myNumMovingTracers+1;
    end;

  //Now we need to know which input file to read.
  //Typically, we use inputfile of course.
  //However, if the input file is .xml file, we need to switch between <> and [] brackets,
  //as we use [tracers] etc. in .xml files to avoid confusion of CGT and XML tags.
  //therefore, we generate an "outputfile_temp1" file which is the inputfile with <> and [] switched.
  //we assume that no bell sign (chr(7)) is used.
  if outputLanguage='xml' then
  begin
    AssignFile(Q,inputfile);
    reset(Q);
    myinputfile:=outputfile+'_temp1';
    myoutputfile:=outputfile+'_temp2';
    AssignFile(Z,myinputfile);
    Rewrite(Z);
    while not EOF(Q) do
    begin
      readln(Q,s);
      s:=StringReplace(s,'<',chr(7),[rfReplaceAll]); s:=StringReplace(s,'[','<',[rfReplaceAll]); s:=StringReplace(s,chr(7),'[',[rfReplaceAll]);
      s:=StringReplace(s,'>',chr(7),[rfReplaceAll]); s:=StringReplace(s,']','>',[rfReplaceAll]); s:=StringReplace(s,chr(7),']',[rfReplaceAll]);
      writeln(Z,s);
    end;
    closefile(Q);
    closefile(Z);
  end
  else
  begin
    myinputfile:=inputfile;
    myoutputfile:=outputfile;
  end;

  //Now do the replacing of the CGT tags
  AssignFile(Q,myinputfile);
  SetTextBuf(Q,InputBuffer);   //this accelerates reading
  reset(Q);
  LinesRead:=0;
  AssignFile(Z,myoutputfile);
  rewrite(Z);

  while not EOF(Q) do
  begin
    readln(Q,s);
    LinesRead:=LinesRead+1;
    if lowercase(copy(trim(s),1,10))='<constants' then
      ConstantLoop(s,Q,Z)
    else if lowercase(copy(trim(s),1,8))='<tracers' then
      TracerLoop(s,Q,Z,false)
    else if lowercase(copy(trim(s),1,16))='<backwardtracers' then
      TracerLoop(s,Q,Z,true)
    else if lowercase(copy(trim(s),1,12))='<auxiliaries' then
      AuxiliaryLoop(s,Q,Z)
    else if lowercase(copy(trim(s),1,10))='<processes' then
      ProcessLoop(s,Q,Z)
    else if lowercase(copy(trim(s),1,10))='<celements' then
      cElementLoop(s,Q,Z,coloredElements)
    else
    begin
      s:=StringReplace(s,'<name>',ModelInfos.name,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<trimName>',ModelInfos.name,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<version>',ModelInfos.version,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<description>',ModelInfos.description,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<author>',ModelInfos.author,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<contact>',ModelInfos.contact,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<codegen_version>',codegenVersion,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'<now>',nowstring,[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplaceTagPlusMinus(s,'<numFlatTracers',myNumFlatTracers);
      s:=StringReplaceTagPlusMinus(s,'<num3DTracers',myNum3DTracers);
      s:=StringReplaceTagPlusMinus(s,'<numWatTracers',myNum3DTracers);
      s:=StringReplaceTagPlusMinus(s,'<numSurTracers',myNumSurTracers);
      s:=StringReplaceTagPlusMinus(s,'<numSedTracers',myNumSedTracers);
      s:=StringReplaceTagPlusMinus(s,'<numFisTracers',myNumFisTracers);
      s:=StringReplaceTagPlusMinus(s,'<numRiverDepTracers',myNumRiverDepTracers);
      s:=StringReplaceTagPlusMinus(s,'<numAtmosDepTracers',myNumAtmosDepTracers);
      s:=StringReplaceTagPlusMinus(s,'<numMovingTracers',myNumMovingTracers);
      s:=StringReplace(s,'<maxIterations>',IntToStr(MaxIterations),[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'</ifParam>','',[rfReplaceAll, rfIgnoreCase]);
      s:=StringReplace(s,'</ifNoParam>','',[rfReplaceAll, rfIgnoreCase]);
      if pos('<nonewline>',lowercase(s))>0 then
      begin
        s:=StringReplace(s,'<noNewLine>','',[rfReplaceAll, rfIgnoreCase]);
        write(Z,s);
      end
      else
        writeln(Z,s);
    end;
  end;

  closefile(Q);
  closefile(Z);

  //now, if output was .xml file, we need to change <> and [] back and delete the temporary files.
  if outputLanguage='xml' then
  begin
    AssignFile(Q,myoutputfile);
    reset(Q);
    AssignFile(Z,outputfile);
    Rewrite(Z);
    while not EOF(Q) do
    begin
      readln(Q,s);
      s:=StringReplace(s,'<',chr(7),[rfReplaceAll]); s:=StringReplace(s,'[','<',[rfReplaceAll]); s:=StringReplace(s,chr(7),'[',[rfReplaceAll]);
      s:=StringReplace(s,'>',chr(7),[rfReplaceAll]); s:=StringReplace(s,']','>',[rfReplaceAll]); s:=StringReplace(s,chr(7),']',[rfReplaceAll]);
      writeln(Z,s);
    end;
    closefile(Q);
    closefile(Z);
    DeleteFile(myinputfile);
    DeleteFile(myoutputfile);
  end;
end;

end.
