Что нового

Ограничение по работе с ini файлами

ynbIpb

Скриптер
Сообщения
399
Репутация
110
Здравствуйте! Возникла необходимость работать с большими ini файлами.
В частности использую функцию IniReadSection
Но в справке написано следующее:
Только первые 32767 символов раздела принимаются во внимания для совместимости с Win9x.
И соответственно у меня половина секции не читается. Зачем это ограничение? Ведь в последних версиях уже отказались от поддержки Win9x.

Как это обойти?

Заранее благодарен.
 

joiner

Модератор
Локальный модератор
Сообщения
3,557
Репутация
628
ynbIpb
посмотри в справке _IniString_ReadSection
справку брать здесь http://autoit-script.ru/index.php/topic,10070.0.html
 

kaster

Мой Аватар, он лучший самый
Команда форума
Глобальный модератор
Сообщения
4,020
Репутация
626
если это ограничение самого языка, то единственный способ обхода – написать свой ini парсер (или воспользоваться чужим), что в общем-то довольно тривиальная задача. все что надо, это
Код:
FileOpen
FileReadLine
StringSplit

и еще пара функций.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
Когда-то и я столкнулся с такой же проблемой, попробовал использовать _IniString.
Проблема исчезла, но возникла другая :
_IniString некорректно не всегда корректно работает при наличии в INI-файле символов ,
имеющих специальное значение для РЕГ. ВЫРАЖЕНИЙ.
Корректно ввести экранирование метасимволов во все ф-ии _IniString не удалось.
Пришлось сделать аналоги с экранированием.
Вот в качестве примера :
Код:
Func _INI_ReadSection($sFile, $sSect)
	Local $sLine, $hFile, $aRet[1][2], $iSectExists=0

	$hFile = FileOpen($sFile, 0)

	If $hFile = -1 Then
		Return SetError(1, 0, 0)
	EndIf

	While 1
		$sLine = FileReadLine($hFile)
		If @error = -1 Then ExitLoop

			If $iSectExists=1 Then
				If StringRegExp($sLine, "\A\h*\[.*\]\h*\z") Then	
					ExitLoop
				EndIf
				$aRet[0][0]+=1
				ReDim $aRet[$aRet[0][0]+1][2]
				$aRet[$aRet[0][0]][0]=StringRegExpReplace($sLine, '(?i)\A(.*?)=.*\z', '\1' )
				$aRet[$aRet[0][0]][1]=StringRegExpReplace($sLine, '(?i)\A.*?=', '' )			
			ElseIf StringRegExp($sLine, "(?mi)^\h*\[\h*\Q" & $sSect & "\E\h*]\h*(\r\n|\z)") Then					
				$iSectExists=1
			EndIf
	WEnd
	FileClose($hFile)
	If $iSectExists=0 Then Return SetError(2, 0, 0)
	Return $aRet
EndFunc
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
А пример можно INI-файла, который некоректно читается?
Теоритически я представляю каждый элемент с новой строки, неважно, какие метасимволы применяются, в них всё равно нет переноса строки, а значит теоритически спотыкаться не начем, если только парсинг сделан криво.

Может сделать свой парсинг? Я представляю это не как работа с текстовыми данными в сыром виде, а как прербразование данных в массив и далее уже работа с форматированными данными. Это намного упрощает весь UDF, так как фактически требуется парсинг только один раз, а далее обращение к форматированным данным.
 
Автор
ynbIpb

ynbIpb

Скриптер
Сообщения
399
Репутация
110
Большое спасибо всем отписавшимся, функция _IniString_ReadSection подхватывает всю секцию, хотя ini файл очень необычный

Код:
#include <Array.au3>
#include <IniString.au3>

