Мне нужно извлечь данные из текстового файла, отформатированного таким образом, используя PHP:
BEGIN
#1
#2
#3
#4
#5
#6
1 2015-05-31 2001-11-24 'Name Surname' ID_1 0
2 2011-04-01 ? ? ID_2 1
2 2013-02-24 ? ? ID_3 1
2 2014-02-28 ? 'Name Surname' ID_4 2
END
Информация организована с помощью логики массива, как показано ниже:
Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ? [#4] => ? [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ? [#4] => ? [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ? [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )
Я искал способ получить этот вывод. Я использую этот код:
<?php
//ini_set('max_execution_time', 300); //300 seconds = 5 minutes
function startsWith($str, $char){
return $str[0] === $char;
}
$txt_path = "./test.txt";
$txt_data = @file_get_contents($txt_path) or die("Could not access file: $txt_path");
//echo $txt_data;
$loop_pattern = "/BEGIN(.*?)END/s";
preg_match_all($loop_pattern, $txt_data, $matches);
$loops = $matches[0];
//print_r($loops);
$loops_count = count($loops);
//echo $loops_count; // number of loops into the file
foreach ($loops as $key => $value) {
$value = trim($value);
$pattern = array("/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/");
$replacement = array(" ", "", "");
$value = preg_replace($pattern, $replacement, $value);
//print_r($value);
//echo "<br><br>";
$value_array = explode("\n", $value);
$value_array_clean = array_filter($value_array, 'strlen');
$value_array_clean_reindex = array_values($value_array_clean);
//print_r($value_array_clean_reindex);
//echo "<br><br>";
$keys = array();
$values = array();
foreach ($value_array_clean_reindex as $key => $value) {
$value = trim($value);
if ( startsWith($value, "#") ) {
array_push($keys, $value);
$keys_count = count($keys);
} else {
array_push($values, $value);
$values_count = count($values);
$loop_dic = array();
foreach ($values as $key => $value) {
$value = trim($value);
preg_match_all("/'(?:.|[^'])*'|\S+/", $value, $matches);
//print_r($matches[0]);
$loop_dic = array_combine($keys, $matches[0]);
}
print_r($loop_dic);
echo "<br><br>";
}
}
}
?>
И это дает мне желаемый результат:
Array ( [#1] => 1 [#2] => 2015-05-31 [#3] => 2001-11-24 [#4] => 'Name Surname' [#5] => ID_1 [#6] => 0 )
Array ( [#1] => 2 [#2] => 2011-04-01 [#3] => ? [#4] => ? [#5] => ID_2 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2013-02-24 [#3] => ? [#4] => ? [#5] => ID_3 [#6] => 1 )
Array ( [#1] => 2 [#2] => 2014-02-28 [#3] => ? [#4] => 'Name Surname' [#5] => ID_4 [#6] => 2 )
Но иногда возникает проблема на уровне команды:
$loop_dic = array_combine($keys, $matches[0]);
Я понял, что в исходном текстовом файле, с очень длинными строками, они ломаются, создавая новую строку; вместо:
2 2014-02-28 ? 'Name Surname' ID_4 2
линия разорвана так:
2 2014-02-28 ? 'Name Surname'
ID_4 2
Итак, когда я взрываю строку \n
, возникает ошибка в длине двух массивов, которые затем я объединяю.
Я бы попросил вас решить эту проблему, получив массивы равной длины, в том числе и в оригинальном файле.
Поискав в сети, я нашел array_fill; может быть, если я знаю count
) количество ключей в его массиве для каждого цикла ([# 1], …, [# 6]), можно было бы зациклить и заполнить массивы для значений, добавляя их последовательно до максимальной длины каждого массива для ценности.
Спасибо за ваше внимание и помощь.
РЕДАКТИРОВАТЬ # 1
Спасибо @ fusion3k за его решение!
Проверка поведения с некоторыми входными файлами показывает две другие проблемы:
1) Анализируя некоторые ошибки, я обнаружил, что иногда входной файл использует двойные кавычки (вместо одинарные кавычки), и есть блоки текста в несколько строк между точка с запятой тоже вроде как:
;This is some text
in multiline with "double
quotes" too
;
это нужно рассматривать как одно значение для данного ключа, для которого значение должно быть встроенным, как это делает код @ fusion3k, заменяя \n
с (пространство). Я пытаюсь объединить рабочий код @ fusion3k с тем, который разработан для решения этой проблемы. Структура файла может быть такой:
BEGIN
#1
#2
#3
#4
#5
#6
1 2015-05-31 2001-11-24 "Name Surname" ID_1 0
2 2011-04-01 ? ? ID_2 1
2 2013-02-24 ? ? ID_3 1
2 2014-02-28 ? "Name Surname" ID_4 2
;This is some text
in multiline with "double
quotes" too
;
2016-01-22 ? "Name Surname" ID_5 2
END
который должен генерировать что-то вроде приведенного выше рабочего кода, но с учетом наличия различных разделителей текстовых блоков, таких как точка с запятой (;
), одинарные кавычки ('
) или, как в некоторых других файлах, двойные кавычки ("
), чтобы разграничить блок текста, который должен рассматриваться как одно значение для ключа, как в этом массиве относительно вышеупомянутого содержимого текстового файла:
Array ( [#1] => Array ( [0] => 1 [1] => 2 [2] => 2 [3] => 2 [4] => This is some text in multiline with "double quotes" too ) [#2] => Array ( [0] => 2015-05-31 [1] => 2011-04-01 [2] => 2013-02-24 [3] => 2014-02-28 [4] => 2016-01-22 ) [#3] => Array ( [0] => 2001-11-24 [1] => ? [2] => ? [3] => ? [4] => ? ) [#4] => Array ( [0] => Name Surname [1] => ? [2] => ? [3] => Name Surname [4] => Name Surname ) [#5] => Array ( [0] => ID_1 [1] => ID_2 [2] => ID_3 [3] => ID_4 [4] => ID_5 ) [#6] => Array ( [0] => 0 [1] => 1 [2] => 1 [3] => 2 [4] => 2 ) )
Я работал над простой строкой, чтобы найти «работающее» регулярное выражение, которое учитывает (точка с запятой) А ТАКЖЕ (одинарные кавычки ИЛИ ЖЕ двойные кавычки). На данный момент я не нашел файлов, которые используют все три разделителя для разделения блока текста, но, кажется, можно найти точка с запятой+single_quotes ИЛИ ЖЕ точка с запятой+двойные кавычки ИЛИ только single_quotes ИЛИ только двойные кавычки; было бы хорошо найти решение со всеми тремя типами разделителей в одном текстовом файле …:
$string = 'something here
;and there
;
oh, "that\'s all!"';
$string = str_replace( "\n", " ", $string );
$origin = array("/[[:blank:]]+/", "/\"/", "/;/");
$replacement = array(" ", "\" ", "; ");
$string = preg_replace($origin, $replacement, $string);
$pattern = '/([;"])\s+/';
print_r(array_filter(preg_split( $pattern, $string ), 'strlen'));
Это вывод (как хотел):
Array ( [0] => something here [1] => and there [2] => oh, [3] => that's all! )
Обратите внимание на текстовый блок между точка с запятой: начинается всегда с новой строки, с точка с запятой в начале, и это заканчивается точка с запятой в новой строке, после которой начинается другая новая строка.
Я не знаю, можно ли было написать это лучше и быстрее … Тогда я попытался объединить его с кодом @ fusion3k, работая над содержимым текстового файла выше, но безуспешно. Я пробовал if/elseif/else
построить так:
if ( preg_match('/;(.*?);|\'(.*?)\'/', $value, $matches) ) {// semicolon with single quotes in the $value string
$value = str_replace( "\n", " ", $value );
$origin = array("/[[:blank:]]+/", "/'/", "/;/");
$replacement = array(" ", "' ", "; ");
$value = preg_replace($origin, $replacement, $value);
$pattern = '/'.str_repeat( "([;'])\s+", count( $keys ) ).'/';
print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
echo "<br><br>";
} elseif ( preg_match('/;(.*?);|"(.*?)"/', $value, $matches) ) {// semicolon with double quotes in the $value string
$value = str_replace( "\n", " ", $value );
$origin = array("/[[:blank:]]+/", "/\"/", "/;/");
$replacement = array(" ", "\" ", "; ");
$value = preg_replace($origin, $replacement, $value);
$pattern = '/'.str_repeat( "([;\"])\s+", count( $keys ) ).'/';
print_r(array_filter(preg_split( $pattern, $value ), 'strlen')); // I would have an array of values of the same length of the array for the keys
echo "<br><br>";
} else {// neither single quotes (or double quotes) nor semicolon in the $value string
$pattern = '/'.str_repeat( "(\S+)\s+", count( $keys ) ).'/';
preg_match_all( $pattern, $value, $matches );
//print_r($matches);
//echo "<br><br>";
$loop_dic = array_combine( $keys, array_slice( $matches, 1 ) );
print_r( $loop_dic ); // this is good...maybe in a better way?
echo "<br><br>";
}
Единственный рабочий код — последний, который использует код @ fusion3k.
2) Второе поведение (вероятно, уже решено), происходит, когда файл очень большой. Команда:
$loop_pattern = "/BEGIN(.*?)END/s";
preg_match_all($loop_pattern, $txt_data, $matches);
$loops = $matches[0];
//print_r($loops);
$loops_count = count($loops);
//echo $loops_count; // number of loops into the file
не принимает все циклы в файлах (большой файл).
Вероятно, ответ Вот, Я думаю. Итак, настройка:
ini_set('max_execution_time', 300); // 300 seconds = 5 minutes
ini_set("pcre.backtrack_limit", "100000000"); // default 100k = "100000"
кажется, что это решает, но я не знаю, единственный ли это способ: действительно, если файл большой (17 МБ или больше), браузер немного не отвечает (я тестирую на Firefox до последней версии) ), до того, как страница закончила загрузку … Было бы хорошо проанализировать весь файл по блокам до его полного размера, может быть, но как это сделать?
Большое спасибо за ваше внимание и помощь
Чтобы решить вашу проблему, общий подход заключается в подсчете найденных совпадений и — если они меньше ключей — продолжайте цикл без повторной инициализации $loop_dic
,
Я предлагаю вам перевернутый подход: вместо того, чтобы разбивать строку за строкой, перед извлечением значений заменяйте символы новой строки пробелами: ваша структура строк достаточно прочна, чтобы разрешить такой подход, и вы знаете номер поля, поэтому этот подход должен работать.
Код вне основного foreach
петля не меняется. Точно так же код для извлечения текста обернут BEGIN ... END
нетронутый:
foreach( $loops as $key => $value )
{
$value = trim( $value );
$pattern = array( "/[[:blank:]]+/", "/BEGIN(.*)/", "/END(.*)/" );
$replacement = array( " ", "", "" );
$value = preg_replace( $pattern, $replacement, $value );
Чтобы получить ключи, мы используем preg_match_all()
затем удаляем относительные строки preg_replace()
:
preg_match_all( '/^#\d+/m', $value, $matches );
$keys = $matches[0];
$value = preg_replace( '/^#\d+\s*/m', '', $value );
Сейчас в $value
у нас есть только строки данных. Мы заменяем все символы новой строки пробелами:
$value = str_replace( "\n", " ", $value );
Затем мы строим шаблон строки, повторяя шаблон поля для номера ключа и извлекаем все строки preg_match_all()
:
$pattern = '/'.str_repeat( "('[^']+'|\S+)\s+", count( $keys ) ).'/';
preg_match_all( $pattern, $value, $matches );
В конце мы используем array_slice()
чтобы удалить глобальные совпадения, мы объединяем его с $keys
и мы получили желаемый результат. foreach
цикл можно замкнуть:
$values = array_combine( $keys, array_slice( $matches, 1 ) );
}
Основное различие между моими $values
и ваш $loop_dic
это в $values
Основной массив у вас есть столбцы, но если вы предпочитаете массив по строкам, вы можете легко преобразовать его.
Я протестировал код с множеством разных «ломаных линий», и он работает. Я предлагаю вам тщательно протестировать его с разными строками, чтобы убедиться, что он работает нормально при любых обстоятельствах.
Других решений пока нет …