2011-11-26 29 views
1

(事先说一句抱歉的长期问题 - 这个问题其实很简单 - 但要解释它也许不是那么简单)PHP - 合并两个TXT文件与条件

我在PHP noobie技能是通过这个挑战:

输入的2

用这样的结构TXT文件:

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

阅读上述,我的“半固定字符串的含义,意味着它是与已知结构的字符串,但是未知的内容..

给一个实际的例子,let's采取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 
. 
. 
. 

我想要做的,是从一个文件中获取$ content部分,并将其放在第二个文件的正确位置。

要回示例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); 

这将通过线做线 - 这是不是我所需要的。我需要的条件.. 也 - 我相信这不是一个聪明的代码,或者有很多更好的方法去 - 所以任何帮助,将不胜感激...

+0

用'把它放在正确的place'你的意思是交替行?或者是什么? – Jon

+0

我的意思是只放一个文件的内容$零件文件2的$内容后,没有influancing其他部件或结构...... – krembo99

回答

3

你实际上想要做的是并行迭代两个文件,然后合并彼此属于的部分。

但是你不能使用行号,因为这些可能会有所不同。所以你需要使用条目号(块)。所以你需要给它一个“数字”或更精确的,从文件中取出一个条目。

因此,您需要一个可以将某些行转换为块的问题数据的迭代器。

所以不是:

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); 

它只是沿着线运行,并改变它的状态。基于该状态,每条线都作为其块的一部分进行处理。如果一个新块开始,它将被创建。它适用于你的问题,你demo已经大纲的SRT文件。

为了它的使用更加灵活,把它变成一个迭代器,这需要$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"; 
} 

上述工作完成后,你可以处理甚至(几乎)同样的代码真正的大文件。灵活,不是吗? The full demonstration

+0

WOW。不管小问题会在后面描述,你的代码真是太棒了!像我这样一个新手可以学到只有看着这个代码,并从你的解释..你已经分割成“块”的逻辑描述的钱,我有自己的逻辑 - 但我并没有执行它的工具。但是小问题 - 是你的代码依赖于换行符(/ n)而不是结构基础。因此,如果一个文件有2个换行符,或者如果他们在文件之间或内部的文件本身不同 - 输出将是inccorect(看这里http://codepad.viper-7.com/3Naga1)。 - > – krembo99

+0

- >(继续由于缺乏评论空间抱歉。)。现在,你的代码对于像我这样的人来说太复杂了,尽管我理解他的逻辑非常好,就像我说的那样,我正在计划挖掘和学习你所展示的所有伟大的东西(大部分我都不知道存在!!) - 但在我原来的问题中,其中一个问题是我找到了一种定义“结构分界线”的方法,它并不总是基于换行符。这可以在CSV,CVF,SRT,CAL或任何其他具有已知结构的文件上使用 - 以某种方式定义结构本身。 – krembo99

+0

正如答案中所示,关键是您首先解析数据并给出以标准化的形式与数据接口。由于存储/读取通常涉及数据从一种表单导入到另一种表单(需要序列化来存储数据),这取决于数据的类型。所以你需要有一个规范化的表单和一种读/写格式的方法。由于格式可能很复杂,因此将读写封装到自己的类中是明智的。例如。 CSV阅读器,SRT Writer等。我的答案自己在你的问题中显示了格式的键/值对。 – hakre