$sPS3_WillowGame_ini = @ScriptDir & "\PS3-WillowGame.ini"
$hPS3_WillowGame_ini = FileOpen ($sPS3_WillowGame_ini, 0+32)
$sPS3_WillowGame_ini_data = FileRead ($hPS3_WillowGame_ini)
FileClose ($hPS3_WillowGame_ini )
$aSection = _IniString_ReadSection ($sPS3_WillowGame_ini_data, "WillowGame.WillowUIDataStore_StringAliasMap")
If $aSection = 0 Then
   MsgBox (0, "", "Неудача!")
   Exit
EndIf
_ArrayDisplay($aSection)

Сам ini файл: PS3-WillowGame.ini, проблемная секция [WillowGame.WillowUIDataStore_StringAliasMap]

gregaz [?]
_IniString некорректно работает при наличии в INI-файле символов ,имеющих специальное значение для РЕГ. ВЫРАЖЕНИЙ.
В моём файле и скобки круглые, скобки квадратные, скобки угловые и дополнительные символы равно, кавычка одинарная и двойная. Вроде бы корректно обрабатывает.
Даже больше я написал скрипт, который полностью считывает и создаёт новый ini файл из этих данных, файлы получились идентичны.
Код:
#include <Array.au3>
#include <IniString.au3>

$sPS3_WillowGame_ini = @ScriptDir & "\PS3-WillowGame.ini"
$hPS3_WillowGame_ini = FileOpen ($sPS3_WillowGame_ini, 0+32)
$sPS3_WillowGame_ini_data = FileRead ($hPS3_WillowGame_ini)
FileClose ($hPS3_WillowGame_ini )
$aSectNames = _IniString_ReadSectionNames ($sPS3_WillowGame_ini_data)
$sNewINI = ""
For $i=1 To $aSectNames[0]
   $aSection = _IniString_ReadSection ($sPS3_WillowGame_ini_data, $aSectNames[$i])
   $sNewINI &= "[" & $aSectNames[$i] & "]" & @CRLF
   If $aSection[0][0] = 0 Then
	  $sNewINI &=@CRLF ; добавляем дополнительный символ переноса
   Else
	  For $j=1 To $aSection[0][0]
		 $sNewINI &= $aSection[$j][0] &"="& $aSection[$j][1] & @CRLF
		 If $j = $aSection[0][0] Then ; если параметр последний
			$sNewINI &=@CRLF ; добавляем дополнительный символ переноса
		 EndIf
	  Next
   EndIf
Next
$hFileToSave = FileOpen (@ScriptDir & "\PS3-WillowGame_new.ini", 2+32+8)
FileWrite ($hFileToSave, $sNewINI)
FileClose ($hFileToSave)


OffTopic:
AZJIO
В справке про _IniString_ReadSectionNames ошибка:
Успех: Возвращает массив, в котором $Array[0][0] = количество секций
Там же не двухмерный массив. должно быть $Array[0]
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO [?]
А пример можно INI-файла, который некоректно читается?
Это было давно, трудно вспомнить все моменты.
Но вот скажем попробуй повторно записать в ИНИ-файл параметр , в названии которого имеются скобки :
Код:
$sIniFileText="[TEST]" & @CRLF & _ 
	'Пример (_WinHttp)=SA'
MsgBox(4096, "Test", $sIniFileText)
_IniString_Write($sIniFileText,'TEST','Пример (_WinHttp)', 'SA')
MsgBox(4096, "Test", $sIniFileText)



Добавлено:
Сообщение автоматически объединено:

AZJIO [?]
Я представляю это не как работа с текстовыми данными в сыром виде, а как прербразование данных в массив и далее уже работа с форматированными данными. Это намного упрощает весь UDF, так как фактически требуется парсинг только один раз, а далее обращение к форматированным данным.

Может быть и есть смысл попробовать.
Только ИНИ-файлы для разных целей имеют разные особенности.Надо это учесть.
Например мне надо иметь и пустые секции. Здесь твой файл уже не работает корректно.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Например мне надо иметь и пустые секции. Здесь твой файл уже не работает корректно.
Москва не сразу строилась, вот ещё три функции обработки. Пока костяк далеается. Можно пока не говорить про удаление кавычек обрамляющих значение и об формате ошибок и возвращаемых значений.

