2011-11-26 32 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