{*******************************************************}
{                                                       }
{       ZaphodsMapSetup                                 }
{                                                       }
{       Setup class for ZaphodsMap keybox files and     }
{       environment variable                            }
{                                                       }
{       Object Pascal implementation of ideas at        }
{       www.zaphodsmap.com. This source compiles with   }
{       at least Delphi 7/9/10/11/12/14/15 for win32,   }
{       Delphi 16 for win32&win64, and Kylix 3.         }
{                                                       }
{       This file is licensed under a Creative Commons  }
{       Attribution 2.5 License.                        }
{       http://creativecommons.org/licenses/by/2.5/     }
{       If you use this file, please keep this notice   }
{       intact.                                         }
{                                                       }
{       Developed by HREF Tools Corp. 2005-2011         }
{                                                       }
{       v1.041                                          }
{                                                       }
{*******************************************************}
unit ZaphodsMapSetup;

interface

{$I hrefdefines.inc}

uses
{$IFDEF CLR}
  System.IO,
  System.Reflection,
  System.Runtime.InteropServices,
{$ENDIF}
{$IFNDEF LINUX}
  Windows,
  Messages,
  Registry,
{$ENDIF}
  SysUtils,
  Classes,
  NativeXml,
  ZaphodsMap,
  ZaphodsMapMessages,
  ZaphodsMapSetupMessages;

{$WARNINGS OFF}   { set to OFF in final version}

const
  cZMGlobalXsdURL = cZMDesignURL + '/xsd/ZMGlobalSettings200605.xsd';
  cZMContextXsdURL = cZMDesignURL + '/xsd/ZMContext200605.xsd';
  cZMKeyboxXsdURL = cZMDesignURL + '/xsd/ZMKeybox200606.xsd';
  cBranchContextFile = 'Branch-Context-File';
  cLocalContextFile = 'Local-Context-File';
  cGlobalSettingsFile = 'Global-Settings-File';

type
  TKeyboxLocation = (klBranch, klLocal);
  TContextLocation = (clBranch, clLocal);

  TZaphodsMapSetup = class(TComponent)
  private
    FLastError: string;
    FZaphodsMap: TZaphodsMap;
    FLocalKeyboxPathAndFileName: string;
    FLocalKeyboxPath: string;
    FLocalKeyboxPrefix: string;

    function CreateXmlFile(
                      const AFileName, RequiredRootName: string;
                      const S: string;
                      out ErrorMessage: string): Boolean;

    function DeleteBranchInternal(ZaphodBranch: string;
      const DeleteWholeBranch: Boolean): Boolean;

    function DeleteKeyboxFile(
                      const KeyboxLocation: TKeyboxLocation): Boolean;

    function GetBranchKeyboxAsKeyedFile(
                      out ErrorMessage: string): TZaphodXmlDoc;

    function GetLocalKeyboxAsKeyedFile(
                      out ErrorMessage: string): TZaphodXmlDoc;

    function GetKeyboxAsKeyedFile(
                      const KeyboxLocation: TKeyboxLocation;
                      out ErrorMessage: string): TZaphodXmlDoc;

    function GetZaphodBranch: string;

    function InternalDeleteKey(
                      const KeyboxLocation: TKeyboxLocation;
                      const AKeyGroupName: string;
                      const AKeyType: TZaphodKeyType;
                      const AKeyName, AContext, AUserName: string;
                      const Method: string): Boolean;

    function InternalSaveKey(
                      const KeyboxLocation: TKeyboxLocation;
                      const AKeyGroupName: string;
                      const AKeyType: TZaphodKeyType;
                      const AKeyName, AContext, AUserName: string;
                      const AValue: string;
                      const Method: string): Boolean;

    function GetKeyboxKeyGroupCount(
                      const KeyboxLocation: TKeyboxLocation;
                      out ErrorMessage: string): Integer;

    function GetBranchKeyboxExists: Boolean;

    function GetBranchKeyboxKeyGroupCount: Integer;

    function GetLocalKeyboxExists: Boolean;

    function GetLocalKeyboxKeyGroupCount: Integer;

    function GetZaphodBranchCount: Integer;

    function GetZaphodsMapEnvVarExists: Boolean;

    function GetZaphodsMapRootFolder: string;

    procedure SetLocalKeyboxPath(Value: string);

    procedure SetLocalKeyboxPrefix(const Value: string);

    procedure SetZaphodBranch(const Value: string);

  protected
    procedure ResetMap;

  public
    constructor Create(AOwner: TComponent); override;

    class function CreateForBranch(AOwner: TComponent;
      const AZaphodBranch: string): TZaphodsMapSetup;

    class function CreateForService(
                      AOwner: TComponent;
                      const AZaphodBranch: string = '';
                      const ALocalKeyboxPrefix: string = ''): TZaphodsMapSetup;

    destructor Destroy; override;

    function CreateZMBranch(ZaphodBranch: string): Boolean;

    function CreateZMContextFile(const Location: TContextLocation;
      const DefaultContext: string = ''): Boolean;

    function CreateZMGlobalFile(const DefaultContext: string = ''): Boolean;

    function CreateZMKeybox(const KeyboxLocation: TKeyboxLocation): Boolean;

    function DeleteZMBranch(ZaphodBranch: string): Boolean;

    function DeleteZMContextFile(
                       const Location: TContextLocation): Boolean;

    function DeleteZMGlobalFile: Boolean; {only if no branches}


    function DeleteZMKeybox(
                       const KeyboxLocation: TKeyboxLocation): Boolean;

    function DeleteZMSubBranch(ZaphodBranch: string): Boolean;

    function SaveKey(  const KeyboxLocation: TKeyboxLocation;
                       const KeyGroupName: string;
                       const KeyType: TZaphodKeyType;
                       const KeyName: string;
                       const KeyValue: string): Boolean;

    function SaveKeyEx(const KeyboxLocation: TKeyboxLocation;
                       const KeyGroupName: string;
                       const KeyType: TZaphodKeyType;
                       const KeyName, Context, UserName: string;
                       const KeyValue: string): Boolean;

    function DeleteKey(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName: string;
                      const KeyType: TZaphodKeyType;
                      const KeyName: string): Boolean;

    function DeleteKeyEx(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName: string;
                      const KeyType: TZaphodKeyType;
                      const KeyName: string;
                      const Context: string = '';
                      const UserName: string = ''): Boolean;

    function SaveKeyboxVarDelimiters(
                      const KeyboxLocation: TKeyboxLocation;
                      const BeginDelimiter: string = '{';
                      const EndDelimiter: string = '}'): Boolean;

    function SaveKeyboxVar(
                       const KeyboxLocation: TKeyboxLocation;
                       const VarName, Value: string): Boolean;

    function SaveKeyGroupVar(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName, VarName, Value: string): Boolean;

    function DeleteKeyboxVar(
                      const KeyboxLocation: TKeyboxLocation;
                      const VarName: string): Boolean;

    function SaveKeyGroup(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName: string): Boolean;

    function DeleteKeyGroup(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName: string;
                      const RequireEmpty: Boolean): Boolean;

    function DeleteKeyGroupVar(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName, VarName: string): Boolean;

    function GetKeyboxVarByName(
                      const KeyboxLocation: TKeyboxLocation;
                      const AVarName: string;
                      var AVarValue: string): Boolean;

    function GetKeyboxVarDelimiters(
                      const KeyboxLocation: TKeyboxLocation;
                      var BeginDelimiter, EndDelimiter: string): Boolean;

    function GetKeyboxGroupVarByName(
                      const KeyboxLocation: TKeyboxLocation;
                      const KeyGroupName, AVarName: string;
                      var AVarValue: string): Boolean;

    function ZaphodsMapEnvVarCreate(
                      const ZaphodsMapRootFolder: string): Boolean;

    property ZaphodBranch: string
                      read GetZaphodBranch
                      write SetZaphodBranch;

    property BranchKeyboxExists: Boolean
                      read GetBranchKeyboxExists;

    property BranchKeyboxKeyGroupCount: Integer
                      read GetBranchKeyboxKeyGroupCount;

    property LocalKeyboxExists: Boolean
                      read GetLocalKeyboxExists;

    property LocalKeyboxKeyGroupCount: Integer
                      read GetLocalKeyboxKeyGroupCount;

    property LocalKeyboxPathAndFileName: string
                      read FLocalKeyboxPathAndFileName;

    property LocalKeyboxPath: string
                      read FLocalKeyboxPath
                      write SetLocalKeyboxPath;

    property LocalKeyboxPrefix: string
                      read FLocalKeyboxPrefix
                      write SetLocalKeyboxPrefix;

    property LastError: string
                      read FLastError;

    property ZaphodBranchCount: Integer
                      read GetZaphodBranchCount;

    property ZaphodsMapEnvVarExists: Boolean
      read GetZaphodsMapEnvVarExists;

    property ZaphodsMapRootFolder: string
      read GetZaphodsMapRootFolder;

  end;

  function DirectoryCount(const Path: string): Integer;

  function FileAndDirectoryCount(const Path: string): Integer;

implementation

uses
  Variants;

{ Helper functions }

function cpDeleteFileName(const FileName: string;
  out ErrorMessage: string): Boolean;
begin
  ErrorMessage := '';
{$IFDEF CLR}
  try
    System.IO.File.Delete(FileName);
    Result := not System.IO.File.Exists(FileName);
  except
    on E: Exception do
    begin
      ErrorMessage := E.Message;
      Result := False;
    end;
  end;
{$ELSE}
  Result := DeleteFile(FileName);
  if not Result then
    ErrorMessage := SysErrorMessage(GetLastError);
{$ENDIF}
end;


{$IFDEF LINUX}

function SetZaphodsMapEnvVar(const Value: string): Boolean;
begin
  { Environment global variables setting technique in Linux differs greatly
    from Windows. It is required to associate variable name with some
    system name (for example ZaphodsMap) that variable belongs to.}
  Result := False;
end;

{$ELSE}

function SetGlobalEnvironment(const Name, Value: string;
  const User: Boolean = True): Boolean;
const
  KeyName = 'Environment';
  REG_MACHINE_LOCATION = 'System\CurrentControlSet\Control\Session Manager\' +
    KeyName;
  REG_USER_LOCATION = KeyName;
{resourcestring
  REG_MACHINE_LOCATION = 'System\CurrentControlSet\Control\Session Manager\'
    + 'Environment';
  REG_USER_LOCATION = 'Environment';}
var
{$IFDEF CLR}
  NamePtr: IntPtr;
{$ENDIF}
  AResult: DWORD;
begin
  with TRegistry.Create do
  try
    if User then
    begin
      { User Environment Variable }
      Result := OpenKey(REG_USER_LOCATION, True);
    end
    else
    begin
      { System Environment Variable }
      RootKey := HKEY_LOCAL_MACHINE;
      Result  := OpenKey(REG_MACHINE_LOCATION, True);
    end;
    if Result then
    begin
      { Write Registry for Global Environment }
      WriteString(Name, Value);
      { Update Current Process Environment Variable }
    {$IFDEF CLR}
      SetEnvironmentVariable(Name, Value);
    {$ELSE}
      SetEnvironmentVariable(PChar(Name), PChar(Value));
    {$ENDIF}
      { Send Message To All Top Windows for Refresh }
    {$IFDEF CLR}
      NamePtr := Marshal.StringToHGlobalAuto(KeyName);
      SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
        WPARAM(NamePtr), SMTO_NORMAL, 1000, AResult);
      Marshal.FreeHGlobal(NamePtr)
    {$ELSE}
      SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
        WPARAM(PChar(KeyName)), SMTO_NORMAL, 1000, AResult);
    {$ENDIF}
    end;
  finally
    Free;
  end;
