close

問題:

PHPWord的cloneRow功能當執行超過100行之後執行效率會急速下降,一千筆要等一分多鐘,資料筆數太多的時候根本無法使用!

 

解法:

其實在這個issue close時也沒有完全解法,是兩年後2017年最後一個留言才把這個問題解決,而且到現在2025年PHPWord最新版1.4.0仍然沒有解決這個問題XDDD

只能自己改寫cloneRow這個function

 

首先,感謝作者們完成這個偉大的項目,並對我的英語能力表示歉意。

我認為在處理大量資料時會出現一個設計問題(順便說一句,在許多任務中處理數百或數十行資料是很常見的)。

在我看來,問題在於值是在行被複製之後才被加入的。對我來說,問題在於每次添加(克隆)一行,處理時間就會增加兩倍:

  • 還有一個搜尋/替換操作要做。
  • 每次搜尋/取代要處理的 XML 資料量都會變大,
    這表示如果要處理的行數為 n,則處理時間將約為 n^2 (n*n)。
    使用 str_replace 或將搜尋/替換值作為數組可以稍微加快速度,但處理時間仍將增加約 n^2 (n*n)

解決方案可能是在克隆時進行搜尋/替換,這樣,搜尋/替換操作將僅針對克隆的部分進行,而不是針對整個文件。處理時間將增加 n,而非 n^2。
這樣,它可以在不到一秒的時間內產生數千行資料…

我修改了 TemplateProcessor::cloneRow,以便它可以接受帶有值參數的陣列並在克隆時執行替換工作。


    public function cloneRow($search, $numberOfClones, $arRepl="", $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT)
    {
        if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) {
            $search = '${' . $search . '}';
        }

        $tagPos = strpos($this->tempDocumentMainPart, $search);
        if (!$tagPos) {
            throw new Exception("Can not clone row, template variable not found or variable contains markup.");
        }

        $rowStart = $this->findRowStart($tagPos);
        $rowEnd = $this->findRowEnd($tagPos);
        $xmlRow = $this->getSlice($rowStart, $rowEnd);

        // Check if there's a cell spanning multiple rows.
        if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) {
            // $extraRowStart = $rowEnd;
            $extraRowEnd = $rowEnd;
            while (true) {
                $extraRowStart = $this->findRowStart($extraRowEnd + 1);
                $extraRowEnd = $this->findRowEnd($extraRowEnd + 1);

                // If extraRowEnd is lower then 7, there was no next row found.
                if ($extraRowEnd < 7) {
                    break;
                }

                // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row.
                $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd);
                if (!preg_match('#<w:vMerge/>#', $tmpXmlRow) &&
                    !preg_match('#<w:vMerge w:val="continue" />#', $tmpXmlRow)) {
                    break;
                }
                // This row was a spanned row, update $rowEnd and search for the next row.
                $rowEnd = $extraRowEnd;
            }
            $xmlRow = $this->getSlice($rowStart, $rowEnd);
        }

        $result = $this->getSlice(0, $rowStart);
        
        // 原始寫法:
        // $result .= implode($this->indexClonedVariables($numberOfClones, $xmlRow));
        
        // 新寫法:
        if(is_array($arRepl))
		for ($i = 1; $i <= $numberOfClones; $i++) {
		    $ar_search=array_keys($arRepl[$i]);
		    $ar_repl=array_values($arRepl[$i]);
		    
		    foreach ($ar_search as &$item) $item=self::ensureMacroCompleted($item);
		    foreach ($ar_repl as &$item) $item = self::ensureUtf8Encoded($item);
		   
		    $result .= $this->setValueForPart($ar_search, $ar_repl, $xmlRow, $limit);
		    //$result .= str_replace($ar_search, $ar_repl, $xmlRow);
		}
	else
		for ($i = 1; $i <= $numberOfClones; $i++) {
		    $result .= preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlRow);
		}
		
        $result .= $this->getSlice($rowEnd);

        $this->tempDocumentMainPart = $result;
    }
    

 

只需透過傳遞數組數組作為第三個參數來調用它:

$array_row_values=array(
	1 => array("row1" => "value 1", "row2" => "value 2" /*etc...*/ ), //values for first line
	2 => array("row1" => "value 1", "row2" => "value 2" /*etc...*/ ), //values for second line
	3 => array("row1" => "value 1", "row2" => "value 2" /*etc...*/ ), //values for third line
	/* ect.. */
);

$templateProcessor->cloneRow('rowFieldName', 1000 /*or greater*/, $array_row_values);

 

並且在克隆時將直接進行替換。

第四個參數(限制)完全是可選的。

如果你以正常方式呼叫它(只有兩個參數),我仍然會相信正常方式(只克隆行,不應用值)

我使用以下程式碼對其進行了測試(使用範例 7 中的範本)


echo date('H:i:s'), ' Creating new TemplateProcessor instance...';
$templateProcessor = new \PhpOffice\PhpWord\TemplateProcessor('Sample_07.docx');

// Variables on different parts of document
$templateProcessor->setValue('weekday', date('l'));            // On section/content
$templateProcessor->setValue('time', date('H:i'));             // On footer
$templateProcessor->setValue('serverName', realpath(__DIR__)); // On header

$nbRSimple=1000;
$nbRcomplexe=1000;


$arValues_ClonesSimples=array();
for ($i=1; $i<=$nbRSimple; $i++)
	$arValues_ClonesSimples[$i]=array(
		"rowValue" => rand(10000000, 99999999),
		"rowNumber" => $i
	);
	

// Simple table
$templateProcessor->cloneRow('rowValue', $nbRSimple, $arValues_ClonesSimples);

$arValues_ClonesComplexes=array();
for ($i=1; $i<=$nbRSimple; $i++)
	$arValues_ClonesComplexes[$i]=array(
		"userId" => rand(10000000, 99999999),
		"userFirstName" => rand(10000000, 99999999),
		"userName" => rand(10000000, 99999999),
		"userPhone" => rand(10000000, 99999999),
	);

$templateProcessor->cloneRow('userId', $nbRcomplexe, $arValues_ClonesComplexes);
	

echo date('H:i:s'), ' Saving the result document...';
$templateProcessor->saveAs('Sample_07_out.docx');

 

--

轉自 https://github.com/PHPOffice/PHPWord/issues/513

 

全站熱搜
創作者介紹
創作者 dizzy03 的頭像
dizzy03

碎碎念

dizzy03 發表在 痞客邦 留言(0) 人氣()