Что вы на самом деле хотите сделать, так это выполнить итерацию по обоим файлам параллельно, а затем объединить части, принадлежащие друг другу.
Но вы не можете использовать номера строк, поскольку они могут различаться.Поэтому вам нужно использовать номер записи (блока).Таким образом, вам нужно дать ему «число» или, точнее, получить одну запись за другой из файла.
Итак, вам нужен итератор для данных, который может превратить некоторые строки вблок.
Таким образом, вместо:
foreach($first_file as $number => $line)
это
foreach($first_file_blocks as $number => $block)
Это можно сделать, написав свой собственный итератор, который принимает строку файла в качестве входных данных изатем преобразует линии в блоки на лету.Для этого вам нужно проанализировать данные, это небольшой пример парсера, основанного на состоянии, который может преобразовывать строки в блоки:
$state = 0;
$blocks = array();
foreach($lines as $line)
{
switch($state)
{
case 0:
unset($block);
$block = array();
$blocks[] = &$block;
$block['number'] = $line;
$state = 1;
break;
case 1:
$block['range'] = $line;
$state = 2;
break;
case 2:
$block['text'] = '';
$state = 3;
# fall-through intended
case 3:
if ($line === '') {
$state = 0;
break;
}
$block['text'] .= ($block['text'] ? "\n" : '') . $line;
break;
default:
throw new Exception(sprintf('Unhandled %d.', $state));
}
}
unset($block);
Он просто проходит по линиям и меняет свое состояние.На основе этого состояния каждая строка обрабатывается как часть своего блока.Если начинается новый блок, он будет создан.Он работает для файла SRT, который вы набросали в своем вопросе, demo .
Чтобы сделать его более гибким, превратите его в итератор, который занимает в конструкторе $lines
и предлагает блоки во время итерации.Это требует небольшого понимания того, как синтаксический анализатор заставляет строки работать, но он работает в основном одинаково.
class SRTBlocks implements Iterator
{
private $lines;
private $current;
private $key;
public function __construct($lines)
{
if (is_array($lines))
{
$lines = new ArrayIterator($lines);
}
$this->lines = $lines;
}
public function rewind()
{
$this->lines->rewind();
$this->current = NULL;
$this->key = 0;
}
public function valid()
{
return $this->lines->valid();
}
public function current()
{
if (NULL !== $this->current)
{
return $this->current;
}
$state = 0;
$block = NULL;
while ($this->lines->valid() && $line = $this->lines->current())
{
switch($state)
{
case 0:
$block = array();
$block['number'] = $line;
$state = 1;
break;
case 1:
$block['range'] = $line;
$state = 2;
break;
case 2:
$block['text'] = '';
$state = 3;
# fall-through intended
case 3:
if ($line === '') {
$state = 0;
break 2;
}
$block['text'] .= ($block['text'] ? "\n" : '') . $line;
break;
default:
throw new Exception(sprintf('Unhandled %d.', $state));
}
$this->lines->next();
}
if (NULL === $block)
{
throw new Exception('Parser invalid (empty).');
}
$this->current = $block;
$this->key++;
return $block;
}
public function key()
{
return $this->key;
}
public function next()
{
$this->lines->next();
$this->current = NULL;
}
}
Основное использование следующее, вывод можно увидеть в Demo :
$blocks = new SRTBlocks($lines);
foreach($blocks as $index => $block)
{
printf("Block #%d:\n", $index);
print_r($block);
}
Так что теперь можно перебирать все блоки в файле SRT.Единственное, что осталось сейчас, - это перебирать оба файла SRT параллельно.Начиная с PHP 5.3 SPL поставляется с MultipleIterator
, который делает это.Теперь все довольно просто, например, я использую одни и те же строки дважды:
$multi = new MultipleIterator();
$multi->attachIterator(new SRTBlocks($lines));
$multi->attachIterator(new SRTBlocks($lines));
foreach($multi as $blockPair)
{
list($block1, $block2) = $blockPair;
echo $block1['number'], "\n", $block1['range'], "\n",
$block1['text'], "\n", $block2['text'], "\n\n";
}
Хранить строку (вместо вывода) в файле довольно тривиально, поэтому я оставляю это вне ответа.
Так что же заметить?Во-первых, последовательные данные, такие как строки в файле, могут быть легко проанализированы в цикле и некотором состоянии.Это работает не только для строк в файле, но и для строк.
Во-вторых, почему я предложил здесь итератор?Во-первых, это просто в использовании.Это был лишь небольшой шаг от параллельной обработки одного файла до двух.Кроме того, итератор может работать и с другим итератором.Например, с классом SPLFileObject
.Он предоставляет итератор для всех строк в файле.Если у вас большие файлы, вы можете просто использовать SPLFileObject
(вместо массива), и вам не нужно будет сначала загружать оба файла в массивы, после небольшого добавления к SRTBlocks
, которое удаляет завершающие символы EOL с концакаждая строка:
$line = rtrim($line, "\n\r");
Это просто работает:
$multi = new MultipleIterator();
$multi->attachIterator(new SRTBlocks(new SplFileObject($file1)));
$multi->attachIterator(new SRTBlocks(new SplFileObject($file2)));
foreach($multi as $blockPair)
{
list($block1, $block2) = $blockPair;
echo $block1['number'], "\n", $block1['range'], "\n",
$block1['text'], "\n", $block2['text'], "\n\n";
}
Таким образом, вы можете обрабатывать даже очень большие файлы с (почти) одним и тем же кодом.Гибко, не так ли? Полная демонстрация .