end;

{$ENDIF}


function cpSetZaphodsMapEnvVar(const Value: string;
  out ErrorMessage: string): Boolean;
begin
{$IFDEF LINUX}
  Result := SetZaphodsMapEnvVar(Value);
{$ELSE}
  Result := SetGlobalEnvironment('ZaphodsMap', Value, False);
{$ENDIF}
  if Result then
    ErrorMessage := ''
  else
    ErrorMessage := SysErrorMessage(GetLastError);
end;


function DirectoryCount(const Path: string): Integer;
var
  F: TSearchRec;
begin
  Result := 0;
  try
    {note: FindFirst allocates resources (memory) which must be released by
      calling FindClose.}
    if FindFirst(IncludeTrailingPathDelimiter(Path) + '*', faAnyFile, F) = 0
      then
    repeat
      if (F.Attr and faDirectory <> 0) and
        (F.Name <> '.') and (F.Name <> '..') then
          Inc(Result);
    until FindNext(F) <> 0;
  finally
    FindClose(F);
  end;
end;


function FileAndDirectoryCount(const Path: string): Integer;
var
  F: TSearchRec;
begin
  Result := 0;
  try
    if FindFirst(IncludeTrailingPathDelimiter(Path) + '*', faAnyFile, F) = 0
      then
    repeat
      if (F.Name <> '.') and (F.Name <> '..') then
        Inc(Result);
    until FindNext(F) <> 0;
  finally
    FindClose(F);
  end;
