{
  The contents of this file are subject to the Mozilla Public License
  Version 1.1 (the "License"); you may not use this file except in compliance
  with the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/

  Software distributed under the License is distributed on an "AS IS" basis,
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  the specific language governing rights and limitations under the License.

  The Original Code is: SynUniHighlighter.pas, released 2003-01
  All Rights Reserved.

  Alternatively, the contents of this file may be used under the terms of the
  GNU General Public License Version 2 or later (the "GPL"), in which case
  the provisions of the GPL are applicable instead of those above.
  If you wish to allow use of your version of this file only under the terms
  of the GPL and not to allow others to use your version of this file
  under the MPL, indicate your decision by deleting the provisions above and
  replace them with the notice and other provisions required by the GPL.
  If you do not delete the provisions above, a recipient may use your version
  of this file under either the MPL or the GPL.

}{
  @abstract(Provides a universal highlighter for SynEdit)
  @authors(Fantasist [walking_in_the_sky@yahoo.com], Vit [nevzorov@yahoo.com],
           Vitalik [vetal-x@mail.ru])
  @created(2003)
  @lastmod(2004-05-12)
}

unit SynUniRules;

interface

uses
  SysUtils, Graphics,
  Classes, SynEditHighlighter, SynUniClasses, Laz2_DOM;

type
  TSynRange = class;
  TSynSet = class;

  TAbstractSymbol = class
    function GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean; virtual; abstract;
  end;

  TSymbols = class(TAbstractSymbol)
    HeadNode: TSymbolNode;
    SynSets: TList;
    function GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean; override;
    function FindSymbol(st: string): TSymbolNode;
    procedure AddSymbol(st: string; tkSynSymbol: TSynSymbol; ABrakeType: TSymbBrakeType);
    procedure AddSet(SymbolSet: TSynSet);
    constructor Create(ch: char; tkSynSymbol: TSynSymbol; ABrakeType: TSymbBrakeType); reintroduce; overload; virtual;
    constructor Create(SymbolSet: TSynSet); reintroduce; overload; virtual;
    destructor Destroy(); override;
  end;

  TDefaultSymbols = class(TAbstractSymbol)
     tkSynSymbol: TSynSymbol;
     function GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean; override;
     constructor Create(SynSymb: TSynSymbol); reintroduce; virtual;
     destructor Destroy(); override;
  end;

  TDefaultTermSymbols = class(TAbstractSymbol)
     tkSynSymbol: TSynSymbol;
     function GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean; override;
     constructor Create(SynSymb: TSynSymbol); virtual;
     destructor Destroy(); override;
  end;

  TSynKeyList = class (TSynRule)
    KeyList: TStringList;
    constructor Create(st: string = '');
    destructor Destroy(); override;
    procedure LoadHglFromXml(xml: TDOMNode; SchCount,SchIndex: integer);
    procedure LoadFromXml(xml: TDOMNode); override;
    procedure SaveToStream(StreamWriter: TStreamWriter; Ind: integer = 0); overload; override;
  end;

  TSynKeyListLink = class(TAbstractRule)
    KeyList: TSynKeyList;
  end;

  TSynSet = class (TSynRule)
    SymbSet: TSymbSet;
    StartType: TSymbStartType;
    BrakeType: TSymbBrakeType;
    constructor Create(aSymbSet: TSymbSet = []);
    destructor Destroy(); override;
    procedure LoadHglFromXml(xml: TDOMNode; SchCount,SchIndex: integer);
    procedure LoadFromXml(xml: TDOMNode); override;
    procedure SaveToStream(StreamWriter: TStreamWriter; Ind: integer = 0); overload; override;
  end;

  TSynSetLink = class(TAbstractRule)
    SynSet: TSynSet;
  end;

  TSynRangeLink = class(TAbstractRule)
    Range: TSynRange;
    Parent: TSynRange;
    constructor Create(aRange: TSynRange); virtual;
  end;

  TSynRangeRule = class
    fCloseSymbol: TSynSymbol;
    fOpenSymbol: TSynSymbol;
    fCloseOnTerm: boolean;
    fCloseOnEol: boolean;
    fAllowPredClose: boolean;
    constructor Create(OpenSymbs: string = ''; CloseSymbs: string = '');
    destructor Destroy(); override;
  end;

  TSynRange = class (TSynRule)
  private
    fCaseSensitive: boolean;
    fOwner: TSynRange;

    fSynSymbols: TList;
    fSynRanges: TList;
    fSynKeyLists: TList;
    fSynSets: TList;

    StringCaseFunct: function (const st: string): string;
    fPrepared: boolean;

  public {temp}
    OpenCount: integer;
    ParentBackup: TSynRange;

    fRule: TSynRangeRule;
    fClosingSymbol: TSynSymbol;

    fDefaultSynSymbol: TSynSymbol;
    fDefaultSymbols: TDefaultSymbols;
    fDefaultTermSymbol: TDefaultTermSymbols;

    fCommonSynRanges: TList;
    fSynRangeLinks: TList;

    CaseFunct: function (ch: char): char;
    fTermSymbols: TSymbSet;
    HasNodeAnyStart: array[char] of boolean;
    SymbolList: array[char] of TAbstractSymbol;
  private
    function GetSynSymbol(Index: Integer): TSynSymbol;
    function GetCommonSynRange(Index: Integer): TSynRange;
    function GetSynRangeLink(Index: Integer): TSynRangeLink;
    function GetSynRange(Index: Integer): TSynRange;
    function GetSynKeyList(Index: Integer): TSynKeyList;
    function GetSynSet(Index: Integer): TSynSet;

    function GetSynSymbolCount(): Integer;
    function GetCommonSynRangeCount(): Integer;
    function GetSynRangeLinkCount(): Integer;
    function GetSynRangeCount(): Integer;
    function GetSynKeyListCount(): Integer;
    function GetSynSetCount(): Integer;

    function GetCaseSensitive: boolean;
    procedure SetCaseSensitive(const Value: boolean);

  public {temp}
    procedure LoadHglFromXml(xml: TDOMNode; SchCount, SchIndex: integer);
    procedure LoadFromXml(xml: TDOMNode); override;
    procedure SaveToStream(StreamWriter: TStreamWriter; Ind: integer = 0); overload; override;
  public
    constructor Create(OpenSymbs: string = ''; CloseSymbs: string = ''); virtual;
    destructor Destroy(); override;

    procedure AddSynSymbol(NewSymb: TSynSymbol);
    procedure AddRule(NewRule: TSynRule);
    procedure AddCommonRange(Range: TSynRange);
    procedure AddRangeLink(NewRangeLink: TSynRangeLink); overload;
    function AddRangeLink(aRange: TSynRange; aName: string; aColor: TColor): TSynRangeLink; overload;
    procedure AddRange(NewRange: TSynRange); overload;
    function AddRange(aOpen, aClose, aName: string; aColor: TColor): TSynRange; overload;
    procedure AddKeyList(NewKeyList: TSynKeyList); overload;
    function AddKeyList(aName: string; aColor: TColor): TSynKeyList; overload;
    procedure AddSet(NewSet: TSynSet); overload;
    function AddSet(aName: string; aSymbSet: TSymbSet; aColor: TColor): TSynSet; overload;//Vitalik 2004

    function FindSymbol(st: string): TSynSymbol;
    function FindSymbolOwner(Symbol: TSynSymbol): TSynKeyList;

    procedure DeleteCommonRange(index: integer); overload;
    procedure DeleteCommonRange(Range: TSynRange); overload;
    procedure DeleteRangeLink(index: integer); overload;
    procedure DeleteRangeLink(RangeLink: TSynRangeLink); overload;
    procedure DeleteRange(index: integer); overload;
    procedure DeleteRange(Range: TSynRange); overload;
    procedure DeleteKeyList(index: integer); overload;
    procedure DeleteKeyList(KeyList: TSynKeyList); overload;
    procedure DeleteSet(index: integer); overload;
    procedure DeleteSet(SynSet: TSynSet); overload;

{    procedure SetParentColor;
    procedure RestoreOldColor; }
    procedure SetDelimiters(Delimiters: TSymbSet);
//    procedure SetStyles(aStyles: TSynUniStyles);    
    procedure SetColorForChilds();

    procedure ClearParsingFields();
    procedure ResetParents(aParent: TSynRange);    
    procedure Prepare(Owner: TSynRange);
    procedure Reset();
    procedure Clear();

    function  FindRange(const Name: string): TSynRange;
    procedure LoadHglFromStream(aSrc: TStream);
  public
    property TermSymbols: TSymbSet read fTermSymbols write fTermSymbols;
