* Program..: DbEditor.prg * Author...: Jeremy Suiter * Date.....: 25/03/2003 * * Notice...: Copyright (c) 2003, Jeremy Suiter, All Rights Reserved * Notes....: A set of functions to load, query, and save .INI files * Modified from functions found on the oasis. * IniIni() * IniLoad() * IniSave() * IniGet() * IniPut() * IniDel() * IniExists() * IniCount() * * Assumptions: 1. That values will only be set in an .INI file after * a section header is encountered. (for example, "load=" * occurs after "[windows]" in WIN.INI) * 2. The .INI file is small (.INI files are supposed to be * less than 64k in size). * 3. That items within sections are uniquely named. * 4. That sections are uniquely named. #Include "Common.ch" #Include "FileIo.ch" #Include "Xbp.ch" #Define BLOCKSIZE 128 #Define CR CHR(13) #Define LF CHR(10) #Define CRLF (CR + LF) #Define INIEXT ".INI" #Define NULSECT "**NUL SECTION**" /* $DOC$ * $FUNCNAME$ * IniLoad() * * $ONELINER$ * Loads an .INI file's contents into an array. * * $SYNTAX$ * IniLoad([]) -> * * $ARGUMENTS$ * cFileName - name of the .INI file to be loaded * (Default value for cFileName is "APPLIC") * (Default extension for cFileName is ".INI") * * $ReturnS$ * aContents - the contents of the .INI file * * $DESCRIPTION$ * This function loads the contents of an .INI files into an array. * This has several advantages: * 1) The disk does not have to be repeatedly accessed to query * or set multiple values. * 2) Re-writing the .INI file is very quick and easy. * * $EXAMPLES$ * aMyIni:=IniLoad("LEDGER") * cHiLight:=IniGet(aMyIni, "colors", "highlight") * If cHiLight != NIL * SETCOLOR(cHiLight) * EndIf * */ * ======================================================================== * FUNCTION IniLoad(cFile) LOCAL cAppStart, nAt, nFileEnd, hIniFile, cLine, aIni LOCAL cItem, cValue, nEquPos, cSection If !("." $ cFile) cFile += INIEXT EndIf cAppStart := AppName(.t.) nAt := RAt('\', cAppStart) if nAt <> 0 cFile := (Left(cAppStart, nAt) + cFile) else cFile := (cAppStart + '\' + cFile) endif aIni:={} // Initialise array hIniFile:=FOPEN(cFile, FO_READ + FO_DENYWRITE) If FERROR() == 0 If EMPTY(aIni) AADD(aIni, { NULSECT, {} }) EndIf nFileEnd:=FSEEK(hIniFile, 0, FS_END) // Find end of file if nFileEnd < 4 Errm('File Size of "' + cFile + '" is very short', 'ERROR FILE SIZE', XBPMB_CRITICAL) nFileEnd := -1 endif FSEEK(hIniFile, 0, FS_SET) // Reposition at top DO WHILE FSEEK(hIniFile, 0, FS_RELATIVE) < nFileEnd cLine:=ReadLine(hIniFile) DO CASE CASE EMPTY(cLine) AADD(aIni[ LEN(aIni), 2 ], { "", "" }) CASE LEFT(cLine , 1) == ";" AADD(aIni[ LEN(aIni), 2 ], { cLine, "" }) CASE LEFT(cLine, 1) == "[" .And. RIGHT(cLine, 1) == "]" cSection:=SUBSTR(cLine, 2) cSection:=LEFT(cSection, LEN(cSection) - 1) AADD(aIni, { cSection, {} }) CASE (nEquPos:=AT("=", cLine)) > 0 cItem :=LEFT(cLine , nEquPos - 1) cValue :=SUBSTR(cLine, nEquPos + 1) AADD(aIni[ LEN(aIni), 2 ], { cItem, cValue }) OTHERWISE * Do nothing - it has the wrong structure ENDCASE ENDDO FCLOSE(hIniFile) else Errm('"' + cFile + '" Open error: ' + DosErrorMessage(FError()), 'OPEN ERROR', XBPMB_CRITICAL) EndIf Return(aIni) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniSave() * * $ONELINER$ * Saves an array as the contents of an .INI file. * * $SYNTAX$ * IniSave( [, ]) -> * * $ARGUMENTS$ * aContents - an array Returned by IniLoad / IniIni. * cFileName - name of the file into which array is to be placed * (Default value for cFileName is "APPLIC") * (Default extension for cFileName is ".INI") * * $ReturnS$ * lSuccess - was the command successful? * * $DESCRIPTION$ * This function saves the contents of an array into an .INI file. * * $EXAMPLES$ * aMyIni:=IniLoad("LEDGER") * IniPut(aMyIni, "colors", "highlight", "W+/B") * If IniSave(aMyIni, "LEDGER") * ? "LEDGER.INI saved" * EndIf * * $SEEALSO$ * IniIni() * IniLoad() * IniPut() * IniGet() * IniDel() * IniExists() * IniCount() * * $INCLUDE$ * common.ch * fileio.ch * * $END$ */ FUNCTION IniSave(aIni, cFile) LOCAL nOutLen, nInCntr, nInLen, lSuccess, hIniFile, nOutCntr, cValue, cItem DEFAULT cFile TO "APPLIC" If ! ("." $ cFile) cFile += INIEXT EndIf lSuccess:=.F. hIniFile:=FCREATE(cFile, FC_NORMAL) If hIniFile > -1 nOutCntr:=1 nOutLen :=LEN(aIni) DO WHILE nOutCntr <= nOutLen If aIni[ nOutCntr, 1 ] != NULSECT FWRITE(hIniFile, "[" + aIni[ nOutCntr, 1 ] + "]" + CRLF) EndIf nInLen :=LEN(aIni[ nOutCntr, 2 ]) nInCntr:=1 DO WHILE nInCntr <= nInLen cItem :=aIni[ nOutCntr, 2, nInCntr, 1 ] DO CASE CASE EMPTY(cItem) FWRITE(hIniFile, CRLF) CASE LEFT(cItem , 1) == ";" FWRITE(hIniFile, cItem + CRLF) OTHERWISE cValue:=aIni[ nOutCntr, 2, nInCntr, 2 ] FWRITE(hIniFile, cItem + "=" + cValue + CRLF) ENDCASE nInCntr++ ENDDO nOutCntr++ ENDDO FCLOSE(hIniFile) lSuccess:=.T. EndIf Return(lSuccess) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniPut() * * $ONELINER$ * Modify the value associated with an item in a section. * * $SYNTAX$ * IniPut(, , , ) -> * * $ARGUMENTS$ * aContents - the contents of an .INI file, loaded through IniLoad() * and/or created by IniIni() and/or modified through * IniPut() * xSection - the name of the major grouping (or section) in the .INI * file (for example, "386Enh" in the SYSTEM.INI file), * or a number for the nth section in the .INI file. * xItem - the identifier of the item to be modified (for example, * "mouse.drv" in the "boot" section of the "SYSTEM.INI" * file, or a number for the nth item in a section. * cValue - the value to be given to the item within the section * within the .INI file (for example, "61" would be placed * against the item "iCountry" within the section "[intl]" * by issuing the commands: * aWinIni:=IniLoad("WIN") * IniPut(aWinIni, "intl", "iCountry", "61") * * $ReturnS$ * lSuccess - was the command successful? * * $DESCRIPTION$ * This function associates values with item names with sections. * If the section does not exist, it creates it. * If the item name does not exist, it creates it. * * $EXAMPLES$ * aMyIni:=IniIni() * IniPut(aMyIni, "windows", "load", "c:\command.com") * If IniSave(aMyIni, "WIN") * ? "WIN.INI saved" * EndIf * * $SEEALSO$ * IniIni() * IniLoad() * IniSave() * IniGet() * IniDel() * IniExists() * IniCount() * * $INCLUDE$ * * $END$ */ FUNCTION IniPut(aIni, xSection, xItem, cValue) LOCAL nOutPos, nInPos, lSuccess, lFinished lSuccess :=.T. lFinished:=.F. If VALTYPE(xSection) == "N" If xSection >= 1 .And. xSection < LEN(aIni) nOutPos:=xSection + 1 Else nOutPos :=0 lFinished:=.T. EndIf Else nOutPos :=ASCAN(aIni, {|x| UPPER(x[1]) == UPPER(xSection) }) EndIf If lFinished lSuccess:=.F. Else If nOutPos == 0 AADD(aIni, { xSection, {} }) nOutPos:=LEN(aIni) EndIf If VALTYPE(xItem) == "N" nInPos:=NthItemPos(aIni, nOutPos, xItem) If nInPos != xItem lFinished:=.T. lSuccess :=.F. EndIf Else nInPos:=ASCAN(aIni[ nOutPos, 2 ], {|x| UPPER(x[1]) == UPPER(xItem) }) EndIf If ! lFinished If nInPos == 0 AADD(aIni[ nOutPos, 2 ], { xItem, cValue }) Else aIni[ nOutPos, 2, nInPos, 2 ]:=cValue EndIf EndIf EndIf Return(lSuccess) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniGet() * * $ONELINER$ * Return section or item names or the value for an item. * * $SYNTAX$ * IniGet(, [, [, lGetName ] ]) -> * * $ARGUMENTS$ * aContents - the contents of an .INI file, loaded through IniLoad() * and/or created by IniIni() and/or modified through * IniPut() * xSection - the name of the major grouping (or section) in the .INI * file (for example, "386Enh" in the SYSTEM.INI file), * or a number for the nth section in the .INI file. * xItem - the identifier of the item to be modified (for example, * "mouse.drv" in the "boot" section of the "SYSTEM.INI" * file, or a number for the nth item in a section. * lGetName - is the name of the item to be Returned? * * $ReturnS$ * cValue - the value associated with the item within the section * within the .INI file (for example, "61" would be Returned * for the item "iCountry" within the section "[intl]" * for someone within Australia, by: * aWinIni:=IniLoad("C:\WINDOWS\WIN.INI") * ? IniGet(aWinIni, "intl", "iCountry") * or the 4th item within the 1st section would be * Returned by: * ? IniGet(aWinIni, 1, 4) * * * $DESCRIPTION$ * This function determines the value of items within sections, or * the name of the nth item, or the name of the nth section. * If there is no such unit, it Returns NIL. * * $EXAMPLES$ * aMyIni :=IniLoad("C:\WINDOWS\WIN.INI") * cRunName:=IniGet(aMyIni, "windows", "run") * If cRunName != NIL * ? "The program which will be run when Windows starts is:" * ? cRunName * EndIf * * $SEEALSO$ * IniIni() * IniLoad() * IniSave() * IniGet() * IniDel() * IniExists() * IniCount() * * $INCLUDE$ * * $END$ */ FUNCTION IniGet(aIni, xSection, xItem, lGetName) LOCAL nOutPos, nInPos, cValue DEFAULT lGetName TO .F. // <> avoid type mismatch in caller function or method in case the ini value is not defined cValue := "" nOutPos:=0 If VALTYPE(xSection) == "N" If (xSection >= 1) .And. (xSection < LEN(aIni)) nOutPos :=xSection + 1 EndIf Else nOutPos :=ASCAN(aIni, {|x| UPPER(x[1]) == UPPER(xSection) }) EndIf If nOutPos > 0 If xItem == NIL cValue:=aIni[ nOutPos, 1 ] Else nInPos:=0 If VALTYPE(xItem) == "N" nInPos:=NthItemPos(aIni, nOutPos, xItem) Else nInPos:=ASCAN(aIni[ nOutPos, 2 ], {|x| UPPER(x[1]) == UPPER(xItem) }) EndIf If nInPos > 0 If lGetName cValue:=aIni[ nOutPos, 2, nInPos, 1 ] Else cValue:=aIni[ nOutPos, 2, nInPos, 2 ] EndIf EndIf EndIf EndIf Return(cValue) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniCount() * * $ONELINER$ * Return the number of sections / items within the .INI file. * * $SYNTAX$ * IniCount( [, ]) -> * * $ARGUMENTS$ * aContents - the contents of an .INI file, loaded through IniLoad() * and/or created by IniIni() and/or modified through * IniPut() * xSection - the name of the major grouping (or section) in the .INI * file (for example, "386Enh" in the SYSTEM.INI file), * or a number for the nth section in the .INI file. * * $ReturnS$ * - the number of sections / items within the .INI file. * * $DESCRIPTION$ * This function determines the number of sections within the .INI * file, or the number of items within the section. * If there is no such section, or no such item within such a section, * it Returns 0. * * $EXAMPLES$ * aMyIni :=IniLoad("C:\WINDOWS\WIN.INI") * ? "There are " * ?? IniCount(aMyIni) * ?? " sections in the WIN.INI file." * * $SEEALSO$ * IniIni() * IniLoad() * IniSave() * IniGet() * IniPut() * IniDel() * IniExists() * * $INCLUDE$ * * $END$ */ FUNCTION IniCount(aIni, xSection) LOCAL nOutPos, nCount:=0 If xSection == NIL nCount:=LEN(aIni) - 1 Else nOutPos:=0 If VALTYPE(xSection) == "N" nOutPos:=xSection + 1 Else nOutPos:=ASCAN(aIni, {|x| UPPER(x[1]) == UPPER(xSection) }) EndIf If nOutPos >= 1 .And. nOutPos <= LEN(aIni) nCount:=0 AEVAL(aIni[ nOutPos, 2 ], {|x| IF(IsAKey(x[1]), nCount++, 0)}) EndIf EndIf Return(nCount) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniExists() * * $ONELINER$ * Determine whether the item and/or section exists * * $SYNTAX$ * IniExists(, [, ]) -> * * $ARGUMENTS$ * aContents - the contents of an .INI file, loaded through IniLoad() * and/or created by IniIni() and/or modified through * IniPut() * xSection - the name of the major grouping (or section) in the .INI * file (for example, "386Enh" in the SYSTEM.INI file), * or a number for the nth section in the .INI file. * xItem - the identifier to be deleted (for example, "mouse.drv" * in the "boot" section of the "SYSTEM.INI" * file, or a number for the nth item in a section. * * $ReturnS$ * lExists - does the item/section exist? * * $DESCRIPTION$ * This function determines whether a given item exists within a * given section (If the item is nominated), or whether the section * exists (If the item is not nominated). * If the section (and item, If nominated) exists, it Returns .T., * otherwise .F. * * $EXAMPLES$ * aMyIni :=IniLoad("C:\WINDOWS\WIN.INI") * If ! IniExists(aMyIni, "windows", "run") * ? "No program will be run when Windows starts." * EndIf * * $SEEALSO$ * IniIni() * IniLoad() * IniSave() * IniDel() * IniGet() * IniPut() * IniCount() * * $INCLUDE$ * * $END$ */ FUNCTION IniExists(aIni, xSection, xItem) LOCAL nOutPos, nInPos, lExists lExists:=.F. If VALTYPE(xSection) == "N" nOutPos:=xSection + 1 Else nOutPos:=ASCAN(aIni, {|x| UPPER(x[1]) == UPPER(xSection) }) EndIf If nOutPos > 0 .And. nOutPos <= LEN(aIni) If xItem == NIL lExists :=.T. Else If VALTYPE(xItem) == "N" lExists:=IniCount(aIni, nOutPos - 1) >= xItem Else nInPos :=ASCAN(aIni[ nOutPos, 2 ], {|x| UPPER(x[1]) == UPPER(xItem) }) lExists:=(nInPos > 0) EndIf EndIf EndIf Return(lExists) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniDel() * * $ONELINER$ * Delete the selected item within a nominated section * * $SYNTAX$ * IniDel(, [, ]) -> * * $ARGUMENTS$ * aContents - the contents of an .INI file, loaded through IniLoad() * and/or created by IniIni() and/or modified through * IniPut() * xSection - the name of the major grouping (or section) in the .INI * file (for example, "386Enh" in the SYSTEM.INI file), * or a number for the nth section in the .INI file. * xItem - the identifier to be deleted (for example, "mouse.drv" * in the "boot" section of the "SYSTEM.INI" * file, or a number for the nth item in a section. * Optional; If not specified, the whole section is deleted. * * $ReturnS$ * lSuccess - was the item deleted? * * $DESCRIPTION$ * This function deletes items within sections, or whole sections. * If there is no such section, or no such item within such a section, * or the item was successfully deleted, it Returns .T. * Otherwise, it Returns .F. (In other words, it always Returns .T.) * * $EXAMPLES$ * aMyIni :=IniLoad("C:\WINDOWS\WIN.INI") * If IniDel(aMyIni, "windows", "run") * ? "No program will be run when Windows starts." * IniSave("C:\WINDOWS\WIN.INI") * EndIf * * $SEEALSO$ * IniIni() * IniLoad() * IniSave() * IniGet() * IniPut() * IniExists() * IniCount() * * $INCLUDE$ * * $END$ */ FUNCTION IniDel(aIni, xSection, xItem) LOCAL aSub, nOutPos, nInPos If VALTYPE(xSection) == "N" nOutPos:=xSection + 1 Else nOutPos:=ASCAN(aIni, {|x| UPPER(x[1]) == UPPER(xSection) }) EndIf If nOutPos >=1 .And. nOutPos <= LEN(aIni) If xItem == NIL ADEL(aIni[ nOutPos ], 1) ASIZE(aIni, LEN(aIni) - 1) Else If VALTYPE(xItem) == "N" nInPos:=NthItemPos(aIni, nOutPos, xItem) Else nInPos:=ASCAN(aIni[ nOutPos, 2 ], {|x| UPPER(x[1]) == UPPER(xItem) }) EndIf If nInPos > 0 aSub:=ACLONE(aIni[ nOutPos, 2 ]) ADEL(aSub, nInPos) ASIZE(aSub, LEN(aSub) - 1) aIni[ nOutPos, 2 ]:=aSub EndIf EndIf EndIf Return(.T.) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * IniIni() * * $ONELINER$ * Return an empty array suitable for use with IniXxx() functions. * * $SYNTAX$ * IniIni() * * $ARGUMENTS$ * * $ReturnS$ * aIni - an empty .INI file array. * * $DESCRIPTION$ * Return an empty array suitable for use with IniXxx() functions. * * $EXAMPLES$ * aMyIni :=IniIni() * IniPut(aMyIni, "windows", "run", "c:\command.com") * IniSave(aMyIni, "c:\windows\win") * * $SEEALSO$ * IniLoad() * IniSave() * IniGet() * IniPut() * IniDel() * IniExists() * IniCount() * * $INCLUDE$ * * $END$ */ FUNCTION IniIni() Return({}) * ======================================================================== * /* $DOC$ * $FUNCNAME$ * ReadLine() * * $ONELINER$ * Read the next CR/LF terminated line from a text file. * * $SYNTAX$ * ReadLine() -> * * $ARGUMENTS$ * hReadFile - the handle for the file to be read * * $ReturnS$ * cBuffer - the contents of the next CR/LF terminated line, without * the CR/LF on the end. * * $DESCRIPTION$ * This function reads the next CR/LF terminated line from a text file. * The data is read in 128 byte chunks to reduce disk access, and the * file is repositioned to be ready for the next line read. * * $EXAMPLES$ * hAuto:=FOPEN("c:\autoexec.bat", FO_READ) * cLine:=ReadLine(hAuto) * * Read until EOF or a blank line * DO WHILE ! EMPTY(cLine) * ? cLine * cLine:=ReadLine(hAuto) * ENDDO * FCLOSE(hAuto) * * $SEEALSO$ * * $INCLUDE$ * fileio.ch * * $END$ */ STATIC FUNCTION ReadLine(hReadFile) LOCAL cReadBuf, cPostBuf, nFilePos, nCharPos, lReadMor, nReadAmt nFilePos:=FSEEK(hReadFile, 0, FS_RELATIVE) cReadBuf:=SPACE(BLOCKSIZE) cPostBuf:="" lReadMor:=.T. DO WHILE lReadMor nReadAmt:=FREAD(hReadFile, @cReadBuf, BLOCKSIZE) lReadMor:=(! (CR $ cReadBuf)) .And. (nReadAmt == BLOCKSIZE) If lReadMor cPostBuf += cReadBuf cReadBuf:=SPACE(BLOCKSIZE) Else If CR $ cReadBuf nCharPos:=AT(CR, cReadBuf) - 1 Else nCharPos:=nReadAmt EndIf cPostBuf += LEFT(cReadBuf, nCharPos) FSEEK(hReadFile, nFilePos + LEN(cPostBuf) + 1, FS_SET) EndIf ENDDO If LEFT(cPostBuf, 1) == LF cPostBuf:=SUBSTR(cPostBuf, 2) EndIf Return(cPostBuf) * ======================================================================== * * Is the item being tested a key (as opposed to a blank line or a comment. * STATIC FUNCTION IsAKey(cItem) LOCAL lRetVal lRetVal:=(LEFT(cItem, 1) != ";") lRetVal:=lRetVal .And. (! EMPTY(cItem)) Return(lRetVal) * ======================================================================== * * Determine the actual position in the array of the nth item * STATIC FUNCTION NthItemPos(aIni, nOutPos, nItem) LOCAL nInPos, nInCntr nInCntr:=0 nInPos :=1 DO WHILE (nInCntr < nItem) .And. nInPos <= LEN(aIni[ nOutPos, 2 ]) If IsAKey(aIni[ nOutPos, 2, nInPos, 1 ]) nInCntr++ EndIf If nInCntr < nItem nInPos++ EndIf ENDDO If nInCntr != nItem nInCntr:=0 EndIf Return(nInPos)