問題:
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
文章標籤
全站熱搜