//    property OpenSymbol: TSynSymbol read fOpenSymbol;
//    property CloseSymbol: TSynSymbol read fCloseSymbol;
//    property CloseOnTerm: boolean read fCloseOnTerm write fCloseOnTerm;
//    property CloseOnEol: boolean read fCloseOnEol write fCloseOnEol;
//    property AllowPredClose: boolean read fAllowPredClose write fAllowPredClose;

    property CommonRanges[index: integer]: TSynRange read GetCommonSynRange;
    property CommonRangeCount: integer read GetCommonSynRangeCount;
    property RangeLinks[index: integer]: TSynRangeLink read GetSynRangeLink;
    property RangeLinkCount: integer read GetSynRangeLinkCount;
    property Ranges[index: integer]: TSynRange read GetSynRange;
    property RangeCount: integer read GetSynRangeCount;
    property Symbols[index: integer]: TSynSymbol read GetSynSymbol;
    property SymbolCount: integer read GetSynSymbolCount;
    property KeyLists[index: integer]: TSynKeyList read GetSynKeyList;
    property KeyListCount: Integer read GetSynKeyListCount;
    property Sets[index: integer]: TSynSet read GetSynSet;
    property SetCount: Integer read GetSynSetCount;

    property CaseSensitive: boolean read GetCaseSensitive write SetCaseSensitive;
    property Prepared: boolean read fPrepared;
    property Parent: TSynRange read fOwner write fOwner;
  end;

//function Verify(tag: string; xml: TXMLParser): boolean; overload;

