I suppose if you are seeking line-by-line .ini file validation with actionable diagnostic detail, then I would avoid trying to re-invent the parser's intricacies/quirks via a regex approach.
Split your .ini file on newline characters, loop that array of strings, attempt to parse each line, handle each parsing attempt with some deliberate condition statements to offer insights about anomalies.
Code: (Demo)
$ini = <<<INI
TPL_CONTACT_MISC="Miscellaneous Info"
date.timezone = Pacific/Tahiti
TPL_CONTACT_MISC="Your german misc info translation"
COM_TODO_ALIGN="Image Float"
JOOMLA! DEFAULT IMAGES=Жуумла! үндсэн зурагнууд
COM_TODO_ALIGN_DESC="This will apply the classes 'pull-left', 'pull-center' or 'pull-right' to the '<figure>' or '<img>' element"
COM_TODO_BROWSE_FILES="Browse files"
COM_TODO_CAPTION="Caption"
COM_TODO_CAPTION_CLASS_LABEL="Caption Class"
COM_MEMBERSHIPS_FIELD_MEMBERSHIP_LABEL 911
COM_FRED_GREETING='Hello'
COM_FRED_GREETING_BARNEY='Yo, Buddy'
DR_SOLD="SOLD"
upload_tmp_dir = /var/www/html/tmp
INI;
$result = [];
foreach (explode(PHP_EOL, $ini) as $i => $line) {
$parsed = @parse_ini_string($line);
if (!is_array($parsed)) {
$anomalies[] = "Invalid character(s) @ line index $i: $line";
continue;
}
$key = key($parsed);
if ($key === null) {
$anomalies[] = "No valid declaration @ line index $i: $line";
} elseif (key_exists($key, $result)) {
$anomalies[] = "Redundant key declaration @ line index $i: $key => $parsed[$key]";
} else {
$result[$key] = $parsed[$key];
}
}
var_export($anomalies);
echo "\n---\n";
var_export($result);
Output:
array (
0 => 'Redundant key declaration @ line index 2: TPL_CONTACT_MISC => Your german misc info translation',
1 => 'Invalid character(s) @ line index 4: JOOMLA! DEFAULT IMAGES=Жуумла! үндсэн зурагнууд',
2 => 'No valid declaration @ line index 9: COM_MEMBERSHIPS_FIELD_MEMBERSHIP_LABEL 911',
)
---
array (
'TPL_CONTACT_MISC' => 'Miscellaneous Info',
'date.timezone' => 'Pacific/Tahiti',
'COM_TODO_ALIGN' => 'Image Float',
'COM_TODO_ALIGN_DESC' => 'This will apply the classes \'pull-left\', \'pull-center\' or \'pull-right\' to the \'<figure>\' or \'<img>\' element',
'COM_TODO_BROWSE_FILES' => 'Browse files',
'COM_TODO_CAPTION' => 'Caption',
'COM_TODO_CAPTION_CLASS_LABEL' => 'Caption Class',
'COM_FRED_GREETING' => 'Hello',
'COM_FRED_GREETING_BARNEY' => 'Yo, Buddy',
'DR_SOLD' => 'SOLD',
'upload_tmp_dir' => '/var/www/html/tmp',
)
In your actual application, replace explode(PHP_EOL, $ini)
with a file($myFilePath)
call.
If we are talking more specifically about Joomla's language .ini files, then additional key and value validation conditions should be added to align with Joomla's version-specific parser requirements (e.g. support for dot in keys and missing double quotes around values). More reading at: Creating a language definition file
After a bit more effort to align with Joomla's parser restrictions: (Demo)
$anomalies = [];
$result = [];
foreach (explode(PHP_EOL, $ini) as $i => $line) {
$parsed = @parse_ini_string($line);
if (!is_array($parsed)) {
$anomalies[] = "Invalid character(s) @ line index $i: $line";
continue;
}
$key = key($parsed);
if ($key === null) {
$anomalies[] = "No valid declaration @ line index $i: $line";
} elseif (!mb_check_encoding($key, 'ASCII')) {
$anomalies[] = "Non-ASCII character found in key @ line index $i: $line";
} elseif (!preg_match('/^[A-Z\d_-]+/', $key)) {
$anomalies[] = "Key is expected to contain only uppercase letters, numbers, underscores, and hyphens @ line index $i: $line";
} elseif (!preg_match('/^[A-Z\d_-]+="(.*)"$/', $line, $m)) {
$anomalies[] = "Value not wrapped in double quotes @ line index $i: $line";
} elseif (preg_match('/(?<!\\\\)(?:\\\\\\\\)*"/', $m[1])) {
$anomalies[] = "Value contains unescaped double quote @ line index $i: $line";
} elseif (key_exists($key, $result)) {
$anomalies[] = "Redundant key declaration @ line index $i: $key => $result[$key] **overwritten by** $parsed[$key]";
$result[$key] = $parsed[$key]; // mimic Joomla parser behaviour
} else {
$result[$key] = $parsed[$key];
}
}
var_export($anomalies);
echo "\n---\n";
var_export($result);