IniVirtual
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO
На мой взгляд это упрощает обработку, однако приводит к заметному замедлению.
Видимо _ArraySearch работает медленнее чем Reg Exp.
Я попробовал запись на 300 kb -м файле.
Получил порядка 120-150 mc.
Тогда, как с помощью Reg Exp 40-70 mc.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Кстати скорость я тоже хотел увеличить. К примеру _IniString_Read содержит 4 регулярных выражения работающих в цикле и вызов функции с использованием ещё одного регулярного выражения. Итого 5 рег.выр. в цикле.


Добавлено:
Сообщение автоматически объединено:

gregaz
А можно тест в студию?
А то у меня результат другой, 0.6 мсек (мой), против 17 мсек - IniString.

Код:
#include <IniString.au3>
$sPath = @ScriptDir & "\Test.reg"
$sIniFileText = FileRead($sPath)

$timer = TimerInit()
$Res = _IniString_Read($sIniFileText, 'HKEY_CURRENT_USER\Software\GNU\ffdshow_audio\default', '"autoloadFreqs"', 'тест')
$timer = Round(TimerDiff($timer), 2) & ' мсек'
MsgBox(0, "Время " & $timer, $Res)


Код:
#include <IniVirtual.au3>
$sPath = @ScriptDir & "\Test.reg"
$sIniFileText = FileRead($sPath)
$a_Mem_Ini = _IniVirtual_Initial($sIniFileText)

$timer = TimerInit()
$Res = _IniVirtual_Read($sIniFileText, 'HKEY_CURRENT_USER\Software\GNU\ffdshow_audio\default', '"autoloadFreqs"', 'тест')
$timer = Round(TimerDiff($timer), 2) & ' мсек'
MsgBox(0, "Время " & $timer, $Res)


В качестве ini взял reg-файл экспортированный из указанного раздела реестра, параметр 270-ый по списку (последний).
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Проверил, у IniString - 25 мсек, ещё дольше, у меня по прежнему 0.6 мсек, хотя я этого и ожидал, так как операция идентичная - поиск по двум массивам, чтобы изменить переменную. Естественно я не брал в учёт инициализацию и сохранение в ini-файл, так как предполагаю, что эти операции будут при старте программы и при выходе.

Можно ещё попробовать Scripting.Dictionary - ассоциативный массив, тогда _ArraySearch в параметрах не понадобится и будет ещё быстрее.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO [?]
Естественно я не брал в учёт инициализацию и сохранение в ini-файл, так как предполагаю, что эти операции будут при старте программы и при выходе.

так это только для статичного ИНИ.
Если он изменяется то и чтение +инициализацию да и запись придется выполнять каждый раз.
Я как раз выполнял тест с учетом чтения +инициализации + записи отсюда и большое время
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Нашлась причина, переменные использовал не массив а прочитанную из файла, в общем 0.6 вместо 0.05.

gregaz
В общем результаты теста:
дано: ini-файл от KMPlayer, 1000 параметров в секции поиск последнего
IniString: 40 мсек
IniVirtual: 2 мсек
Scripting.Dictionary: 0.16 мсек

Но при использовании Scripting.Dictionary вариант от ynbIpb работать не будет, потому что там в секции ключи с одним именем, а Scripting.Dictionary повтор дубликата совсем не позволяет.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
Я тоже протестировал чтение параметра на уже готовом тексте ИНИ от Total Comander и получил подобное соотношение :
IniString: 8.5 ms
IniVirtual : 0.25 ms

Интересно, что даже с помощью одного рег. выражения (для полного текста)
Код:
$s_Text= FileRead($s_Ini)

$Timer=TimerInit()
$sPatern = "(?mi)^\[" & $sSection & "\]\r\n(?:^[^\[].+?\r\n)*^" & $sKey & "=(.+?)\r\n"
$aResult = StringRegExp( $s_Text, $sPatern, 1 )
MsgBox(4096, Round(TimerDiff($Timer), 2) & ' мсек', $aResult[0])