end;


function KeyboxAsString(const KeyboxLocation: TKeyboxLocation): string;
const
  KeyboxNames: array[TKeyboxLocation] of string = 
    ('BranchKeybox', 'LocalKeybox');
begin
  Result := KeyboxNames[KeyboxLocation];
end;


function KeyboxError(const KeyboxLocation: TKeyboxLocation;
  const Method, ErrorMessage: string): string;
begin
  Result := KeyboxAsString(KeyboxLocation) + ' ' + Method + ' ' + szmError
    + ': ' + ErrorMessage;
end;


function NormalizeZaphodBranch(const Value: string): string;
begin
  Result := ExcludeTrailingPathDelimiter(Value);
  if IsPathDelimiter(Result, 1) then
    Result := Copy(Result, 2, MaxInt);
end;


procedure SplitPath(const Path: string; var PathLeft, Folder: string);
var
  I: Integer;
begin
  if Path <> '' then
  begin
    for I := Length(Path) downto 1 do
      if Path[I] = PathDelim then
        Break;
    if Path[I] = PathDelim then
    begin
      PathLeft := Copy(Path, 1, I-1);
      Folder := Copy(Path, I+1, MaxInt);
    end
    else
    begin
      PathLeft := Path;
      Folder := '';
    end;
  end
  else
  begin
    PathLeft := '';
    Folder := '';
  end;
end;


{ TZaphodsMapSetup - private methods }

function TZaphodsMapSetup.CreateXmlFile(const AFileName,
  RequiredRootName: string; const S: string; out ErrorMessage: string): Boolean;
var
  AKey: TZaphodKey;
begin
  Result := False;
  FLastError := '';
  with FZaphodsMap do
  begin
    AKey := ActivateTempKey(AFileName, RequiredRootName);
    if AKey <> nil then
    begin
      Result := AKey.CreateKeyedFileFromString(S, FLastError);
      DeactivateKey(AKey.KeyIndex);
    end
    else
      FLastError := KeyLastError;
  end;
  ErrorMessage := FLastError;
end;


function TZaphodsMapSetup.DeleteBranchInternal(ZaphodBranch: string;
  const DeleteWholeBranch: Boolean): Boolean;
var
  Path: string;
  Folder: string;
  FolderToDelete: string;
  Done: Boolean;
  KeepFolderLength: Integer;
  I: Integer;
  Reason: string;
begin
  Result := False;
  FLastError := '';
  if (ZaphodBranch = '') or (ZaphodBranch = PathDelim) then
  begin
    FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
      szmZMBranchBlank]);
    Exit;
  end;
  ZaphodBranch := NormalizeZaphodBranch(ZaphodBranch);
  KeepFolderLength := Length(ZaphodsMapRootFolder) - 1;
  if not DeleteWholeBranch then
  begin
    {preserve path up to last PathDelim}
    for I := Length(ZaphodBranch) downto 1 do
      if ZaphodBranch[I] = PathDelim then
        Break;
    if I > 1 then
      KeepFolderLength := KeepFolderLength + I;
  end;

  Path := ExcludeTrailingPathDelimiter(FZaphodsMap.ZaphodsMapRootFolder
    + ZaphodBranch);
  if (not ZaphodsMapEnvVarExists) then
    FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
      szmZaphodsMapEnvNotFound])
  else
  if (Length(FZaphodsMap.ZaphodsMapRootFolder) < 4) then
    FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
      szmZMBranchPathTooShort])
  else
  if not SysUtils.DirectoryExists(Path) then
    FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
      szmZaphodsBranchNotFound])
  else
  begin
    Done := False;
    repeat
      SplitPath(Path, Path, Folder);
      if Folder <> '' then
      begin
        FolderToDelete := Path + PathDelim + Folder;
        if SameText(FolderToDelete, ExcludeTrailingPathDelimiter(
          FZaphodsMap.ZaphodsMapRootFolder)) then
        begin
          FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
            'Unexpected Error: Branch is ZaphodsMap Root Folder']);
          Done := True;
        end
        else
        begin
          if (Length(Path) >= KeepFolderLength) then
          begin
            if not RemoveDir(FolderToDelete) then
            begin
              I := FileAndDirectoryCount(FolderToDelete);
              if I > 0 then
                Reason := Format(szmFolderNotEmpty, [FolderToDelete, I])
              else
                Reason := Format('system error when trying to delete "%s"',
                  [FolderToDelete]);
              FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
                Reason]);
            end;
          end;
        end;
      end
      else
      begin
        FLastError := Format(szmUnableToDeleteBranch, [ZaphodBranch,
          'Unknown Error']);
        Done := True;
      end;
      if (Length(Path) <= KeepFolderLength) or (FLastError <> '') then
        Done := True;
    until Done;
  end;
  if FLastError = '' then
    Result := True;
end;


function TZaphodsMapSetup.DeleteKeyboxFile(
  const KeyboxLocation: TKeyboxLocation): Boolean;
var
  KeyboxFileName: string;
  KeyboxKeyGroupCount: Integer;
  ErrorMessage: string;
