#include-once

; версия 0.4 от 2013.07.20
; http://www.autoitscript.com/forum/topic/147402-inivirtualspe

; =======================================
; Title .........: IniVirtual
; AutoIt Version : 3.3.6.1 - 3.3.8.1
; Language ......: English + Русский
; Description ....: Операции с ini-файлом в памяти
; Author(s) .......: AZJIO
; =======================================

; #CURRENT# =============================
; _IniVirtualSPE_Delete
; _IniVirtualSPE_IsDuplicateKeys
; _IniVirtualSPE_IsDuplicateSection
; _IniVirtualSPE_Read
; _IniVirtualSPE_ReadSection
; _IniVirtualSPE_ReadSectionNames
; _IniVirtualSPE_RenameSection
; _IniVirtualSPE_Write
; _IniVirtualSPE_WriteSection
; _SectionExists
; =======================================

Func _IniVirtualSPE_Read($s_INI_Text, $sSection, $sKey, $sDefault = '')
	$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
	Local $aSection = StringRegExp($s_INI_Text, '(?si)(?:\r\n|\A)\h*\[\h*' & $sSection & '\h*\]\h*\r\n\s*([^\[\s].*?)(?=\r\n\h*\[|\s*\z)', 1) ; извлечение контента секции
	If @error Then Return $sDefault
	$aSection = StringRegExp($aSection[0], '(?mi)^\h*' & StringRegExpReplace($sKey, '[][{}()*+?.\\^$|=<>#]', '\\$0') & '\h*=\h*(.*?)\h*\r?$', 1) ; извлечение параметра
	If @error Then Return $sDefault
	Switch StringLeft($aSection[0], 1) & StringRight($aSection[0], 1) ; если параметры в кавычках, то удаление кавычек " и '
		Case '""', "''"
			$aSection[0] = StringTrimLeft(StringTrimRight($aSection[0], 1), 1)
	EndSwitch
	Return $aSection[0]
EndFunc   ;==>_IniVirtualSPE_Read

Func _IniVirtualSPE_ReadSection($s_INI_Text, $sSection)
	$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
	Local $aSection = StringRegExp($s_INI_Text, '(?si)(?:\r\n|\A)\h*\[\h*' & $sSection & '\h*\]\h*\r\n\s*([^\[\s].*?)(?=\r\n\h*\[|\s*\z)', 1) ; извлечение контента секции
	If @error Then Return SetError(1, 0, 0)
	Local $aKey = __IniVirtualSPE_GetKeysVal($aSection[0])
	If Not $aKey[0][0] Then SetError(1, 0, $aKey)
	Return $aKey
EndFunc   ;==>_IniVirtualSPE_ReadSection

Func __IniVirtualSPE_GetKeysVal($vData)
	$vData = StringRegExp($vData, '(?m)^\h*([^;].*?)\h*=\h*(["'']?)(.*?)\2\h*\r?$', 3) ; учитывает пробелы между элементами и обрамление кавычками
	Local $d, $u = UBound($vData) ; 0 как индикатор ошибки
	Local $aData2D[$u / 3 + 1][2] = [[$u / 3]]
	For $i = 0 To $u - 1 Step 3
		$d = Int($i / 3) + 1
		$aData2D[$d][0] = $vData[$i]
		$aData2D[$d][1] = $vData[$i + 2]
	Next
	Return $aData2D
EndFunc   ;==>__IniVirtualSPE_GetKeysVal