получаю время порядка 0.35 ms


Добавлено:
Сообщение автоматически объединено:

Конечно на фоне затрат времени на получение самого текста файла и его записи все эти значения малосущественны.
А уменьшить время чтения-записи вряд ли удастся.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
По вышеуказанной ссылке обновил, все функции уже есть осталось улучшать.

Для полного текста ini-файла я пробовал, параметр может взятся из другой секции, поэтому надо сначала получить контент секции и вней искать вторым рег.выр. По любому минимум два регулярных выражения, но всё же не 5 и не в цикле для каждой строки.

Конечно на фоне затрат времени на получение самого текста файла и его записи все эти значения малосущественны.
Пока все варианты не будут испробованы результат останется загадкой. Первоначальный партсинг немного тратит издержки на формарование массива, зато потом при каждом чтении/записи заметно уменшение, другой вариант будет чуть быстрее читать/сохранять файл, но при каждом доступе к данным будет парсить данные как будто первый раз загружает. Остался баланс, неужели данные после каждого изменения буду сохранятся? Если нет то пара запросов и скрипт на таком способе в общей сложности уже выигрывает.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO [?]
Остался баланс, неужели данные после каждого изменения буду сохранятся?
Все зависит от целей использования файла.
Мне надо постоянное сохранение данных.Использую ИНИ-файл в качестве базы Snippets.

Как вариант можно поступить таким образом :
Все операции выполнять с использованием виртуального массива, что значительно ускоряет работу скрипта.
Выполнять инициализацию массива только после полного завершения работы ф-ий (Write,Delete, Rename) и всех операций, связанных с ними. (Кстати эти функции используются гораздо реже ф-ий Read, ReadSection, ReadSectionNames)
Т.е. практически в паузе между операциями, где время не так критично.
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Как вариант можно ещё рассмотреть UDF - _Setting, есть в справке. При запуске программы можно импортировать INI в реестр и все записи делать в реестре (работать с реестром как с INI-файлом), а при выходе из программы сохранить данные из реестра в INI-файл, удалив источник в реестре. Если при запуске программа обнаруживает данные в реестре (например после сбоя), то предлагает их скопировать в INI.
Все эти операции уже поддерживаются в этом UDF.
 

gregaz

AutoIT Гуру
Сообщения
1,166
Репутация
299
AZJIO
OffTopic:
С реестром еще не работал, поэтому пара вопросов :
Какой обьем можно заносить?
Есть ли ограничение на размер значения параметра ? (У меня достаточно длинные значения)


Тогда надо будет в _Setting вносить изменения , используя IniVirtual или:
Код:
If FileGetSize($sIni) / 1024 <= 31 Then...





Добавлено:
Сообщение автоматически объединено:

Кстати в справке есть опечатки типа : #Include <_Setting.au3.au3>
 

AZJIO

Меценат
Меценат
Сообщения
2,892
Репутация
1,196
gregaz
Есть ли ограничение на размер значения параметра ?
Думаю у ini больше ограничения в разы, тут вроде даже задумываться не стоит. Симбиоз IniVirtual с _Setting пока не буду делать. Потому что _Setting проверена и используется в JumpReg, я её писал с полным представлением цели. Как я понял _Setting не может конвертировать в реестр из-за ограничения движка нативных функций? Я ещё не уверен что IniVirtual сейчас в правильном направлении развивается. У меня был вариант проверки не масив в массиве, а во второй колонке содерживое секций, которое будет парсится с помощью рег.выр., то есть попробовать варианты скорости разных комбинаций.


Добавлено:
Сообщение автоматически объединено:

gregaz
Вариант на регулярных выражениях пробую. Чтение 4 мсек (среднее между предыдущими). Плюсом является то, что коментарии в INI не будут затронуты.

IniVirtual_RegExp
 
Верх