begin
  Result := False;
  FLastError := '';
  if (KeyboxLocation = klLocal) and (FLocalKeyboxPath = '') then
  begin
    FLastError := Format(szmUnableToDeleteXmlFile, [KeyboxAsString(
      KeyboxLocation), '', szmLocalKeyboxPathNotSpecified]);
    Exit;
  end
  else
  if (KeyboxLocation = klBranch) then
  begin
    if FZaphodsMap.ZaphodBranch = '' then
      FLastError := Format(szmUnableToDeleteXmlFile,
        [KeyboxAsString(KeyboxLocation), cZMBranchKeyboxFileName,
        szmZaphodBranchNotSpecified])
    else
    if not ZaphodsMapEnvVarExists then
      FLastError := Format(szmUnableToCreateXmlFile,
        [KeyboxAsString(KeyboxLocation), FZaphodsMap.ZaphodBranch
        + cZMBranchKeyboxFileName, szmZaphodsMapEnvNotFound]);
    if FLastError <> '' then
     Exit;
  end;

  FZaphodsMap.Reset;
  if KeyboxLocation = klBranch then
  begin
    KeyboxFileName := FZaphodsMap.BranchKeybox;
    KeyboxKeyGroupCount := BranchKeyboxKeyGroupCount;
  end
  else
  begin
    KeyboxFileName := LocalKeyboxPathAndFileName;
    KeyboxKeyGroupCount := LocalKeyboxKeyGroupCount;
  end;

  {delete only allowed if no keygroups exist}
  if KeyboxKeyGroupCount > 0 then
    FLastError := Format(szmUnableToDeleteXmlFile, [KeyboxAsString(
      KeyboxLocation), KeyboxFileName, szmAtLeastOneKeyGroupExists])
  else
  begin
    if FileExists(KeyboxFileName) then
    begin
      if cpDeleteFileName(KeyboxFileName, ErrorMessage) then
        Result := True
      else
        FLastError := Format(szmUnableToDeleteXmlFile,
          [KeyboxAsString(KeyboxLocation), KeyboxFileName, ErrorMessage]);
    end
    else
      FLastError := Format(szmUnableToDeleteXmlFile,
        [KeyboxAsString(KeyboxLocation), KeyboxFileName, szmFileNotFound]);
  end;
end;


function TZaphodsMapSetup.GetBranchKeyboxAsKeyedFile(out ErrorMessage: string):
  TZaphodXmlDoc;
var
  TempKey: TZaphodKey;
begin
  ErrorMessage := '';
  Result := nil;
  with FZaphodsMap do
  begin
    Reset;
    if BranchKeyboxXmlOK then
    begin
      TempKey := ActivateTempKey(BranchKeybox, cZMKeyboxRootNodeName);
      if TempKey <> nil then
      begin
        with TempKey do
        begin
          if KeyedFile.LoadFile then
            Result := KeyedFile
          else
            ErrorMessage := KeyedFile.LastError;
        end;
      end
      else
        ErrorMessage := KeyLastError;
    end
    else
      ErrorMessage := BranchKeyboxWarnings;
  end;
end;


function TZaphodsMapSetup.GetLocalKeyboxAsKeyedFile(out ErrorMessage: string):
  TZaphodXmlDoc;
var
  TempKey: TZaphodKey;
begin
  ErrorMessage := '';
  Result := nil;
  if LocalKeyboxPathAndFileName <> '' then
  begin
    with FZaphodsMap do
    begin
      Reset;
      TempKey := ActivateTempKey(LocalKeyboxPathAndFileName,
        cZMKeyboxRootNodeName);
      if TempKey <> nil then
      begin
        with TempKey do
        begin
          if KeyedFile.LoadFile then
            Result := KeyedFile
          else
            ErrorMessage := KeyedFile.LastError;
        end;
      end
      else
        ErrorMessage := KeyLastError;
    end;
  end
  else
    ErrorMessage := szmLocalKeyboxPathNotSpecified;
end;


function TZaphodsMapSetup.GetKeyboxAsKeyedFile(
  const KeyboxLocation: TKeyboxLocation; out ErrorMessage: string):
  TZaphodXmlDoc;
begin
  if KeyboxLocation = klBranch then
    Result := GetBranchKeyboxAsKeyedFile(ErrorMessage)
  else
    Result := GetLocalKeyboxAsKeyedFile(ErrorMessage);
end;


function TZaphodsMapSetup.GetKeyboxKeyGroupCount(
  const KeyboxLocation: TKeyboxLocation;  out ErrorMessage: string): Integer;
var
  ANode: TZaphodNode;
  AList: TList;
  KeyedFile: TZaphodXmlDoc;
begin
  Result := -1;
  AList := nil;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    ANode := KeyedFile.ZNode(nil, ['KeyGroups']);
    if ANode <> nil then
    begin
      AList := TList.Create;
      ANode.FindNodes('KeyGroup', AList);
      Result := AList.Count;
      FreeAndNil(AList);
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxAsString(KeyboxLocation) + 'KeyGroupCount error: '
      + FLastError;
end;


function TZaphodsMapSetup.GetBranchKeyboxExists: Boolean;
begin
  FZaphodsMap.Reset;
  Result := FZaphodsMap.BranchKeyboxExists;
end;


function TZaphodsMapSetup.GetBranchKeyboxKeyGroupCount: Integer;
begin
  Result := GetKeyboxKeyGroupCount(klBranch, FLastError);
end;


function TZaphodsMapSetup.GetLocalKeyboxExists: Boolean;
begin
  if FLocalKeyboxPathAndFileName = '' then
    Result := False
  else
    Result := FileExists(FLocalKeyboxPathAndFileName);
end;


function TZaphodsMapSetup.GetLocalKeyboxKeyGroupCount: Integer;
begin
  Result := GetKeyboxKeyGroupCount(klLocal, FLastError);
end;


function TZaphodsMapSetup.GetZaphodBranchCount: Integer;
begin
  if ZaphodsMapEnvVarExists then
    Result := DirectoryCount(FZaphodsMap.ZaphodsMapRootFolder)
  else
    Result := -1;
end;


function TZaphodsMapSetup.GetZaphodsMapEnvVarExists: Boolean;
begin
  FZaphodsMap.Reset;
  Result := FZaphodsMap.ZaphodsMapEnvVarExists;
end;


function TZaphodsMapSetup.GetZaphodsMapRootFolder: string;
begin
  FZaphodsMap.Reset;
  Result := FZaphodsMap.ZaphodsMapRootFolder;
end;


procedure TZaphodsMapSetup.SetLocalKeyboxPath(Value: string);
begin
  if Value = '' then
  begin
    FLocalKeyboxPath := '';
    FLocalKeyboxPathAndFileName := '';
  end
  else
  begin
    Value := IncludeTrailingPathDelimiter(Value);
    with FZaphodsMap do
    begin
      if (ZaphodsMapRootFolder <> '') and
        SameText(Copy(Value, 1, Length(ZaphodsMapRootFolder)),
          ZaphodsMapRootFolder) then
      Raise Exception.Create(szmZMRootFolderNotAllowedInLocalPath);
    end;
    FLocalKeyboxPath := Value;
    FLocalKeyboxPathAndFileName := FLocalKeyboxPath + FLocalKeyboxPrefix +
      cZMLocalKeyboxSuffix;
  end;
end;


