XML-based config file

Share your advanced PureBasic knowledge/code with the community.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

XML-based config file

Post by Lunasole »

Hi.
This one is simple but useful to quickly append config/settings support to your app.
It supports both raw XML mode (where user can edit config), and compressed mode (binary file, greatly decreased in size).
Usage is also simple: whole 2 functions for Save and Load

Code: Select all

EnableExplicit

; Assume this is your app configuration ^^
Structure ConfigEx
	Value$
EndStructure
Structure Config
	SomeStuff$
	Another.a
	StuffX.ConfigEx
	Stuff.a [4]
EndStructure

;{ Config XML v2 }
	
	; *InData		a structure of type CONFIG (define it as needed)
	; Filename$		path to a config file
	; Compress		TRUE to compress data (will make config unreadable, but much smaller than raw XML)
	; Format		TRUE to reformat XML before save
	; RETURN:		0 on success
	;				1 if XML-related error
	;				2 if compression failed
	;				3 if file-related error
	Procedure SaveCfg (*inData.CONFIG, Filename$, Compress = #False, Format = #False)
		Protected Res, TreeXML = CreateXML(#PB_Any, #PB_UTF8)
		Protected xSize, *hMem, *hCMem, hFile, fSize
		If IsXML(TreeXML) And InsertXMLStructure(RootXMLNode(TreeXML), *inData, CONFIG)
			If Format And Not Compress
				FormatXML(TreeXML, #PB_XML_ReFormat | #PB_XML_WindowsNewline, 2)
			EndIf
			If Compress ; pack config to xml, then pack xml to compressed binary, then save to file
				xSize = ExportXMLSize(TreeXML, #PB_XML_NoDeclaration)
				If xSize < 64 ; using this to avoid "comression failed" on CompressMemory()
					xSize = 64
				EndIf
				*hMem = AllocateMemory(xSize)
				*hCMem = AllocateMemory(xSize)
				Res = 2
				If *hMem And *hCMem And ExportXML(TreeXML, *hMem, MemorySize(*hMem), #PB_XML_NoDeclaration)
					FreeXML(TreeXML)
					UseZipPacker()
					fSize = CompressMemory(*hMem, MemorySize(*hMem), *hCMem, MemorySize(*hCMem), #PB_PackerPlugin_Zip)
					If fSize
						DeleteFile(Filename$, #PB_FileSystem_Force)
						hFile = CreateFile(#PB_Any, Filename$)
						If hFile
							WriteLong(hFile, xSize) 		; uncompressed size [first 8 bytes of file]
							WriteData(hFile, *hCMem, fSize)	; at last, save to file compressed data
							CloseFile(hFile)
							Res = 0
						Else
							Res = 3
						EndIf
					Else
						Res = 2
					EndIf
				EndIf
			Else		; pack cfg to xml and write to file
				If Not SaveXML(TreeXML, Filename$, #PB_XML_NoDeclaration)
					Res = 3
				EndIf
			EndIf
		Else
			Res = 1
		EndIf
		;cls
		If *hMem : FreeMemory(*hMem) : EndIf
		If *hCMem : FreeMemory(*hCMem) : EndIf		
		If IsXML(TreeXML) : FreeXML(TreeXML) :EndIf
		If IsFile(hFile) : CloseFile(hFile) : EndIf
		ProcedureReturn Res
	EndProcedure
	
	; *OutData		a structure of type CONFIG to receive data
	; Filename$		path to a config file
	; Compressed	if file was compressed on saving, set it to TRUE
	; RETURN:		0 on success
	;				1 if XML-related error
	;				2 if decompression failed
	;				3 if file-related error
	Procedure ReadCfg (*OutData.CONFIG, Filename$, Compressed = #False)
		Protected Res, TreeXML
		Protected xSize, *hMem, *hCMem, hFile, fSize
		
		If Compressed	; read from file, decompress xml, get map from xml
			If FileSize(Filename$) >= SizeOf(Long)
				hFile = OpenFile(#PB_Any, Filename$)
			EndIf
			If hFile
				fSize = Lof(hFile) - SizeOf(Long)
				xSize = ReadLong(hFile) ; read size of uncompressed data
				*hMem = AllocateMemory(fSize)
				If *hMem And ReadData(hFile, *hMem, fSize) = fSize ; read compressed binary
					UseZipPacker()
					*hCMem = AllocateMemory(xSize)
					If *hCMem And UncompressMemory(*hMem, MemorySize(*hMem), *hCMem, MemorySize(*hCMem), #PB_PackerPlugin_Zip) = xSize ; decompress
						TreeXML = CatchXML(#PB_Any, *hCMem, MemorySize(*hCMem), 0, #PB_UTF8)										   ; get XML
						If IsXML(TreeXML)
							ExtractXMLStructure(MainXMLNode(TreeXML), *OutData, CONFIG) ; finally extract data
						Else
							Res = 1
						EndIf
					Else
						Res = 2
					EndIf
				Else
					Res = 3
				EndIf
			Else
				Res = 3
			EndIf
		Else 
			TreeXML = LoadXML(#PB_Any, Filename$, #PB_UTF8)
			If IsXML(TreeXML)
				ExtractXMLStructure(MainXMLNode(TreeXML), *OutData, CONFIG) ; finally extract data
			Else
				Res = 1
			EndIf
		EndIf
		
		;cls
		If *hMem : FreeMemory(*hMem) : EndIf
		If *hCMem : FreeMemory(*hCMem) : EndIf
		If IsXML(TreeXML) : FreeXML(TreeXML) : EndIf
		If IsFile(hFile) : CloseFile(hFile) : EndIf
		ProcedureReturn Res
	EndProcedure
	
;}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Example
;;;;;;;;;;;;;;;;;

; Set some config values
Define F.Config
	F\Another = 1
	F\SomeStuff$ = "a"
	F\Stuff[2] = 6
	F\StuffX\Value$ = "stuffX"

; 1) Writing config file (compressed)
SaveCfg(F, "config.bin", #True)


; 2) Now read it back to an empty structure, like on application startup
Define R.Config
ReadCfg(R, "config.bin", #True)

; Show results
Debug R\Another
Debug R\SomeStuff$
Debug R\Stuff[2]
Debug R\StuffX\Value$
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"