const
  DefaultTermSymbols: TSymbSet = ['*','/','+','-','=','\','|','&','(',')',
    '[',']','{','}','`','~','!','@',',','$','%','^','?',':',';','''','"','.',
    '>','<','#'];

implementation

uses
  Laz2_XMLRead;

function CaseNone(ch: char): char; //: Need for CaseSensitive
begin
  Result := ch;
end;

function StringCaseNone(const st: string): string; //: Need for CaseSensitive
begin
  Result := st;
end;

//==== TSymbols ==============================================================
procedure TSymbols.AddSymbol(st: string; tkSynSymbol: TSynSymbol; ABrakeType: TSymbBrakeType);
//: Add SynSymbol to the tree Symbols
var
  i: integer;
  l: integer;
  Node: TSymbolNode;
  SList: TSymbolList;
begin
  SList := HeadNode.NextSymbs;      //: All branches of current node (first - root node)
  Node := nil;                      //: Current Node
  l := Length(st);                   //: Length of adding string
  for i := 1 to l do                //: Check all symbols of adding string
  begin
    Node := SList.FindSymbol(st[i]);     //: Try to find current symbol of adding string among branches
    if Node = nil then                  //: If we can't find current symbol
    begin
      Node := TSymbolNode.Create(st[i]);     //: then create node with current symbol
      SList.AddSymbol(Node);                //: and add it to current branches
    end;
    SList := Node.NextSymbs;            //: Go to finded or added node
  end;
  Node.StartType := tkSynSymbol.StartType;
  Node.BrakeType := ABrakeType;     //: Set Break Type and ...
  Node.tkSynSymbol := tkSynSymbol;  //: ... SynSymbol of last Node
end;

constructor TSymbols.Create(ch: char; tkSynSymbol: TSynSymbol;
  ABrakeType: TSymbBrakeType);
begin
  HeadNode := TSymbolNode.Create(ch, tkSynSymbol, ABrakeType);
  SynSets := TList.Create;
end;

constructor TSymbols.Create(SymbolSet: TSynSet);
begin
  SynSets := TList.Create;
  AddSet(SymbolSet);
end;

destructor TSymbols.Destroy;
begin
  if Assigned(HeadNode) then
    HeadNode.Free;
  FreeList(SynSets);
  inherited;
end;

function TSymbols.FindSymbol(st: string): TSymbolNode;
//: Find string st in the tree Symbols
var
  i: integer;
  l: integer;
  Node, prvNode: TSymbolNode;
begin
  Node := HeadNode;   //: Root of the tree
  l := Length(st);     //: Length of string
  for i := 1 to l do
  begin
    prvNode := Node.NextSymbs.FindSymbol(st[i]);
    if prvNode = nil then     //: If don't find
      break;                  //: Exit from cycle
    Node := prvNode;          //: Else go to the brench or nil, if don't find
  end;
  Result := Node;     //: Return node, if found, and nil, if not found
end;

procedure TSymbols.AddSet(SymbolSet: TSynSet);// ABrakeType: TSymbBrakeType);
begin
  SynSets.Add(SymbolSet);
end;

function TSymbols.GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean;
//: Try to find any token
var
  curNode, nxtStart, prevFind: TSymbolNode;
  i, posStart, posNext, posPrev: integer;
  AllowedTermSymbols: TSymbSet;

  function CanBeToken(): boolean;
  var i: integer;
  begin
    CanBeToken := True;
    if curNode.tkSynSymbol = nil then
      CanBeToken := False
    else if (curNode.BrakeType = btTerm) and not (fLine[succ(Run)] in CurRule.fTermSymbols) then
      CanBeToken := False
    else
      case curNode.tkSynSymbol.StartLine of
        slFirstNonSpace:
          for i := 0 to posStart-1 do
            {$IFNDEF FPC}
            if not (fLine[i] in [' ', #32, #9]) then begin
            {$ELSE}
            if not (fLine[i] in [#32, #9]) then begin
            {$ENDIF}
              CanBeToken := False;
              break;
            end;
        slFirst:
          if posStart <> 0 then
            CanBeToken := False;
      end;
  end;

begin
  Result := False;
  posStart := Run;
  if Assigned(HeadNode) then begin
    curNode := HeadNode;
    posNext := posStart;
    nxtStart := nil;
    repeat
      if nxtStart <> nil then begin
        curNode := nxtStart;
        Run := posNext;
        nxtStart := nil;
      end;
      if CanBeToken then
        prevFind := curNode
      else
        prevFind := nil;
      posPrev := Run;
      while (curNode.NextSymbs.Count > 0) and (fLine[Run] <> #0) do begin
        inc(Run);
        curNode := curNode.NextSymbs.FindSymbol(CurRule.CaseFunct(fLine[Run]));
        if curNode = nil then begin
          dec(Run);
          break;
        end;

        if CanBeToken then begin
          prevFind := curNode;
          posPrev := Run;
        end;

        if nxtStart = nil then
         if (CurRule.HasNodeAnyStart[CurRule.CaseFunct(curNode.ch)] or
            (curNode.ch in CurRule.fTermSymbols) or
            (CurRule.CaseFunct(fLine[Run]) in CurRule.fTermSymbols)) then begin
           nxtStart := curNode;
           posNext := Run;
         end;
      end;

      Run := posPrev;

      if prevFind = nil then
        continue;
      if prevFind.tkSynSymbol = nil then
        continue; //Never happened???

      if fLine[Run] <> #0 then                                                     //: Go to next symbol in line if it isn't end of line
        inc(Run);

      if prevFind.BrakeType = btAny then begin                                      //: If token can end by any symbol
        Result := True;                          //: We find it!
        tkSynSymbol := prevFind.tkSynSymbol;      //: Here it is!
        Exit;
      end;

      if fLine[Run] in CurRule.fTermSymbols then begin                        //: If token can end by delimeter and current symbol is delimeter
        Result := True;                      //: We find it!
        tkSynSymbol := prevFind.tkSynSymbol;  //: Here it is!
        Exit;
      end;
    until nxtStart = nil;
  end;

  Run := posStart;
  AllowedTermSymbols := CurRule.fTermSymbols;
  for i := 0 to SynSets.Count-1 do begin
    AllowedTermSymbols := AllowedTermSymbols - TSynSet(SynSets[i]).SymbSet;
  end;

  for i := 0 to SynSets.Count-1 do begin
    Run := posStart;
    repeat
      inc(Run);
    until not (fLine[Run] in TSynSet(SynSets[i]).SymbSet) or (fLine[Run] = #0);
    //: If number ends on some Term-symbol, then
    if TSynSet(SynSets[i]).BrakeType = btAny then begin
      Result := True;                   //: We find it!
      tkSynSymbol := TSynSymbol.Create('', TSynSet(SynSets[i]).Attribs);
      exit;
    end;
    if (fLine[Run] in AllowedTermSymbols) then begin
      Result := True;                   //: We find it!
      tkSynSymbol := TSynSymbol.Create('', TSynSet(SynSets[i]).Attribs);
      exit;
    end;
  end;
  Run := succ(posStart);
end;

//==== TDefaultSymbols =======================================================
constructor TDefaultSymbols.Create(SynSymb: TSynSymbol);
begin
  tkSynSymbol := SynSymb;
end;

destructor TDefaultSymbols.Destroy;
begin
  tkSynSymbol.Free;
  inherited;
end;

function TDefaultSymbols.GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean;
//: Read just symbol, nothing to return
begin
  inc(Run);
  Result := False;
end;

//==== TDefaultTermSymbols ===================================================
constructor TDefaultTermSymbols.Create(SynSymb: TSynSymbol);
begin
  tkSynSymbol := SynSymb;
end;

destructor TDefaultTermSymbols.Destroy;
begin
  tkSynSymbol.Free;
  inherited;
end;

function TDefaultTermSymbols.GetToken(CurRule: TSynRange; fLine: PChar; var Run: integer; var tkSynSymbol: TSynSymbol): boolean;
begin
  if fLine[Run] <> #0 then  //: If is not end of line then
     Inc(Run);                       //: go to next symbol in fLine
  tkSynSymbol := self.tkSynSymbol;        //: And return DefaultTermSymbol
  Result := True;                         //: We found token
end;

//////////////////////////////////////////////////////////////////////////////
          //  RRRRRRR   UUU   UUU  LLL      EEEEEEE   SSSSSS   //
          //   R     R   U     U    L        E       S         //
          //   RRRRRR    U     U    L        EEEEE    SSSSSS   //
          //   R   R     U     U    L    L   E              S  //
          //  RRR   RR    UUUUU    LLLLLLL  EEEEEEE   SSSSSS   //
//////////////////////////////////////////////////////////////////////////////

//==== TSynKeyList ===========================================================
constructor TSynKeyList.Create(st: string);
begin
  inherited Create;

  KeyList := TStringList.Create;
  KeyList.Text := st;
end;

destructor TSynKeyList.Destroy;
begin
  KeyList.Free;
  inherited;
end;

//==== TSynSet =========================================================
constructor TSynSet.Create(aSymbSet: TSymbSet = []);
begin
  inherited Create;

  SymbSet := aSymbSet;
end;

destructor TSynSet.Destroy;
begin
  inherited;
end;

//==== TSynRangeRule =========================================================
constructor TSynRangeRule.Create(OpenSymbs: string = ''; CloseSymbs: string = '');
begin
  fOpenSymbol := TSynSymbol.Create(OpenSymbs, nil);
  fCloseSymbol := TSynSymbol.Create(CloseSymbs, nil);
end;

destructor TSynRangeRule.Destroy(); 
begin
  fOpenSymbol.Free();
  fCloseSymbol.Free();
end;

//==== TSynRange =============================================================
constructor TSynRange.Create(OpenSymbs: string; CloseSymbs: string);
begin
  inherited Create;
  OpenCount := 0;

  fRule := TSynRangeRule.Create(OpenSymbs, CloseSymbs);

  fRule.fOpenSymbol.StartType := stAny;
  fRule.fOpenSymbol.BrakeType := btAny;
  fRule.fCloseSymbol.StartType := stAny;
  fRule.fCloseSymbol.BrakeType := btAny;

  FillChar(SymbolList, sizeof(SymbolList), 0);

  SetCaseSensitive(False);

  fPrepared := False;
  fRule.fCloseOnTerm := False;
  fRule.fCloseOnEol := False;

  fSynKeyLists := TList.Create;
  fSynSets := TList.Create;
  fSynSymbols := TList.Create;
  fSynRanges := TList.Create;
  fSynRangeLinks := TList.Create;

  fTermSymbols := DefaultTermSymbols;
end;

destructor TSynRange.Destroy;
//: Destructor of TSynRange
begin
//#  Reset; ???
  fRule.Free();
{  if Assigned(fRule.fOpenSymbol) then
    fRule.fOpenSymbol.Free;
  if Assigned(fRule.fCloseSymbol) then
    fRule.fCloseSymbol.Free;}
  FreeList(fSynKeyLists);
  FreeList(fSynSets);
  FreeList(fSynSymbols);
  FreeList(fSynRanges);
  FreeList(fSynRangeLinks);
  inherited;
end;

//=== Work with fSynSymbols ==================================================
procedure TSynRange.AddSynSymbol(NewSymb: TSynSymbol);
//: Add SynSymbol to the list fSynSymbols. If SynSymbol already exist in list
//:  then remove it and add to the end of the list
//: ??? Может надо если существет не добавлять???
var
  SynSym: TSynSymbol;
begin
  SynSym := FindSymbol(NewSymb.Symbol);
  if SynSym <> nil then
  begin
    fSynSymbols.Remove(SynSym);
    SynSym.Free;
  end;
//  NewSymb.Order := Order;
  fSynSymbols.Add(NewSymb);
end;

function TSynRange.FindSymbol(st: string): TSynSymbol;
//: Find SynSymbol (Symbol = st) in the list fSynSymbols
var
  i: integer;
begin
  Result := nil;
  for i := 0 to fSynSymbols.Count-1 do
   if TSynSymbol(fSynSymbols.Items[i]).Symbol = st then
   begin
     Result := TSynSymbol(fSynSymbols.Items[i]);
     exit;
   end;
end;

//============================================================================
function TSynRange.FindSymbolOwner(Symbol: TSynSymbol): TSynKeyList;
//: Find KeyList that contain SynSymbol
//> Never used!!!
var
  i, j: integer;
begin
  Result := nil;
  for i := 0 to fSynKeyLists.Count-1 do
    if TSynKeyList(fSynKeyLists[i]).KeyList.Find(Symbol.Symbol, j) then
    begin
      Result := TSynKeyList(fSynKeyLists[i]);
      exit;
    end;
end;

//=== Adding rules ===========================================================
procedure TSynRange.AddRule(NewRule: TSynRule);
begin
  if NewRule is TSynRange   then AddRange(NewRule as TSynRange) else
  if NewRule is TSynKeyList then AddKeyList(NewRule as TSynKeyList) else
  if NewRule is TSynSet     then AddSet(NewRule as TSynSet) else
    raise Exception.Create('!!!');
end;

procedure TSynRange.AddCommonRange(Range: TSynRange);
begin
  fSynRangeLinks.Add(Range);
end;

procedure TSynRange.AddRangeLink(NewRangeLink: TSynRangeLink);
begin
  fSynRangeLinks.Add(NewRangeLink);
end;

function TSynRange.AddRangeLink(aRange: TSynRange; aName: string; aColor: TColor): TSynRangeLink;
begin
  Result := TSynRangeLink.Create(aRange);
  with Result do begin
    Name := aName;
    Attribs.Foreground := aColor;
    Attribs.ParentForeground := False;
  end;
  AddRangeLink(Result);
end;

procedure TSynRange.AddRange(NewRange: TSynRange);
begin
  fSynRanges.Add(NewRange);
end;

function TSynRange.AddRange(aOpen, aClose, aName: string; aColor: TColor): TSynRange;
begin
  Result := TSynRange.Create(aOpen, aClose);
  with Result do begin
    Name := aName;
    Attribs.Foreground := aColor;
    Attribs.ParentForeground := False;
  end;
  AddRange(Result);
end;

procedure TSynRange.AddKeyList(NewKeyList: TSynKeyList);
begin
  fSynKeyLists.Add(NewKeyList);
end;

function TSynRange.AddKeyList(aName: string; aColor: TColor): TSynKeyList;
begin
  Result := TSynKeyList.Create('');
  with Result do begin
    Name := aName;
    Attribs.Foreground := aColor;
    Attribs.ParentForeground := False;
  end;
  AddKeyList(Result);
end;

procedure TSynRange.AddSet(NewSet: TSynSet);
begin
  fSynSets.Add(NewSet);
end;

function TSynRange.AddSet(aName: string; aSymbSet: TSymbSet; aColor: TColor): TSynSet;
begin
  Result := TSynSet.Create(aSymbSet);
  with Result do begin
    Name := aName;
    Attribs.Foreground := aColor;
    Attribs.ParentForeground := False;
  end;
  AddSet(Result);
end;

//=== Deleting rules =========================================================
procedure TSynRange.DeleteCommonRange(index: integer);
begin
  TSynRangeLink(fCommonSynRanges[index]).Free;
  fCommonSynRanges.Delete(index);
end;

procedure TSynRange.DeleteCommonRange(Range: TSynRange);
begin
  fCommonSynRanges.Remove(Range);
end;

procedure TSynRange.DeleteRangeLink(index: integer);
begin
  TSynRangeLink(fSynRangeLinks[index]).Free;
  fSynRangeLinks.Delete(index);
end;

procedure TSynRange.DeleteRangeLink(RangeLink: TSynRangeLink);
begin
  fSynRangeLinks.Remove(RangeLink);
  RangeLink.Free;
end;

procedure TSynRange.DeleteRange(Range: TSynRange);
begin
  fSynRanges.Remove(Range);
  Range.Free;
end;

procedure TSynRange.DeleteRange(index: integer);
begin
  TSynRange(fSynRanges[index]).Free;
  fSynRanges.Delete(index);
end;

procedure TSynRange.DeleteKeyList(KeyList: TSynKeyList);
begin
  fSynKeyLists.Remove(KeyList);
  KeyList.Free;
end;

procedure TSynRange.DeleteKeyList(index: integer);
begin
  TSynKeyList(fSynKeyLists[index]).Free;
  fSynKeyLists.Delete(index);
end;

procedure TSynRange.DeleteSet(SynSet: TSynSet);
begin
  fSynSets.Remove(SynSet);
  SynSet.Free;
end;

procedure TSynRange.DeleteSet(index: integer);
begin
  TSynSet(fSynSets[index]).Free;
  fSynSets.Delete(index);
end;

//=== GetCount rules =========================================================
function TSynRange.GetSynSymbolCount: Integer;
begin
  Result := fSynSymbols.Count;
end;

function TSynRange.GetSynRangeLinkCount: Integer;
begin
  Result := fSynRangeLinks.Count;
end;

function TSynRange.GetCommonSynRangeCount(): Integer;
begin
  Result := fCommonSynRanges.Count;
end;

function TSynRange.GetSynRangeCount: Integer;
begin
  Result := fSynRanges.Count;
end;

function TSynRange.GetSynKeyListCount: Integer;
begin
  Result := fSynKeyLists.Count;
end;

function TSynRange.GetSynSetCount: Integer;
begin
  Result := fSynSets.Count;
end;

//=== GetRule from list ======================================================
function TSynRange.GetSynSymbol(Index: Integer): TSynSymbol;
begin
  Result := TSynSymbol(fSynSymbols[Index]);
end;

function TSynRange.GetCommonSynRange(Index: Integer): TSynRange;
begin
  Result := TSynRange(fCommonSynRanges[Index]);
end;

function TSynRange.GetSynRangeLink(Index: Integer): TSynRangeLink;
begin
  Result := TSynRangeLink(fSynRangeLinks[Index]);
end;

function TSynRange.GetSynRange(Index: Integer): TSynRange;
begin
  Result := TSynRange(fSynRanges[Index]);
end;

function TSynRange.GetSynKeyList(Index: Integer): TSynKeyList;
begin
  Result := TSynKeyList(fSynKeyLists[Index]);
end;

function TSynRange.GetSynSet(Index: Integer): TSynSet;
begin
  Result := TSynSet(fSynSets[Index]);
end;

//=== SetDelimiters ==========================================================
procedure TSynRange.SetDelimiters(Delimiters: TSymbSet);
var
  i: integer;
begin
  TermSymbols := Delimiters;
  for i := 0 to RangeCount-1 do
    Ranges[i].SetDelimiters(Delimiters);
end;

(*
procedure TSynRange.SetStyles(aStyles: TSynUniStyles);
//var
//  i: integer;
begin
{  Styles := aStyles;
  for i := 0 to RangeCount-1 do
    Ranges[i].SetStyles(aStyles);}
end;
*)

//=== Case Sensitive =========================================================
function TSynRange.GetCaseSensitive: boolean;
//: Return CaseSensitive
begin
  Result := FCaseSensitive;
end;

procedure TSynRange.SetCaseSensitive(const Value: boolean);
//: Set CaseSensitive
begin
  fCaseSensitive := Value;
  if not Value then begin
    CaseFunct := UpCase;
    StringCaseFunct := UpperCase;
  end
  else begin
    CaseFunct := CaseNone;
    StringCaseFunct := StringCaseNone;
  end;
end;

//=== Prepare rules for parsing ==============================================
procedure QuickSortSymbolList(const List: TList; const lowerPos, upperPos: integer);
var
  i, middlePos: integer;
  pivotValue: string;
Begin
  if lowerPos < upperPos then
  begin

    pivotValue := TSynSymbol(List[lowerPos]).Symbol;
    middlePos := lowerPos; 

    for i := lowerPos + 1 to upperPos do
    begin
      if TSynSymbol(List[i]).Symbol < pivotValue then
      begin
        inc(middlePos);
        List.Exchange(i,middlePos);
      end;
    end;
    List.Exchange(lowerPos,middlePos);

    QuickSortSymbolList(List, lowerPos, middlePos-1);
    QuickSortSymbolList(List, middlePos+1, upperPos);
  end;
end;

procedure TSynRange.ClearParsingFields();
var
  i: integer;
begin
  OpenCount := 0;
  for i := 0 to RangeCount-1 do
    Ranges[i].ClearParsingFields();
end;

procedure TSynRange.ResetParents(aParent: TSynRange);
var
  i: integer;
begin
  Parent := aParent;
  for i := 0 to RangeCount-1 do
    Ranges[i].ResetParents(Self);
end;

procedure TSynRange.Prepare(Owner: TSynRange);
//: This procedure prepare Range for parsing
//: Is called only from SetLine
var
  i, j, Len: integer;
  SynSymbol: TSynSymbol;
  s: string;
  FirstChar: char;
  BrakeType: TSymbBrakeType;

  function SafeInsertSymbol(Symb: TSynSymbol; Rules: TSynRange; Attribs: TSynHighlighterAttributes): TSynSymbol;
  //: This function add Symb to SynRange, if and only if there is no it there
  //: Return added or found element
  begin
    Result := Rules.FindSymbol(Symb.Symbol); //: Find Symb in Rules
    if Result = nil then begin //: If Symb not found, then add Symb to Rules
      Result := TSynSymbol.Create(Symb.Symbol, Symb.Attributes);
      Result.StartType := Symb.StartType;
      Result.BrakeType := Symb.BrakeType;
      Result.StartLine := Symb.StartLine;
      Rules.AddSynSymbol(Result);
    end;
    if Result.Attributes = nil then                //: If attributes of SynSymbol not setted
      Result.Attributes := Attribs;                //: then set them to Attribs
  end;

  function InsertSymbol(Symb: TSynSymbol; Rules: TSynRange): TSynSymbol;
  begin
    Result := Rules.FindSymbol(Symb.Symbol);
    if Result = nil then begin
      Result := TSynSymbol.Create(Symb.Symbol, Symb.Attributes);
      Result.BrakeType := Symb.BrakeType;
      Rules.AddSynSymbol(Result);
    end;
    Result.Attributes := Symb.Attributes;
  end;

var
  Range: TSynRange;
  RangeLink: TSynRangeLink;

begin
  Reset;            //: If already prepared then reset it!
  fOwner := Owner;
  OpenCount := 0;

  fDefaultSynSymbol  := TSynSymbol.Create('', Attribs);
  fDefaultTermSymbol := TDefaultTermSymbols.Create(TSynSymbol.Create('', Attribs));
  fDefaultSymbols    := TDefaultSymbols.Create(TSynSymbol.Create('', Attribs));

  fTermSymbols := fTermSymbols+AbsoluteTermSymbols;

 if Enabled then begin

  //Add all keywords to list fSynSymbols:
  for i := 0 to fSynKeyLists.Count-1 do //: All KeyLists
    if TSynKeyList(fSynKeyLists[i]).Enabled then
      for j := 0 to TSynKeyList(fSynKeyLists[i]).KeyList.Count-1 do begin//: All keywords in KeyLists
        //: Add current keyword to list fSynSymbols:
        InsertSymbol{AddSymbol}(TSynSymbol.Create(TSynKeyList(fSynKeyLists[i]).KeyList[j],
                                TSynKeyList(fSynKeyLists[i]).Attribs),
                                self);
      end;

  //Assign range opening and closing symbols and Prepare range rules.
  for i := 0 to fSynRanges.Count-1 do begin
    Range := TSynRange(fSynRanges[i]);
    if Range.Enabled then begin
      //Assign range opening symbol
      SynSymbol := SafeInsertSymbol(Range.fRule.fOpenSymbol, Self, Range.Attribs);
      SynSymbol.fOpenRule := Range;

      //Assing range closing symbols
      SynSymbol := SafeInsertSymbol(Range.fRule.fCloseSymbol, Range, Range.Attribs);
      Range.fClosingSymbol := SynSymbol;
      Range.Prepare(Self);
    end;
  end;

  for i := 0 to fSynRangeLinks.Count-1 do begin
    RangeLink := TSynRangeLink(fSynRangeLinks[i]);
    Range := RangeLink.Range;
    if RangeLink.Enabled then begin
      //Assign range opening symbol
      SynSymbol := SafeInsertSymbol(Range.fRule.fOpenSymbol, Self, Range.Attribs);
      SynSymbol.fOpenRule := RangeLink;
      RangeLink.Parent := Self;
      //Assing range closing symbols
//      SynSymbol := SafeInsertSymbol(Range.fRule.fCloseSymbol, Range, Range.Attribs.Std);
//      Range.fClosingSymbol := SynSymbol;
//      Range.Prepare(Self);
    end;
  end;

  //Build tokens table
  QuickSortSymbolList(fSynSymbols, 0, fSynSymbols.Count-1); //: Sort fSynSymbols
  for i := 0 to fSynSymbols.Count-1 do begin       //: run all  SynSymbols
    SynSymbol := TSynSymbol(fSynSymbols[i]);       //: SynSymbol - next SymSymbol
    Len := Length(SynSymbol.Symbol);
    if Len < 1 then                                //: If length equal zero
      continue;                                    //:   then next SynSymbol
    s := SynSymbol.Symbol;                         //: String of SynSymbol
    FirstChar := s[1];                             //: First symbol of string of SynSymbol

    if SynSymbol.BrakeType <> btUnspecified then   //: If BrakeType defined then
      BrakeType := SynSymbol.BrakeType             //:   Write this BreakType to local variable
    else                                           //: Else (if BrakeType not defined)
      if s[Len] in fTermSymbols then               //:   If last symbol is TermSymbol
        BrakeType := btAny                         //:     Write to BreakType: btAny
      else                                         //:   Else
        BrakeType := btTerm;                       //:     Write to BreakType: btTerm

    if SymbolList[CaseFunct(FirstChar)] = nil then //: If in SymbolList on FirstChar there is no nothing
    begin
      if Len = 1 then                              //:   If length of string of SynSymbol equal 1
                                                   //:     then write SynSymbol in this element of SimbolList
        SymbolList[CaseFunct(FirstChar)] := TSymbols.Create(FirstChar, SynSymbol, BrakeType)
      else begin                                   //:   Else (length of SynSymbol greate then 1)
                                                   //:     Write fDefaultSynSymbol (???) to this element | FirstChar
        SymbolList[CaseFunct(FirstChar)] := TSymbols.Create(FirstChar, fDefaultSynSymbol, BrakeType);
                                                   //:     and add SynSymbol to this element | All but without FirstChar
        TSymbols(SymbolList[CaseFunct(FirstChar)]).AddSymbol(StringCaseFunct(copy(s, 2, Len-1)), SynSymbol, BrakeType);
      end;
    end
    else begin                                     //: Else (if in SynSymbol exist something)
      if Len = 1 then
      else                                         //: If length of string SynSymbol greate then 1
                                                   //:   Add SynSymbol to this element | All but without FirstChar
        TSymbols(SymbolList[CaseFunct(FirstChar)]).AddSymbol(StringCaseFunct(copy(s, 2, Len-1)), SynSymbol, BrakeType);
    end;
  end;

{begin}
  if fSynSets.Count > 0 then
    for i := 0 to 255 do
      for j := 0 to fSynSets.Count-1 do begin
        if TSynSet(fSynSets[j]).Enabled and (char(i) in TSynSet(fSynSets[j]).SymbSet) then
          if SymbolList[CaseFunct(char(i))] = nil then
            SymbolList[CaseFunct(char(i))] := TSymbols.Create(TSynSet(fSynSets[j]))
          else
            TSymbols(SymbolList[CaseFunct(char(i))]).AddSet(TSynSet(fSynSets[j]));
      end;
//          SymbolList[char(i)] := fSetSymbols;
//          TSetSymbols(SymbolList[char(i)]).AddSetfSetSymbols;
{end}
 end;
  //Fill remaining table
  for i := 0 to 255 do
  if SymbolList[char(i)] = nil then
  begin
    if char(i) in fTermSymbols then
      SymbolList[char(i)] := fDefaultTermSymbol
    else
      SymbolList[char(i)] := fDefaultSymbols;
   end;

  fPrepared := true;
end;

procedure TSynRange.Reset;
//: Clear some properties of SynRange,
//: вызывается при очистке Clear, а также при Подготовке SynRang'a (Prepare)
//: Ресетится только если SynRange был уже подготовлен!
var
  i: integer;
begin
  if not fPrepared then
    exit;
  fDefaultSynSymbol.Free;
  fDefaultTermSymbol.Free;
  fDefaultSymbols.Free;
  for i := 0 to 255 do
    SymbolList[char(i)] := nil; //maybe need to free???
  for i := 0 to fSynRanges.Count-1 do
    TSynRange( fSynRanges[i] ).Reset;
  ClearList(fSynSymbols);
  fPrepared := False;
end;

procedure TSynRange.Clear;
//: Clear primary properties of SynRang, call in creating new rools
var
  i: integer;
begin
//!!!!!!!!!!!!!!!!!!!!!! Нужно еще очищать или удалять OpenSymbol и CloseSymbol !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  Reset;  //: Reser Range (clear some properties)
  for i := 0 to fSynRanges.Count-1 do   //: Clear all sub-ranges
    TSynRange(fSynRanges[i]).Clear;
  ClearList(fSynRanges);
  ClearList(fSynSymbols);
  ClearList(fSynKeyLists);
  ClearList(fSynSets);
end;

function TSynRange.FindRange(const Name: string): TSynRange;
Var
  I: integer;
begin
  Result := nil;
  if fOwner = nil then Exit;

  for I := 0 to fOwner.RangeCount - 1 do
    if SameText(Name, fOwner.Ranges[I].Name) then
    begin
      Result := fOwner.Ranges[I];
      Exit;
    end;
end;

procedure TSynRange.SetColorForChilds;
var
  i: integer;
begin
  for i := 0 to RangeCount-1 do begin
    if Ranges[i].Attribs.ParentForeground then begin
      Ranges[i].Attribs.Foreground := Attribs.Foreground;
      Ranges[i].Attribs.OldColorForeground := Attribs.Foreground;
    end;
    if Ranges[i].Attribs.ParentBackground then begin
      Ranges[i].Attribs.Background := Attribs.Background;
      Ranges[i].Attribs.OldColorBackground := Attribs.Background;
    end;
    Ranges[i].SetColorForChilds;
  end;
  for i := 0 to KeyListCount-1 do begin
    if KeyLists[i].Attribs.ParentForeground then
      KeyLists[i].Attribs.Foreground := Attribs.Foreground;
    if KeyLists[i].Attribs.ParentBackground then
      KeyLists[i].Attribs.Background := Attribs.Background;
  end;
  for i := 0 to SetCount-1 do begin
    if Sets[i].Attribs.ParentForeground then
      Sets[i].Attribs.Foreground := Attribs.Foreground;
    if Sets[i].Attribs.ParentBackground then
      Sets[i].Attribs.Background := Attribs.Background;
  end;
end;

//==== TSynRangeLink =========================================================
constructor TSynRangeLink.Create(aRange: TSynRange);
begin
  inherited Create;
  Range := aRange;
  Parent := nil;
end;

//////////////////////////////////////////////////////////////////////////////
     //  LLL       OOOOO    AAAAA   DDDDDD   IIIII  NN    N   GGGGGG  //
     //   L       O     O  A     A   D    D    I    NNN   N  G        //
     //   L       O     O  AAAAAAA   D    D    I    N NNN N  G   GGG  //
     //   L    L  O     O  A     A   D    D    I    N   NNN  G     G  //
     //  LLLLLLL   OOOOO   A     A  DDDDDD   IIIII  N    NN   GGGGGG  //
//////////////////////////////////////////////////////////////////////////////
procedure TSynKeyList.LoadFromXml(xml: TDOMNode);
var
  I, J: Integer;
  ChildNode: TDOMNode;
  Key, Value, LowValue: string;
//  OldAttribs: {TSynHighlighter}TSynAttributes;
begin
  if xml = nil then Exit;
  if not SameText(xml.NodeName, 'Keywords') then xml:= xml.FindNode('Keywords');
  if xml = nil then raise Exception.Create(ClassName + '.LoadFromXml - no keywords to load!');

  for I := 0 to Int32(xml.Attributes.Length) - 1 do
  begin
    Key := xml.Attributes[I].NodeName;
    Value := xml.Attributes[I].NodeValue;
    LowValue := LowerCase(Value);
    if SameText('Name', Key) then Name := Value else
    if SameText('Enabled', Key) then Enabled := (LowValue = 'true') else
    if SameText('Attributes', Key) then
      Attribs.LoadFromString(Value) else
    if SameText('Style', Key) then begin
      Style := Value;
      if Styles <> nil then //begin
//          OldAttribs := Attribs;
        Attribs := Styles.GetStyleDef(Value, Attribs);
//          if OldAttribs <> Attribs then
//            Attribs.UseStyle := True;
{        if (Attribs = DefaultAttr) or (Attribs = nil) then
        Attribs := DefaultAttri;}
//        end;
    end else
//    Attribs := fStyles.GetStyleDef(getAttrValue('style', xml), defaultattr);
  end;

  KeyList.BeginUpdate;
  KeyList.Clear;
  try
    for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
    begin
      ChildNode:= xml.ChildNodes.Item[J];
      if SameText('Word', ChildNode.NodeName) then
        if (ChildNode.Attributes.Length > 0) and SameText('Value', ChildNode.Attributes[0].NodeName) then
          KeyList.Add(ChildNode.Attributes[0].NodeValue);
    end;
  finally
    KeyList.EndUpdate;
  end;
end;

procedure TSynSet.LoadFromXml(xml: TDOMNode);
var
  i: integer;
  Key, Value, LowValue: string;
//  OldAttribs: {TSynHighlighter}TSynAttributes;
begin
  if xml = nil then Exit;
  if not SameText(xml.NodeName, 'Set') then xml:= xml.FindNode('Set');
  if xml = nil then raise Exception.Create(ClassName + '.LoadFromXml - no set to load!');

  for i := 0 to Int32(xml.Attributes.Length) - 1 do
  begin
    Key := xml.Attributes[i].NodeName;
    Value := xml.Attributes[i].NodeValue;
    LowValue := LowerCase(Value);         {ind := 0;}
    if SameText('Name', Key) then Name := Value else
    if SameText('Enabled', Key) then Enabled := (LowValue = 'true') else
    if SameText('Attributes', Key) then
      Attribs.LoadFromString(Value) else
    if SameText('Style', Key) then begin
      Style := Value;
      if Styles <> nil then //begin
//          OldAttribs := Attribs;
        Attribs := Styles.GetStyleDef(Value, Attribs);
//          if OldAttribs <> Attribs then
//            Attribs.UseStyle := True;
{        if (Attribs = DefaultAttr) or (Attribs = nil) then
        Attribs := DefaultAttri;}
//        end;
    end else
    if SameText('Symbols', Key) then SymbSet := StrToSet(Value)
//    Attribs := fStyles.GetStyleDef(getAttrValue('style', xml), defaultattr);
  end;
end;

procedure TSynRange.LoadFromXml(xml: TDOMNode);
var
  I, J: Integer;
  ChildNode: TDOMNode;
  NewSynRange: TSynRange;
  NewSynKeyList: TSynKeyList;
  NewSynSet: TSynSet;
  Key, Value, LowValue: string;
//  OldAttribs: {TSynHighlighter}TSynAttributes;
begin
  if xml = nil then Exit;
  if not SameText(xml.NodeName, 'Range') then xml:= xml.FindNode('Range');
  if xml = nil then raise Exception.Create(ClassName + '.LoadFromXml - no range to load!');

//    Clear;            //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Вставить нормальный Clear в KeyList и SynSet !!!!!!!!!!!!!!!!!!
  Enabled := True;   CaseSensitive := False;
  for i := 0 to Int32(xml.Attributes.Length) - 1 do
  begin
    Key := xml.Attributes[i].NodeName;
    Value := xml.Attributes[i].NodeValue;
    LowValue := LowerCase(Value);
    if SameText('Name', Key) then Name := Value else
    if SameText('Enabled', Key) then Enabled := (LowValue = 'true') else
    if SameText('Attributes', Key) then
      Attribs.LoadFromString(Value) else
    if SameText('Style', Key) then begin
      Style := Value;
      if Styles <> nil then //begin
//          OldAttribs := Attribs;
        Attribs := Styles.GetStyleDef(Value, Attribs);
//          if OldAttribs <> Attribs then
//            Attribs.UseStyle := True;
{        if (Attribs = DefaultAttr) or (Attribs = nil) then
        Attribs := DefaultAttri;}
//        end;
    end else
    if SameText('CaseSensitive', Key) then CaseSensitive := (LowValue = 'true') else
    if SameText('Delimiters', Key) then
//      if SameText(GetAttrValue('spaces', xml), 'true') then
//        TermSymbols := String2Set(xml.CurContent) + [#32, #9, #13, #10] else
      TermSymbols := StrToSet(Value)  // CloseOnTerm := true;
  end;

  for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
  begin
    ChildNode:= xml.ChildNodes.Item[J];
    if SameText('Rule', ChildNode.NodeName) then
    for i := 0 to Int32(ChildNode.Attributes.Length) - 1 do begin
      Key := ChildNode.Attributes[i].NodeName;
      Value := ChildNode.Attributes[i].NodeValue;
        LowValue := LowerCase(Value);
        if SameText('Enabled', Key) then Enabled := (LowValue = 'true') else
        if SameText('OpenSymbol', Key) then fRule.fOpenSymbol.Symbol := Value else
        if SameText('OpenSymbolFinishOnEol', Key) then
          if LowValue = 'true' then fRule.fOpenSymbol.Symbol := fRule.fOpenSymbol.Symbol + #0 else else
        if SameText('OpenSymbolStartLine', Key) then with fRule.fOpenSymbol do
          if LowValue = 'true' then     StartLine := slFirst else
          if LowValue = 'nonspace' then StartLine := slFirstNonSpace else
                                        StartLine := slNotFirst else
        if SameText('OpenSymbolPartOfTerm', Key) then with fRule.fOpenSymbol do
          if LowValue = 'true' then begin  StartType := stAny;  BrakeType := btAny; end else
          if LowValue = 'left' then begin  StartType := stAny;  BrakeType := btTerm; end else
          if LowValue = 'right' then begin StartType := stTerm; BrakeType := btAny; end else
                                     begin StartType := stTerm; BrakeType := btTerm; end else
        if SameText('CloseSymbol', Key) then fRule.fCloseSymbol.Symbol := Value else
        if SameText('CloseSymbolFinishOnEol', Key) then
          if LowValue = 'true' then fRule.fCloseSymbol.Symbol := fRule.fCloseSymbol.Symbol + #0 else else
        if SameText('CloseSymbolStartLine', Key) then with fRule.fCloseSymbol do
          if LowValue = 'true' then     StartLine := slFirst else
          if LowValue = 'nonspace' then StartLine := slFirstNonSpace else
                                        StartLine := slNotFirst else
        if SameText('CloseSymbolPartOfTerm', Key) then with fRule.fCloseSymbol do
          if LowValue = 'true' then begin  StartType := stAny;  BrakeType := btAny; end else
          if LowValue = 'left' then begin  StartType := stAny;  BrakeType := btTerm; end else
          if LowValue = 'right' then begin StartType := stTerm; BrakeType := btAny; end else
                                     begin StartType := stTerm; BrakeType := btTerm; end else
        if SameText('CloseOnTerm',    Key) then fRule.fCloseOnTerm    := (LowValue = 'true') else
        if SameText('CloseOnEol',     Key) then fRule.fCloseOnEol     := (LowValue = 'true') else
        if SameText('AllowPredClose', Key) then fRule.fAllowPredClose := (LowValue = 'true') else
      end else
    if SameText('Range', ChildNode.NodeName) then begin
      NewSynRange := TSynRange.Create;
      NewSynRange.Styles := Styles;
      AddRange(NewSynRange);
      NewSynRange.LoadFromXml(ChildNode);
    end else
    if SameText('Keywords', ChildNode.NodeName) then begin
      NewSynKeyList := TSynKeyList.Create;
      NewSynKeyList.Styles := Styles;
      AddKeyList(NewSynKeyList);
      NewSynKeyList.LoadFromXml(ChildNode);
    end else
    if SameText('Set', ChildNode.NodeName) then begin
      NewSynSet := TSynSet.Create;
      NewSynSet.Styles := Styles;
      AddSet(NewSynSet);
      NewSynSet.LoadFromXml(ChildNode);
    end
{      else if SameText(xml.CurName, 'TextStyle') then
    begin
      DefaultAttri := fStyles.getStyleDef(xml.CurContent, defaultAttr);
      if (NumberAttri = DefaultAttr) or (NumberAttri = nil) then
        NumberAttri := DefaultAttri;
    end
    else if SameText(xml.CurName, 'NumberStyle') then
      NumberAttri := fStyles.getStyleDef(xml.CurContent, defaultAttr);}
  end;
end;

//////////////////////////////////////////////////////////////////////////////
        //   SSSSSS   AAAAAA   V       V  IIIII  NN    N   GGGGGGG  //
        //  S        A      A  V       V    I    NNN   N  G         //
        //   SSSSS   AAAAAAAA  VV     VV    I    N NNN N  G    GGG  //
        //        S  A      A    VV VV      I    N   NNN  G      G  //
        //  SSSSSS   A      A      V      IIIII  N    NN   GGGGGGG  //
//////////////////////////////////////////////////////////////////////////////

procedure TSynKeyList.SaveToStream(StreamWriter: TStreamWriter; Ind: integer);
var
  i: integer;
begin
  with StreamWriter do begin
    WriteTag(Ind, 'Keywords');
    WriteParam('Name', Name);
    WriteBoolParam('Enabled', Enabled, True);
    Attribs.SaveToStream(StreamWriter);    
    WriteParam('Style', Style);
    WriteString(CloseStartTag + EOL);
    for i := 0 to KeyList.Count-1 do begin
      WriteTag(Ind+2, 'Word');
      WriteParam('Value', KeyList[i], CloseEmptyTag);
    end;
    WriteTag(Ind, '/Keywords', True);
  end;
end;

procedure TSynSet.SaveToStream(StreamWriter: TStreamWriter; Ind: integer);
begin
  with StreamWriter do begin
    WriteTag(Ind, 'Set');
    WriteParam('Name', Name);
    WriteBoolParam('Enabled', Enabled, True);
    Attribs.SaveToStream(StreamWriter);    
    WriteParam('Style', Style);
    WriteParam('Symbols', SetToStr(SymbSet), CloseEmptyTag);
{    if S.StartType = stAny then
      if S.BrakeType = btAny then InsertTag(Ind+1, 'SymbolSetPartOfTerm', 'True')
      else InsertTag(Ind+1, 'SymbolSetPartOfTerm', 'Left')
    else
      if S.BrakeType = btAny then InsertTag(Ind+1, 'SymbolSetPartOfTerm', 'Right')
      else InsertTag(Ind+1, 'SymbolSetPartOfTerm', 'False');}
  end;
end;

procedure TSynRange.SaveToStream(StreamWriter: TStreamWriter; Ind: integer);
var
  i: integer;
begin
  with StreamWriter do begin
    WriteTag(Ind, 'Range');
    WriteParam('Name', Name);
    WriteBoolParam('Enabled', Enabled, True);
    Attribs.SaveToStream(StreamWriter);    
    WriteParam('Style', Style);
    WriteBoolParam('CaseSensitive', CaseSensitive, False);
    WriteString(EOL + Indent(Ind + Length('<Range')));
    WriteParam('Delimiters', SetToStr(TermSymbols), CloseStartTag);
    for i := 0 to 0 do begin
      WriteTag(Ind+2, 'Rule');
      with fRule.fOpenSymbol do begin
        if Length(Symbol) > 0 then
          if Symbol[Length(Symbol)] = #0 then begin
            WriteParam('OpenSymbol', copy(Symbol, 1, Length(Symbol) - 1));
            WriteParam('OpenSymbolFinishOnEol', 'True');
          end else begin
            WriteParam('OpenSymbol', Symbol);
            {WriteParam('OpenSymbolFinishOnEol', 'False');}
          end;
        if StartLine = slFirst         then WriteParam('OpenSymbolStartLine', 'True') else
        if StartLine = slFirstNonSpace then WriteParam('OpenSymbolStartLine', 'NonSpace');
                                       //else WriteParam('OpenSymbolStartLine', 'False');
        if StartType = stAny then
          if BrakeType = btAny then //WriteParam('OpenSymbolPartOfTerm', 'True')
                               else WriteParam('OpenSymbolPartOfTerm', 'Left') else
          if BrakeType = btAny then WriteParam('OpenSymbolPartOfTerm', 'Right')
                               else WriteParam('OpenSymbolPartOfTerm', 'False');
      end;
      with fRule.fCloseSymbol do begin
        if Length(Symbol) > 0 then
          if Symbol[Length(Symbol)] = #0 then begin
            WriteParam('CloseSymbol', copy(Symbol, 1, Length(Symbol) - 1));
            WriteParam('CloseSymbolFinishOnEol', 'True');
          end else begin
            WriteParam('CloseSymbol', Symbol);
            {WriteParam('CloseSymbolFinishOnEol', 'False');}
          end;
        if StartLine = slFirst         then WriteParam('CloseSymbolStartLine', 'True') else
        if StartLine = slFirstNonSpace then WriteParam('CloseSymbolStartLine', 'NonSpace');
                                       //else WriteParam('CloseSymbolStartLine', 'False');
        if StartType = stAny then
          if BrakeType = btAny then //WriteParam('CloseSymbolPartOfTerm', 'True')
                               else WriteParam('CloseSymbolPartOfTerm', 'Left') else
          if BrakeType = btAny then WriteParam('CloseSymbolPartOfTerm', 'Right')
                               else WriteParam('CloseSymbolPartOfTerm', 'False');
      end;
      WriteBoolParam('CloseOnTerm',    fRule.fCloseOnTerm,    False);
      WriteBoolParam('CloseOnEol',     fRule.fCloseOnEol,     False);
      WriteBoolParam('AllowPredClose', fRule.fAllowPredClose, False);
      WriteString(CloseEmptyTag + EOL);
    end;
    for i := 0 to KeyListCount-1 do KeyLists[i].SaveToStream(StreamWriter, Ind+2);
    for i := 0 to SetCount-1     do Sets[i].SaveToStream(StreamWriter, Ind+2);
    for i := 0 to RangeCount-1   do Ranges[i].SaveToStream(StreamWriter, Ind+2);
    WriteTag(Ind, '/Range', True);
  end;
end;

function ReadValue(ANode: TDOMNode): String;
begin
  if Assigned(ANode.FirstChild) then
    Result:= ANode.FirstChild.NodeValue
  else
    Result:= EmptyStr;
end;

function Verify(tag: string; xml: TDOMNode): boolean; overload;
begin
  Result := SameText(xml.NodeName, tag);
end;

procedure LoadAttri(curRule: TSynRule; xml: TDOMNode);
var
  J: Integer;
  ChildNode: TDOMNode;
begin
//  inc(CurRule.ind);
  CurRule.Attribs{ByIndex[0]}.ParentForeground := False;
  CurRule.Attribs{ByIndex[0]}.ParentBackground := False;
  for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
  begin
    ChildNode:= xml.ChildNodes.Item[J];
    if Verify('Back',ChildNode) then begin
      CurRule.Attribs.Background := StrToIntDef(ReadValue(ChildNode), $FFFFFF);
      CurRule.Attribs.OldColorBackground := CurRule.Attribs.Background;
    end else
    if Verify('Fore',ChildNode) then begin
      CurRule.Attribs.Foreground := StrToIntDef(ReadValue(ChildNode), 0);
      CurRule.Attribs.OldColorForeground := CurRule.Attribs.Foreground;
    end else
    if Verify('Style',ChildNode) then
      CurRule.Attribs.Style := StrToFontStyle(ReadValue(ChildNode))
    else if Verify('ParentForeground',ChildNode) then
      CurRule.Attribs.ParentForeground := LowerCase(ReadValue(ChildNode)) = 'true'
    else if Verify('ParentBackground',ChildNode) then
      CurRule.Attribs.ParentBackground := LowerCase(ReadValue(ChildNode)) = 'true';
  end;
end;

procedure TSynKeyList.LoadHglFromXml(xml: TDOMNode; SchCount,SchIndex: integer);
var
  J: Integer;
  ChildNode: TDOMNode;
  TempSchIndex: integer;
begin
//  if curKw = nil then Exit;
  if xml = nil then Exit;

  if ( SameText('KW'{'Keywords'}, xml.NodeName) ) then
    begin
      if xml.Attributes.Length > 0 then Name := xml.Attributes[0].NodeValue
//    Attribs := fStyles.GetStyleDef(getAttrValue('style', xml), defaultattr);
    end
  else
    raise Exception.Create(ClassName + '.LoadFromXml - no keywords to load!');

//  ClearAttributes();
//  for i := 0 to SchCount-1 do
//    AddAttribute();
//  ind := -1;
  TempSchIndex := SchIndex;

  KeyList.BeginUpdate;
  KeyList.Clear;
  try
    for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
    begin
      ChildNode:= xml.ChildNodes.Item[J];
      if SameText(ChildNode.NodeName, 'Attri') or SameText(ChildNode.NodeName, 'Def') then begin
        if TempSchIndex >= 0 then LoadAttri(self, ChildNode);
        dec(TempSchIndex);
      end else
      if Verify('Enabled',ChildNode) then
        Enabled := LowerCase(ReadValue(ChildNode)) = 'true'
      else
      if Verify('W',ChildNode) then begin
        KeyList.Add(ReadValue(ChildNode));
      end;
    end;
  finally
    ind := SchIndex;
    KeyList.EndUpdate;
  end;
end;

procedure TSynSet.LoadHglFromXml(xml: TDOMNode; SchCount,SchIndex: integer);
var
  J: Integer;
  ChildNode: TDOMNode;
  TempSchIndex: integer;
begin
//  if curSet = nil then Exit;
  if xml = nil then Exit;

  if ( SameText('Set', xml.NodeName) ) then
    begin
      if xml.Attributes.Length > 0 then Name := xml.Attributes[0].NodeValue
//    Attribs := fStyles.GetStyleDef(getAttrValue('style', xml), defaultattr);
    end
  else
    raise Exception.Create(ClassName + '.LoadFromXml - no set to load!');

{  ClearAttributes();
  for i := 0 to SchCount-1 do
    AddAttribute();
  ind := -1;}
  TempSchIndex := SchIndex;

  for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
  begin
    ChildNode:= xml.ChildNodes.Item[J];
    if SameText(ChildNode.NodeName, 'Attri') or SameText(ChildNode.NodeName, 'Def') then begin
      if TempSchIndex >= 0 then LoadAttri(self, ChildNode);
      dec(TempSchIndex);
    end else
    if Verify('Enabled',ChildNode) then
      Enabled := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('S',ChildNode) then
      SymbSet := StrToSet(ReadValue(ChildNode));
  end;

  ind := SchIndex;
end;

procedure TSynRange.LoadHglFromXml(xml: TDOMNode; SchCount, SchIndex: integer);
var
  NewSynRange: TSynRange;
  NewSynKeyList: TSynKeyList;
  NewSynSet: TSynSet;
  S: string;
  TempSchIndex: integer;
  J: Integer;
  ChildNode: TDOMNode;
begin
  fRule.fOpenSymbol.BrakeType := btAny;
  if SameText(xml.NodeName, 'Range') then
  begin
    if xml.Attributes.Length > 0 then
      S:= xml.Attributes[0].NodeValue;
    if S <> '' then
      Name := S;
  end else
    raise Exception.Create(ClassName + '.LoadFromXml - no range to load!');

{  ClearAttributes();
  for i := 0 to SchCount-1 do
    AddAttribute();
  ind := -1;}
  TempSchIndex := SchIndex;

  for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
  begin
    ChildNode:= xml.ChildNodes.Item[J];
    if Verify('Enabled', ChildNode) then
      Enabled := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('CaseSensitive', ChildNode) then
      CaseSensitive := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('OpenSymbol', ChildNode) then
      fRule.fOpenSymbol.Symbol := ReadValue(ChildNode)
    else
    if Verify('CloseSymbol', ChildNode) then
      fRule.fCloseSymbol.Symbol := ReadValue(ChildNode)
    else
    if Verify('OpenSymbolFinishOnEol', ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then
        fRule.fOpenSymbol.Symbol := fRule.fOpenSymbol.Symbol + #0
      else
    else
    if Verify('CloseSymbolFinishOnEol',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then
        fRule.fCloseSymbol.Symbol := fRule.fCloseSymbol.Symbol + #0
      else
    else
    if Verify('CloseOnTerm',ChildNode) then
      fRule.fCloseOnTerm := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('CloseOnEol',ChildNode) then
      fRule.fCloseOnEol := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('AllowPredClose',ChildNode) then
      fRule.fAllowPredClose := LowerCase(ReadValue(ChildNode)) = 'true'
    else
    if Verify('OpenSymbolStartLine',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then
        fRule.fOpenSymbol.StartLine := slFirst
      else if LowerCase(ReadValue(ChildNode)) = 'nonspace' then
        fRule.fOpenSymbol.StartLine := slFirstNonSpace
      else
        fRule.fOpenSymbol.StartLine := slNotFirst
    else
    if Verify('CloseSymbolStartLine',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then
        fRule.fCloseSymbol.StartLine := slFirst
      else if LowerCase(ReadValue(ChildNode)) = 'nonspace' then
        fRule.fCloseSymbol.StartLine := slFirstNonSpace
      else
        fRule.fCloseSymbol.StartLine := slNotFirst
    else
    if Verify('AnyTerm',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then
        fRule.fOpenSymbol.BrakeType := btAny
      else
        fRule.fOpenSymbol.BrakeType := btTerm
//        if StrToBoolDef(ReadValue(ChildNode), false) then
//          fRule.fOpenSymbol.BrakeType := btTerm;
    else
    if Verify('OpenSymbolPartOfTerm',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then begin
        fRule.fOpenSymbol.StartType := stAny;
        fRule.fOpenSymbol.BrakeType := btAny;
      end else if LowerCase(ReadValue(ChildNode)) = 'left' then begin
        fRule.fOpenSymbol.StartType := stAny;
        fRule.fOpenSymbol.BrakeType := btTerm;
      end else if LowerCase(ReadValue(ChildNode)) = 'right' then begin
        fRule.fOpenSymbol.StartType := stTerm;
        fRule.fOpenSymbol.BrakeType := btAny;
      end else begin
        fRule.fOpenSymbol.StartType := stTerm;
        fRule.fOpenSymbol.BrakeType := btTerm;
      end
    else
    if Verify('CloseSymbolPartOfTerm',ChildNode) then
      if LowerCase(ReadValue(ChildNode)) = 'true' then begin
        fRule.fCloseSymbol.StartType := stAny;
        fRule.fCloseSymbol.BrakeType := btAny;
      end else if LowerCase(ReadValue(ChildNode)) = 'left' then begin
        fRule.fCloseSymbol.StartType := stAny;
        fRule.fCloseSymbol.BrakeType := btTerm;
      end else if LowerCase(ReadValue(ChildNode)) = 'right' then begin
        fRule.fCloseSymbol.StartType := stTerm;
        fRule.fCloseSymbol.BrakeType := btAny;
      end else begin
        fRule.fCloseSymbol.StartType := stTerm;
        fRule.fCloseSymbol.BrakeType := btTerm;
      end
    else
    if Verify('DelimiterChars',ChildNode) then
//        if SameText(GetAttrValue('spaces', xml), 'true') then
//          TermSymbols := String2Set(ReadValue(ChildNode)) + [#32, #9, #13, #10]
//        else
      if Assigned(ChildNode.FirstChild) then
        TermSymbols := StrToSet(ReadValue(ChildNode))
      else
//        CloseOnTerm := true;
    else
{      else if SameText(xml.CurName, 'TextStyle') then
    begin
      DefaultAttri := fStyles.getStyleDef(ReadValue(ChildNode), defaultAttr);
      if (NumberAttri = DefaultAttr) or (NumberAttri = nil) then
        NumberAttri := DefaultAttri;
    end
    else if SameText(xml.CurName, 'NumberStyle') then
      NumberAttri := fStyles.getStyleDef(ReadValue(ChildNode), defaultAttr);}
    if SameText(ChildNode.NodeName, 'Attri') or SameText(ChildNode.NodeName, 'Def') then begin
      if TempSchIndex >= 0 then LoadAttri(self, ChildNode);
      dec(TempSchIndex);
    end else
    if SameText(ChildNode.NodeName, 'Range') then begin
      NewSynRange := TSynRange.Create;
      AddRange(NewSynRange);
      NewSynRange.LoadHglFromXml(ChildNode, SchCount, SchIndex);
    end else
    if SameText(ChildNode.NodeName, 'KW') then begin
      NewSynKeyList := TSynKeyList.Create;
      AddKeyList(NewSynKeyList);
      NewSynKeyList.LoadHglFromXml(ChildNode, SchCount, SchIndex);
    end else
    if SameText(ChildNode.NodeName, 'Set') then begin
      NewSynSet := TSynSet.Create;
      AddSet(NewSynSet);
      NewSynSet.LoadHglFromXml(ChildNode, SchCount, SchIndex);
    end;
  end;
  ind := SchIndex;
end;

procedure TSynRange.LoadHglFromStream(aSrc: TStream);
var
  I, J: Integer;
  xml: TXMLDocument = nil;
  SchCount, SchIndex: integer;
  ChildNode, ChildNode2: TDOMNode;
begin
  try
    SchCount := 0;   SchIndex := -1;
    ReadXMLFile(xml, aSrc);

    for J := 0 to Int32(xml.ChildNodes.Count) - 1 do
    begin
      ChildNode:= xml.ChildNodes.Item[J];
      if Verify('SchemeIndex', ChildNode) then
        SchIndex := StrToInt(ReadValue(ChildNode))
      else if Verify('Schemes', ChildNode) then
      begin
        for I:= 0 to Int32(ChildNode.ChildNodes.Count) - 1 do begin
        ChildNode2:= ChildNode.ChildNodes.Item[I];
          if Verify('S', ChildNode2) then
            inc(SchCount);
        end
      end
      else if SameText(ChildNode.NodeName, 'Range') then
        LoadHglFromXml(ChildNode, SchCount, SchIndex);
    end;
  finally
    xml.Free;
  end;
end;

end.