procedure TZaphodsMapSetup.SetLocalKeyboxPrefix(const Value: string);
begin
  FLocalKeyboxPrefix := Value;
  if FLocalKeyboxPath <> '' then
    FLocalKeyboxPathAndFileName := FLocalKeyboxPath + FLocalKeyboxPrefix +
      cZMLocalKeyboxSuffix;
end;

procedure TZaphodsMapSetup.SetZaphodBranch(const Value: string);
begin
  FZaphodsMap.ZaphodBranch := Value;
end;

function TZaphodsMapSetup.InternalSaveKey(const KeyboxLocation: TKeyboxLocation;
  const AKeyGroupName: string; const AKeyType: TZaphodKeyType;
  const AKeyName, AContext, AUserName: string;
  const AValue: string; const Method: string):Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  XPath: string;
  ANode: TZaphodNode;
  AttributeID: string;
  KeyID: string;
  FileReloaded: Boolean;
begin
  Result := False;
  GetZMKeyProperties(AKeyType, KeyID, AttributeID);

  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        XPath := XPathBuilder(['KeyGroups/KeyGroup', '@name', AKeyGroupName,
          '/Keys/'+ KeyID, '@name', AKeyName]);
        if AContext <> '' then
          XPath := XPathBuilder([XPath, '@context', AContext]);
        if AUserName <> '' then
          XPath := XPathBuilder([XPath, '@username', AUserName]);
        ANode := ZNode(nil, [XPath], cxNone, True);
        if ANode <> nil then
          ANode.AttributeByName[AttributeID] := AValue;
        Result := EndUpdate;
        if not Result then
          FLastError := KeyedFile.LastError;
      end
      else
        FLastError := KeyedFile.LastError;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, Method, FLastError);
end;


function TZaphodsMapSetup.GetZaphodBranch: string;
begin
  Result := FZaphodsMap.ZaphodBranch;
end;


function TZaphodsMapSetup.InternalDeleteKey(
  const KeyboxLocation: TKeyboxLocation; const AKeyGroupName: string;
  const AKeyType: TZaphodKeyType; const AKeyName, AContext, AUserName: string;
  const Method: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  XPath: string;
  ANode: TZaphodNode;
  AttributeID: string;
  KeyID: string;
  FileReloaded: Boolean;
begin
  Result := False;
  GetZMKeyProperties(AKeyType, KeyID, AttributeID);
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      try
        if BeginUpdate(FileReloaded) then
        begin
          XPath := XPathBuilder(['KeyGroups/KeyGroup', '@name', AKeyGroupName,
            '/Keys/' + KeyID, '@name', AKeyName]);
          if AContext <> '' then
            XPath := XPathBuilder([XPath, '@context', AContext]);
          if AUserName <> '' then
            XPath := XPathBuilder([XPath, '@username', AUserName]);
          ANode := ZNode(nil, [XPath]);
          if ANode <> nil then
            ANode.Delete;
          Result := EndUpdate;
          if not Result then
            FLastError := KeyedFile.LastError;
        end
        else
          FLastError := KeyedFile.LastError;
      except
        on E:Exception do
        begin
          FLastError := E.Message;
        end;
      end;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, Method, FLastError);
end;

{ TZaphodsMapSetup - protected methods }


(*function TZaphodsMapSetup.GetAsKeyedFile(const FileName: string;
  const RootNodeName: string; out ErrorMessage: string): TZaphodXmlDocument;
var
  TempKey: TZaphodKey;
begin
  ErrorMessage := '';
  Result := nil;
  if FileExists(FileName) then
  begin
    with FZaphodsMap do
    begin
      Reset;
      TempKey := ActivateTempKey(FileName, RootNodeName);
      if TempKey <> nil then
      begin
        with TempKey do
        begin
          if KeyedFile.LoadFile then
            Result := KeyedFile
          else
            ErrorMessage := KeyedFile.LastError;
        end;
      end
      else
        ErrorMessage := KeyLastError;
    end;
  end
  else
    ErrorMessage := Format(szmNamedFileNotFound, [FileName]);
end;*)

procedure TZaphodsMapSetup.ResetMap;
begin
  FZaphodsMap.Reset;
end;


{ TZaphodsMapSetup - public methods }

constructor TZaphodsMapSetup.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FZaphodsMap := nil;
  FZaphodsMap := TZaphodsMap.Create(self);
  FZaphodsMap.ErrorDetailAllowed :=
   [edFileNames, edFilePathsAndNames, edComponentNames, edMethodNames];
  FLocalKeyboxPath := '';
  FLocalKeyboxPrefix := '';
  FLocalKeyboxPathAndFileName := '';
end;


class function TZaphodsMapSetup.CreateForBranch(AOwner: TComponent;
  const AZaphodBranch: string): TZaphodsMapSetup;
begin
  Result := Create(AOwner);
  Result.FZaphodsMap.ZaphodBranch := AZaphodBranch;
end;


class function TZaphodsMapSetup.CreateForService(AOwner: TComponent;
  const AZaphodBranch: string = '';
  const ALocalKeyboxPrefix: string = ''): TZaphodsMapSetup;
begin
  Result := CreateForBranch(AOwner, AZaphodBranch);
  Result.LocalKeyboxPrefix := ALocalKeyboxPrefix;
end;


destructor TZaphodsMapSetup.Destroy;
begin
  FreeAndNil(FZaphodsMap);
  inherited;
end;


function TZaphodsMapSetup.CreateZMContextFile(const Location: TContextLocation;
  const DefaultContext: string = ''): Boolean;
var
  S: string;
  FileName: string;
