PHP - объединить два файла TXT с условиями - PullRequest
1 голос
/ 26 ноября 2011

(заранее извините за длинный вопрос - проблема на самом деле проста - но объяснить это, возможно, не так просто)

Мои навыки нуби в PHP ставятся под сомнение:

Ввод 2 TXT-файлов со структурой, подобной этой:

$rowidentifier //number,letter,string etc..
$some semi-fixed-string $somedelimiter $semi-fixed-string
$content //with unknown length or strings or lines number.

, читая выше, мое значение в "полу-фиксированной строке" означает, что это строка со структурой KNOWN, но НЕИЗВЕСТНЫМ контентом ..

, чтобы привести практический пример, давайте возьмем файл SRT (я просто использую его как морскую свинку, поскольку структура очень похожа на то, что мне нужно):

1
00:00:12,759 --> 00:00:17,458
"some content here "
that continues here

2
00:00:18,298 --> 00:00:20,926
here we go again...

3
00:00:21,368 --> 00:00:24,565
...and this can go forever...

4
.
.
.

чтоЯ хочу сделать, это взять часть содержимого $ из одного файла и поместить его в правильное место во втором файле.

возвращаясь к примеру SRT, имеющему:

//file1 

    1
    00:00:12,759 --> 00:00:17,458
    "this is the italian content "
    which continues in italian here

    2
    00:00:18,298 --> 00:00:20,926
    here we go talking italian again ...

и

//file2 

    1
    00:00:12,756 --> 00:00:17,433
    "this is the spanish, chinese, or any content "
    which continues in spanish, or chinese here

    2
    00:00:16,293 --> 00:00:20,96
    here we go talking spanish, chinese or german again ...

приведут к

//file3 

        1
        00:00:12,756 --> 00:00:17,433
        "this is the italian content "
        which continues in italian here
        "this is the spanish, chinese, or any content "
        which continues in spanish, or chinese here

        2
        00:00:16,293 --> 00:00:20,96
        here we go talking italian again ...
        here we go talking spanish, chinese or german again ...

или более php, например:

$rowidentifier //unchanged
$some semi-fixed-string $somedelimiter $semi-fixed-string //unchanged, except maybe an option to choose if to keep file1 or file2 ...
$content //from file 1
$content //from file 2

, так что после всего этого введения - это то, что яиметь (что на самом деле ничего не значит ..)

$first_file = file('file1.txt'); // no need to comment right ?
$second_file = file('file2.txt'); // see above comment
$result_array = array(); /construct array
foreach($first_file as $key=>$value) //loop array and.... 
$result_array[]= trim($value).'/r'.trim($second_file[$key]); //..here is my problem ...

// $Value is $content - but LINE BY LINE , and in our case, it could be 2-3- or even 4 lines
// should i go by delimiters /n/r ??  (not a good idea - how can i know they are there ?? )
// or should i go for regex to lookup for string patterns ? that is insane , no ?

$fp = fopen('merge.txt', 'w+'); fwrite($fp, join("\r\n", $result_array); fclose($fp);

это будет делать построчно - это нешляпа мне нужнаМне нужны условия .. также - я уверен, что это не умный код, или что есть много лучших способов сделать это - так что любая помощь будет оценена ...

1 Ответ

3 голосов
/ 24 декабря 2011

Что вы на самом деле хотите сделать, так это выполнить итерацию по обоим файлам параллельно, а затем объединить части, принадлежащие друг другу.

Но вы не можете использовать номера строк, поскольку они могут различаться.Поэтому вам нужно использовать номер записи (блока).Таким образом, вам нужно дать ему «число» или, точнее, получить одну запись за другой из файла.

Итак, вам нужен итератор для данных, который может превратить некоторые строки вблок.

Таким образом, вместо:

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";
}

Таким образом, вы можете обрабатывать даже очень большие файлы с (почти) одним и тем же кодом.Гибко, не так ли? Полная демонстрация .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...