Заполняйте массивы последовательно до достижения максимальной длины

Мне нужно извлечь данные из текстового файла, отформатированного таким образом, используя 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 до последней версии) ), до того, как страница закончила загрузку … Было бы хорошо проанализировать весь файл по блокам до его полного размера, может быть, но как это сделать?

Большое спасибо за ваше внимание и помощь

1

Решение

Чтобы решить вашу проблему, общий подход заключается в подсчете найденных совпадений и — если они меньше ключей — продолжайте цикл без повторной инициализации $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 ) );
}

Ideone демо

Основное различие между моими $values и ваш $loop_dic это в $values Основной массив у вас есть столбцы, но если вы предпочитаете массив по строкам, вы можете легко преобразовать его.

Я протестировал код с множеством разных «ломаных линий», и он работает. Я предлагаю вам тщательно протестировать его с разными строками, чтобы убедиться, что он работает нормально при любых обстоятельствах.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]