begin
  Result := False;
  FLastError := '';
  S := ZaphodMinimalXMLDoc(cZMXslURL, cZMContextRootNodeName, cZMContextXsdURL,
    '    <DefaultContext value="' + DefaultContext + '" />');
  case Location of
    clBranch:
      begin
        if FZaphodsMap.ZaphodBranch = '' then
          FLastError := Format(szmUnableToCreateXmlFile,
            [cBranchContextFile, cZMContextFileName,
            szmZaphodBranchNotSpecified])
        else
        if not ZaphodsMapEnvVarExists then
          FLastError := Format(szmUnableToCreateXmlFile,
            [cBranchContextFile,FZaphodsMap.ZaphodBranch
            + cZMContextFileName, szmZaphodsMapEnvNotFound])
        else
        with FZaphodsMap do
        begin
          Reset;
          FileName := ExtractFilePath(BranchKeybox) + cZMContextFileName;
          if FileExists(FileName) then
            FLastError := Format(szmUnableToCreateXmlFile,
              [cBranchContextFile, FileName, szmFileAlreadyExists])
          else
          begin
            Result := CreateXmlFile(FileName, cZMContextRootNodeName, S,
              FLastError);
            if not Result then
              FLastError := Format(szmUnableToCreateXmlFile,
                [cBranchContextFile, FileName, FLastError])
          end;
        end;
      end;
    clLocal:
      begin
        if FLocalKeyboxPath <> '' then
        begin
          FileName := FLocalKeyboxPath + cZMContextFileName;
          if FileExists(FileName) then
              FLastError := Format(szmUnableToCreateXmlFile, [cLocalContextFile,
                FileName, szmFileAlreadyExists])
          else
          begin
            Result := CreateXmlFile(FileName, cZMContextRootNodeName, S,
              FLastError);
            if not Result then
              FLastError := Format(szmUnableToCreateXmlFile, [cLocalContextFile,
                FileName, FLastError])
          end;
        end
        else
        begin
          FLastError := Format(szmUnableToCreateXmlFile, [cLocalContextFile,
            cZMContextFileName, szmLocalKeyboxPathNotSpecified])
        end;
      end;
  end;
end;


function TZaphodsMapSetup.CreateZMGlobalFile(
  const DefaultContext: string): Boolean;
var
  S: string;
  FileName: string;
begin
  Result := False;
  FLastError := '';
  S := ZaphodMinimalXMLDoc(cZMXslURL, cZMGlobalRootNodeName, cZMGlobalXsdURL,
    '    <DefaultContext value="' + DefaultContext + '" />');
  if ZaphodsMapEnvVarExists then
  begin
    with FZaphodsMap do
    begin
      FileName := ZaphodsMapRootFolder + cZMGlobalFileName;
    end;
    if FileExists(FileName) then
      FLastError := Format(szmUnableToCreateXmlFile,
        [cGlobalSettingsFile, FileName, szmFileAlreadyExists])
    else
    begin
      Result := CreateXmlFile(FileName, cZMGlobalRootNodeName, S, FLastError);
      if not Result then
        FLastError := Format(szmUnableToCreateXmlFile, [cGlobalSettingsFile,
          FileName, FLastError]);
    end;
  end
  else
    FLastError := Format(szmUnableToCreateXmlFile,[cGlobalSettingsFile,
      cZMGlobalFileName, szmZaphodsMapEnvNotFound]);
end;


function TZaphodsMapSetup.CreateZMKeybox(const KeyboxLocation: TKeyboxLocation):
  Boolean;
var
  S: string;
  FileName: string;
begin
  Result := False;
  FLastError := '';
  S := ZaphodMinimalXMLDoc(cZMXslURL, cZMKeyboxRootNodeName, cZMKeyboxXsdURL,
    '    <KeyGroups />');
  case KeyboxLocation of
    klBranch:
      begin
        if FZaphodsMap.ZaphodBranch = '' then
          FLastError := Format(szmUnableToCreateXmlFile,
            [KeyboxAsString(KeyboxLocation), cZMBranchKeyboxFileName,
            szmZaphodBranchNotSpecified])
        else
        if not ZaphodsMapEnvVarExists then
          FLastError := Format(szmUnableToCreateXmlFile,
            [KeyboxAsString(KeyboxLocation), FZaphodsMap.ZaphodBranch
            + cZMBranchKeyboxFileName, szmZaphodsMapEnvNotFound])
        else
        with FZaphodsMap do
        begin
          Reset;
          FileName := BranchKeybox;
          if FZaphodsMap.BranchKeyboxExists then
            FLastError := Format(szmUnableToCreateXmlFile,
              [KeyboxAsString(KeyboxLocation), FileName, szmFileAlreadyExists])
          else
          begin
            Result := CreateXmlFile(FileName, cZMKeyboxRootNodeName, S,
              FLastError);
            if not Result then
              FLastError := Format(szmUnableToCreateXmlFile,
                [KeyboxAsString(KeyboxLocation), FileName, FLastError])
          end;
        end;
      end;
    klLocal:
      begin
        FileName := FLocalKeyboxPathAndFileName;
        if (FileName <> '') then
        begin
          if FileExists(FileName) then
              FLastError := Format(szmUnableToCreateXmlFile,
                [KeyboxAsString(KeyboxLocation), FileName,
                szmFileAlreadyExists])
          else
          begin
            Result := CreateXmlFile(FileName, cZMKeyboxRootNodeName, S,
              FLastError);
            if not Result then
              FLastError := Format(szmUnableToCreateXmlFile,
                [KeyboxAsString(KeyboxLocation), FileName, FLastError])
          end;
        end
        else
        begin
          FLastError := Format(szmUnableToCreateXmlFile,
            [KeyboxAsString(KeyboxLocation), FileName,
            szmLocalKeyboxPathNotSpecified]);
        end;
      end;
  end;
end;


function TZaphodsMapSetup.CreateZMBranch(ZaphodBranch: string): Boolean;
var
  Path: string;
  ErrorMessage: string;
begin
  Result := False;
  FLastError := '';
  if (ZaphodBranch = '') or (ZaphodBranch = PathDelim) then
  begin
    FLastError := Format(szmUnableToCreateBranch,[ZaphodBranch,
      szmZMBranchBlank]);
    Exit;
  end;
  ZaphodBranch := NormalizeZaphodBranch(ZaphodBranch);
  if (not ZaphodsMapEnvVarExists) then
    FLastError := Format(szmUnableToCreateBranch, [ZaphodBranch,
      szmZaphodsMapEnvNotFound])
  else
  if (Length(FZaphodsMap.ZaphodsMapRootFolder) < 4) then
    FLastError := Format(szmUnableToCreateBranch, [ZaphodBranch,
      szmZMBranchPathTooShort])
  else
  begin
    Path := ExcludeTrailingPathDelimiter(FZaphodsMap.ZaphodsMapRootFolder
      + ZaphodBranch);
    if SysUtils.DirectoryExists(Path) then
      FLastError := Format(szmUnableToCreateBranch, [ZaphodBranch,
        szmZaphodsBranchAlreadyExists])
    else
    begin
      Result := ZaphodAssertDirectoryFor(Path + PathDelim, ErrorMessage);
      if not Result then
        FLastError := Format(szmUnableToCreateBranch, [ZaphodBranch,
          ErrorMessage]);
    end;
  end;
end;

function TZaphodsMapSetup.DeleteZMBranch(ZaphodBranch: string): Boolean;
begin
  Result := DeleteBranchInternal(ZaphodBranch, False);