Func _IniVirtualSPE_Delete(ByRef $s_INI_Text, $sSection, $sKey = Default)
	If $sKey = Default Then
		$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
		$s_INI_Text = StringRegExpReplace($s_INI_Text, '(?si)(?:\r\n|\A)(\h*\[\h*' & $sSection & '\h*\]\h*(?:\r\n\s*[^\[\s].*?|))(?=\r\n\h*\[|\s*\z)', '') ; удаление секции
		If @error Then
			Return SetError(0, 1, 1)
		Else
			Return SetError(0, @extended, 1)
		EndIf
	Else
		Local $aSection = StringRegExp($s_ini_Text, '(?si)(?:\r\n|\A)\h*\[\h*' & StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0') & '\h*\]\h*(\r\n\s*[^\[\s].*?\h*)(\s*\[\h*.*?\h*\]|\s*\z)', 2) ; извлечение контента секции
		If @error Then Return SetError(0, 1, 1)
		Local $iEndSection = @extended - StringLen($aSection[2]) - 1 ; конец секции
		Local $aKey = StringRegExp($aSection[1], '(?si)(\r\n\h*' & StringRegExpReplace($sKey, '[][{}()*+?.\\^$|=<>#]', '\\$0') & '\h*=\h*.*?\h*)(\s*\r\n|\s*\z)', 2) ; извлечение параметра
		If @error Then Return SetError(0, 2, 1)
		Local $iStart = @extended - StringLen($aKey[1]) - StringLen($aKey[2]) - 1 + $iEndSection - StringLen($aSection[1]) ; начало секции + начало параметра
		$s_INI_Text = StringLeft($s_INI_Text, $iStart) & StringTrimLeft($s_INI_Text, $iStart + StringLen($aKey[1]))
		Return 1
	EndIf
EndFunc   ;==>_IniVirtualSPE_Delete

Func _IniVirtualSPE_Write(ByRef $s_ini_Text, $sSection, $sKey, $sValue)
	; If StringRegExp($s_ini_Text, '\]\h*(?:\r\n|[\r\n])\h*\[') Then $s_ini_Text = StringRegExpReplace($s_ini_Text, '\]\h*(?:\r\n|[\r\n])\h*\[', ']' & @CRLF & @CRLF & '[')
	$sValue = StringRegExpReplace($sValue, '^([''"]).*\1\z', '"\0"')
	; Если значение содержит пробел справа или слева, то обрамляем в кавычки
	If StringRegExp($sValue, '^\h|\h\z') Then $sValue = '"' & $sValue & '"'
	Local $aSection = StringRegExp($s_ini_Text, '(?si)(?:\r\n|\A)\h*\[\h*' & StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0') & '\h*\]\h*(\r\n\s*[^\[\s].*?\h*)?(\s+\[\h*.*?\h*\]|\s*\z)', 2) ; извлечение контента секции
	If @error Then
		; если не найдено, то запись в конец файла
		$s_ini_Text &= @CRLF & @CRLF & '[' & $sSection & ']' & @CRLF & $sKey & '=' & $sValue; Добавление новой секции
	Else
		Local $iEndSection = @extended - StringLen($aSection[2]) - 1 ; конец секции
		; извлечение параметра
		Local $aKey = StringRegExp($aSection[1], '(?si)\r\n\h*' & StringRegExpReplace($sKey, '[][{}()*+?.\\^$|=<>#]', '\\$0') & '\h*=\h*(.*?)(\s*\r\n|\s*\z)', 2)
		If @error Then
			; если не найден ключ, то запись в конец секции
			$s_ini_Text = StringLeft($s_ini_Text, $iEndSection) & @CRLF & $sKey & '=' & $sValue & StringTrimLeft($s_ini_Text, $iEndSection) ; Добавление нового параметра
		Else
			Local $iStart = @extended - StringLen($aKey[1]) - StringLen($aKey[2]) - 1 + $iEndSection - StringLen($aSection[1]) ; начало секции + начало параметра
			$s_ini_Text = StringLeft($s_ini_Text, $iStart) & StringReplace(StringTrimLeft($s_ini_Text, $iStart), $aKey[1], $sValue, 1) ; Замена значения
		EndIf
	EndIf
	Return 1
EndFunc   ;==>_IniVirtualSPE_Write

Func _IniVirtualSPE_WriteSection(ByRef $s_INI_Text, $sSection, $vData, $iIndex = 1)
	Local $u = 0
	If IsArray($vData) Then
		If UBound($vData, 0) <> 2 Then Return SetError(1, 0, 0)
		$u = UBound($vData)
		If $iIndex > $u - 1 Or $iIndex < 0 Or Not StringIsDigit($iIndex) Then Return SetError(2, 0, 0)
	EndIf
	_IniVirtualSPE_Delete($s_INI_Text, $sSection) ; удаляем секцию на случай если она существует
	If $u Then ; Если массив, то в цикле соединяем элементы в текстовые данные
		Local $sData = ''
		For $i = $iIndex To $u - 1
			$vData[$i][1] = StringRegExpReplace($vData[$i][1], '^([''"]).*\1\z', '"\0"')
			If StringRegExp($vData[$i][1], '^\h|\h\z') Then $vData[$i][1] = '"' & $vData[$i][1] & '"'
			$sData &= $vData[$i][0] & '=' & $vData[$i][1] & @CRLF
		Next
		$vData = StringTrimRight($sData, 2)
	EndIf
	$s_INI_Text &= @CRLF & @CRLF & '[' & $sSection & ']' & @CRLF & StringRegExpReplace($vData, '(?<!\r)\n', @CRLF)
	Return 1
EndFunc   ;==>_IniVirtualSPE_WriteSection

Func _SectionExists($s_INI_Text, $sSection)
	$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
	Return StringRegExp($s_INI_Text, '(?si)(?:\r\n|\A)\h*\[\h*' & $sSection & '\h*\]\h*(?=\r\n|\s*\z)') ; извлечение контента секции
EndFunc   ;==>_SectionExists

Func _IniVirtualSPE_RenameSection(ByRef $s_INI_Text, $sSection, $sNewName, $flag = 0)
	If Not _SectionExists($s_INI_Text, $sSection) Then Return SetError(1, 0, 0) ; если старая секция не существует, то завершение функции
	If $flag Then ; Если перезаписывать, то
		_IniVirtualSPE_Delete($s_INI_Text, $sNewName) ; удаляем новую секцию на случай если она существует
		$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
		$s_INI_Text = StringRegExpReplace($s_INI_Text, '(?mi)^(\h*\[\h*)(' & $sSection & ')(\h*\]\h*\r?)$', '\1' & $sNewName & '\3') ; переименование секции
	Else
		If _SectionExists($s_INI_Text, $sNewName) Then
			Return SetError(2, 0, 0)
		Else
			$sSection = StringRegExpReplace($sSection, '[][{}()*+?.\\^$|=<>#]', '\\$0')
			$s_INI_Text = StringRegExpReplace($s_INI_Text, '(?mi)^(\h*\[\h*)(' & $sSection & ')(\h*\]\h*\r?)$', '\1' & $sNewName & '\3') ; переименование секции
		EndIf
	EndIf
	Return 1
EndFunc   ;==>_IniVirtualSPE_RenameSection

Func _IniVirtualSPE_ReadSectionNames($s_INI_Text)
	$s_INI_Text = StringRegExp($s_INI_Text, '(?m)^\h*\[\h*(.*?)\h*\]\h*\r?$', 3) ; переименование секции
	If @error Then Return SetError(1, 0, 0)
	Local $u = UBound($s_INI_Text)
	ReDim $s_INI_Text[$u + 1]
	For $i = $u To 1 Step -1
		$s_INI_Text[$i] = $s_INI_Text[$i - 1]
	Next
	$s_INI_Text[0] = $u
	Return $s_INI_Text
EndFunc   ;==>_IniVirtualSPE_ReadSectionNames

Func _IniVirtualSPE_IsDuplicateKeys($s_INI_Text, $sSection)
	Local $aDuplicate[1] = [0]
	$aKey = _IniVirtualSPE_ReadSection($s_INI_Text, $sSection)
	If @error Then Return $aDuplicate
	Local $s = Chr(1)
	Assign($s, 1, 1)
	For $i = 1 To $aKey[0][0]
		If IsDeclared($aKey[$i][0] & $s) Then
			$aDuplicate[0] += 1
			ReDim $aDuplicate[$aDuplicate[0] + 1]
			$aDuplicate[$aDuplicate[0]] = $aKey[$i][0]
		Else
			Assign($aKey[$i][0] & $s, 1, 1)
		EndIf
	Next
	Return $aDuplicate
	; If $aDuplicate[0] Then
		; Return $aDuplicate
	; Else
		; Return True
	; EndIf
EndFunc   ;==>_IniVirtualSPE_IsDuplicateKeys

Func _IniVirtualSPE_IsDuplicateSection($s_INI_Text)
	Local $aDuplicate[1] = [0]
	$aKey = _IniVirtualSPE_ReadSectionNames($s_INI_Text)
	If @error Then Return $aDuplicate
	Local $s = Chr(1)
	Assign($s, 1, 1)
	For $i = 1 To $aKey[0]
		If IsDeclared($aKey[$i] & $s) Then
			$aDuplicate[0] += 1
			ReDim $aDuplicate[$aDuplicate[0] + 1]
			$aDuplicate[$aDuplicate[0]] = $aKey[$i]
		Else
			Assign($aKey[$i] & $s, 1, 1)
		EndIf
	Next
	Return $aDuplicate
	; If $aDuplicate[0] Then
		; Return $aDuplicate
	; Else
		; Return True
	; EndIf
EndFunc   ;==>_IniVirtualSPE_IsDuplicateSection