end;




function TZaphodsMapSetup.DeleteZMContextFile(
  const Location: TContextLocation): Boolean;
var
  FileName: string;
  FileDesc: string;
  ErrorMessage: string;
begin
  Result := False;
  FLastError := '';
  if (Location = clLocal) and (FLocalKeyboxPath = '') then
  begin
    FLastError := Format(szmUnableToDeleteXmlFile, [cLocalContextFile,
      cZMContextFileName, szmLocalKeyboxPathNotSpecified]);
    Exit;
  end
  else
  if (Location = clBranch) then
  begin
    if FZaphodsMap.ZaphodBranch = '' then
      FLastError := Format(szmUnableToDeleteXmlFile,
        [cBranchContextFile, cZMContextFileName,
        szmZaphodBranchNotSpecified])
    else
    if not ZaphodsMapEnvVarExists then
      FLastError := Format(szmUnableToCreateXmlFile,
        [cBranchContextFile,FZaphodsMap.ZaphodBranch
        + cZMContextFileName, szmZaphodsMapEnvNotFound]);
    if FLastError <> '' then
     Exit;
  end;
  FZaphodsMap.Reset;
  if Location = clBranch then
  begin
    FileName := ExtractFilePath(FZaphodsMap.BranchKeybox) + cZMContextFileName;
    FileDesc := cBranchContextFile;
  end
  else
  begin
    FileName := LocalKeyboxPath + cZMContextFileName;
    FileDesc := cLocalContextFile;
  end;

  if FileExists(FileName) then
  begin
    if cpDeleteFileName(FileName, ErrorMessage) then
      Result := True
    else
      FLastError := Format(szmUnableToDeleteXmlFile, [FileDesc, FileName,
        ErrorMessage]);
  end
  else
    FLastError := Format(szmUnableToDeleteXmlFile, [FileDesc, FileName,
      szmFileNotFound]);
end;


function TZaphodsMapSetup.DeleteZMGlobalFile: Boolean; {only if no branches}
var
  FileName: string;
  ErrorMessage: string;
begin
  Result := False;
  FZaphodsMap.Reset;
  if FZaphodsMap.ZaphodsMapRootFolder <> '' then
  begin
    FileName := FZaphodsMap.ZaphodsMapRootFolder + cZMGlobalFileName;
    if FileExists(FileName) then
    begin
      {ensure no Zaphod Branches exist}
      if ZaphodBranchCount = 0 then
      begin
        if cpDeleteFileName(FileName, ErrorMessage) then
          Result := True
        else
          FLastError := Format(szmUnableToDeleteXmlFile,
            [cGlobalSettingsFile, FileName, ErrorMessage]);
      end
      else
        FLastError := Format(szmUnableToDeleteXmlFile,
          [cGlobalSettingsFile, FileName, Format(szmZaphodBranchesExist,
            [ZaphodBranchCount])]);
    end
    else
      FLastError := Format(szmUnableToDeleteXmlFile, [cGlobalSettingsFile,
        FileName, szmFileNotFound]);
  end
  else
    FLastError := Format(szmUnableToDeleteXmlFile,
      [cGlobalSettingsFile, cZMGlobalFileName, szmZaphodsMapEnvNotFound]);
end;


function TZaphodsMapSetup.DeleteZMKeybox(const KeyboxLocation:
  TKeyboxLocation): Boolean;
begin
  Result := DeleteKeyboxFile(KeyboxLocation);
end;

function TZaphodsMapSetup.DeleteZMSubBranch(ZaphodBranch: string): Boolean;
begin
  Result := DeleteBranchInternal(ZaphodBranch, False);
end;


function TZaphodsMapSetup.SaveKey(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName: string; const KeyType: TZaphodKeyType;
  const KeyName: string; const KeyValue: string): Boolean;
begin
  Result := InternalSaveKey(KeyboxLocation, KeyGroupName, KeyType, KeyName, '',
    '', KeyValue, 'SaveKey');
end;


function TZaphodsMapSetup.SaveKeyEx(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName: string; const KeyType: TZaphodKeyType;
  const KeyName, Context, UserName: string;
  const KeyValue: string): Boolean;
begin
  Result := InternalSaveKey(KeyboxLocation, KeyGroupName, KeyType, KeyName,
    Context, UserName, KeyValue, 'SaveKeyEx');
end;


function TZaphodsMapSetup.DeleteKey(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName:string; const KeyType: TZaphodKeyType;
  const KeyName: string): Boolean;
begin
  Result := InternalDeleteKey(KeyboxLocation, KeyGroupName, KeyType, KeyName,
    '', '', 'DeleteKey');
end;


function TZaphodsMapSetup.DeleteKeyEx(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName: string;  const KeyType: TZaphodKeyType;
  const KeyName: string; const Context: string = '';
  const UserName: string = ''): Boolean;
begin
  Result := InternalDeleteKey(KeyboxLocation, KeyGroupName, KeyType, KeyName,
    Context, UserName, 'DeleteKeyEx');
end;


function TZaphodsMapSetup.SaveKeyboxVarDelimiters(const KeyboxLocation:
  TKeyboxLocation;
  const BeginDelimiter: string = '{';
  const EndDelimiter: string = '}'): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  ANode: TZaphodNode;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if KeyboxLocation = klBranch then
      begin
        ANode := ZNode(nil, ['Variables']);
        if (ANode <> nil) and (ANode.AttributeByName['begin'] <> '')
          and (ANode.AttributeByName['end'] <> '') then
            FLastError := szmVarDelimitersTagAlreadyExists;
      end;
      if FLastError = '' then
      begin
        if BeginUpdate(FileReloaded) then
        begin
          Result := SaveVarDelimiters(BeginDelimiter, EndDelimiter);
          if Result then
          begin
            Result := EndUpdate;
            if not Result then
              FLastError := KeyedFile.LastError;
          end
          else
          begin
            FLastError := KeyedFile.LastError;
            KeyedFile.CancelUpdate;
          end;
        end
        else
          FLastError := KeyedFile.LastError;
      end;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
  begin
    FLastError := KeyboxError(KeyboxLocation, 'SaveKeyboxVarDelimiters',
      FLastError);
  end;
end;


function TZaphodsMapSetup.SaveKeyboxVar(const KeyboxLocation: TKeyboxLocation;
  const VarName, Value: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        Result := SaveVar(nil, VarName, Value);
        if Result then
        begin
          Result := EndUpdate;
          if not Result then
            FLastError := KeyedFile.LastError;
        end
        else
        begin
          FLastError := KeyedFile.LastError;
          CancelUpdate;
        end;
      end
      else
        FLastError := KeyboxError(KeyboxLocation, 'SaveKeyboxVar',
          KeyedFile.LastError);
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'SaveKeyboxVar', FLastError);
end;


function TZaphodsMapSetup.SaveKeyGroupVar(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName, VarName, Value: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  ANode: TZaphodNode;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        ANode := ZNode(nil, ['KeyGroups/KeyGroup', '@name',
          KeyGroupName], cxNone, True);
        if ANode <> nil then
          Result := SaveVar(ANode, VarName, Value);
        if Result then
        begin
          Result := EndUpdate;
          if not Result then
            FLastError := KeyedFile.LastError;
        end
        else
        begin
          FLastError := KeyedFile.LastError;
          CancelUpdate;
        end;
      end
      else
        FLastError := KeyedFile.LastError;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'SaveKeyGroupVar', FLastError);
end;


function TZaphodsMapSetup.DeleteKeyboxVar(const KeyboxLocation: TKeyboxLocation;
  const VarName: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        Result := DeleteVar(nil, VarName);
        if Result then
        begin
          Result := EndUpdate;
          if not Result then
            FLastError := KeyedFile.LastError;
        end
        else
        begin
          FLastError := KeyedFile.LastError;
          CancelUpdate;
        end;
      end;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'DeleteKeyboxVar', FLastError);
end;


function TZaphodsMapSetup.SaveKeyGroup(const KeyboxLocation: TKeyboxLocation;
  const KeyGroupName: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  XPath: string;
  ANode: TZaphodNode;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        XPath := XPathBuilder(['KeyGroups/KeyGroup', '@name', KeyGroupName]);
        ANode := ZNode(nil, [XPath], cxNone, True);
        if ANode = nil then
          FLastError := KeyedFile.LastError;
        Result := EndUpdate;
        if not Result then
          FLastError := KeyedFile.LastError;
      end
      else
        FLastError := KeyedFile.LastError;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'SaveKeyGroup', FLastError);
end;


function TZaphodsMapSetup.DeleteKeyGroup(
  const KeyboxLocation: TKeyboxLocation; const KeyGroupName: string;
  const RequireEmpty: Boolean): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  KeyGroupNode: TZaphodNode;
  ANode: TZaphodNode;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        KeyGroupNode := ZNode(nil, ['KeyGroups/KeyGroup', '@name',
          KeyGroupName]);
        if KeyGroupNode <> nil then
        begin
          if RequireEmpty then
          begin
            ANode := KeyGroupNode.NodeByName('Keys');
            if (ANode <> nil) and (ANode.NodeCount > 0) then
              FLastError := Format(szmCannotDeleteGroupAsKeysInUse,
                [KeyGroupName, XPathOfNode(KeyGroupNode)]);
          end;
          if FLastError = '' then
          begin
            KeyGroupNode.Delete;
            Result := EndUpdate;
            if not Result then
              FLastError := KeyedFile.LastError;
          end
          else
            CancelUpdate;
        end
        else
          CancelUpdate; {KeyGroup node already deleted as not found}
      end
      else
        FLastError := KeyedFile.LastError;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'DeleteKeyGroup', FLastError);
end;


function TZaphodsMapSetup.DeleteKeyGroupVar(
  const KeyboxLocation: TKeyboxLocation; const KeyGroupName,
  VarName: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
  ANode: TZaphodNode;
  FileReloaded: Boolean;
begin
  Result := False;
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      if BeginUpdate(FileReloaded) then
      begin
        ANode := ZNode(nil, ['KeyGroups/KeyGroup', '@name', KeyGroupName]);
        if ANode <> nil then
        begin
          Result := DeleteVar(ANode, VarName);
          if Result then
          begin
            Result := EndUpdate;
            if not Result then
              FLastError := KeyedFile.LastError;
          end
          else
          begin
            FLastError := KeyedFile.LastError;
            CancelUpdate;
          end;
        end
        else
          CancelUpdate; {KeyGroupVar already deleted as not found}
      end
      else
        FLastError := KeyedFile.LastError;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
  if FLastError <> '' then
    FLastError := KeyboxError(KeyboxLocation, 'DeleteKeyGroupVar', FLastError);
end;


function TZaphodsMapSetup.GetKeyboxVarByName(
  const KeyboxLocation: TKeyboxLocation; const AVarName: string;
  var AVarValue: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
begin
  Result := False;
  AVarValue := '';
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      AVarValue := ZNodeAttr(nil, ['Variables/Var', '@name', AVarName], cxNone,
        '');
      Result := True;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
end;


function TZaphodsMapSetup.GetKeyboxVarDelimiters(
  const KeyboxLocation: TKeyboxLocation; var BeginDelimiter,
  EndDelimiter: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
begin
  Result := False;
  BeginDelimiter := '';
  EndDelimiter := '';
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      BeginDelimiter := ZNodeAttr(nil, ['Variables'], cxNone, '', 'begin');
      EndDelimiter := ZNodeAttr(nil, ['Variables'], cxNone, '', 'end');
      Result := True;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
end;

function TZaphodsMapSetup.GetKeyboxGroupVarByName(
  const KeyboxLocation: TKeyboxLocation; const KeyGroupName, AVarName: string;
  var AVarValue: string): Boolean;
var
  KeyedFile: TZaphodXmlDoc;
begin
  Result := False;
  AVarValue := '';
  KeyedFile := GetKeyboxAsKeyedFile(KeyboxLocation, FLastError);
  if KeyedFile <> nil then
  begin
    with KeyedFile do
    begin
      AVarValue := ZNodeAttr(nil, ['KeyGroups/KeyGroup', '@name', KeyGroupName,
        'Variables/Var', '@name', AVarName], cxNone, '');
      Result := True;
    end;
  end;
  FZaphodsMap.DeactivateAllKeys;
end;



function TZaphodsMapSetup.ZaphodsMapEnvVarCreate(
  const ZaphodsMapRootFolder: string): Boolean;
begin
  FLastError := '';
  Result := False;
  if GetEnvironmentVariable('ZaphodsMap') = '' then
    Result := cpSetZaphodsMapEnvVar(ZaphodsMapRootFolder, FLastError)
  else
    FLastError := szmZaphodsMapEnvVarAlreadyExists;
  if Result then
    FZaphodsMap.Reset;
end;


end.
