| Total Complexity | 50 |
| Total Lines | 361 |
| Duplicated Lines | 0 % |
| Changes | 6 | ||
| Bugs | 0 | Features | 0 |
Complex classes like TemplateProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use TemplateProcessor, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 25 | class TemplateProcessor extends T |
||
| 26 | { |
||
| 27 | |||
| 28 | /** |
||
| 29 | * TemplateProcessor 构造器 |
||
| 30 | * |
||
| 31 | * @param string $documentTemplate Word 文件路径 |
||
| 32 | */ |
||
| 33 | public function __construct($documentTemplate) |
||
| 34 | { |
||
| 35 | parent::__construct($documentTemplate); |
||
| 36 | $this->_tempDocumentSettingPart = $this->readPartWithRels($this->_getSettingName()); |
||
| 37 | } |
||
| 38 | |||
| 39 | /** |
||
| 40 | * 获取 Word 的设置文件名 |
||
| 41 | * |
||
| 42 | * @return string |
||
| 43 | */ |
||
| 44 | protected function _getSettingName() |
||
| 45 | { |
||
| 46 | return 'word/settings.xml'; |
||
| 47 | } |
||
| 48 | |||
| 49 | /** |
||
| 50 | * Word 的设置部分 |
||
| 51 | * |
||
| 52 | * @var string |
||
| 53 | */ |
||
| 54 | protected $_tempDocumentSettingPart = ''; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * Word 的主体部分 |
||
| 58 | * |
||
| 59 | * @return string |
||
| 60 | */ |
||
| 61 | public function getMain() |
||
| 62 | { |
||
| 63 | return $this->tempDocumentMainPart; |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * 设置主体代码 |
||
| 68 | * |
||
| 69 | * @param string $main |
||
| 70 | * |
||
| 71 | * @return static |
||
| 72 | */ |
||
| 73 | public function setMain($main) |
||
| 74 | { |
||
| 75 | $this->tempDocumentMainPart = $main; |
||
| 76 | return $this; |
||
| 77 | } |
||
| 78 | |||
| 79 | /** |
||
| 80 | * 获取头部 |
||
| 81 | * |
||
| 82 | * @return string |
||
| 83 | */ |
||
| 84 | public function getHeaders() |
||
| 85 | { |
||
| 86 | return $this->tempDocumentHeaders; |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * 获取脚部 |
||
| 91 | * |
||
| 92 | * @return string |
||
| 93 | */ |
||
| 94 | public function getFooters() |
||
| 95 | { |
||
| 96 | return $this->tempDocumentFooters; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * 获取设置 |
||
| 101 | * |
||
| 102 | * @return string |
||
| 103 | */ |
||
| 104 | public function getSettings() |
||
| 105 | { |
||
| 106 | return $this->_tempDocumentSettingPart; |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * 搜索关键字 |
||
| 111 | * |
||
| 112 | * @param string $search 待搜索的关键字 |
||
| 113 | * @param string $part 搜索的字符串部分,如果是 null,则搜索整个 main 内容 |
||
| 114 | * |
||
| 115 | * @return integer 关键字位置 |
||
| 116 | */ |
||
| 117 | public function tagPos($search, $part = null) |
||
| 118 | { |
||
| 119 | $search = parent::ensureMacroCompleted($search); |
||
| 120 | if (null === $part) { |
||
| 121 | return strpos($this->tempDocumentMainPart, $search); |
||
| 122 | } else { |
||
| 123 | return strpos($part, $search); |
||
| 124 | } |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * 覆盖父类的 save 方法 |
||
| 129 | * |
||
| 130 | * @return string |
||
| 131 | */ |
||
| 132 | public function save() |
||
| 133 | { |
||
| 134 | foreach ($this->tempDocumentHeaders as $index => $xml) { |
||
| 135 | $this->savePartWithRels($this->getHeaderName($index), $xml); |
||
| 136 | } |
||
| 137 | |||
| 138 | $this->savePartWithRels($this->getMainPartName(), $this->tempDocumentMainPart); |
||
| 139 | $this->savePartWithRels($this->_getSettingName(), $this->_tempDocumentSettingPart); |
||
| 140 | |||
| 141 | foreach ($this->tempDocumentFooters as $index => $xml) { |
||
| 142 | $this->savePartWithRels($this->getFooterName($index), $xml); |
||
| 143 | } |
||
| 144 | |||
| 145 | $this->zipClass->addFromString($this->getDocumentContentTypesName(), $this->tempDocumentContentTypes); |
||
| 146 | |||
| 147 | // Close zip file |
||
| 148 | if (false === $this->zipClass->close()) { |
||
| 149 | throw new Exception('Could not close zip file.'); // @codeCoverageIgnore |
||
| 150 | } |
||
| 151 | |||
| 152 | return $this->tempDocumentFilename; |
||
| 153 | } |
||
| 154 | |||
| 155 | /** |
||
| 156 | * 获取块 |
||
| 157 | * |
||
| 158 | * @param string $string |
||
| 159 | * |
||
| 160 | * @return string |
||
| 161 | */ |
||
| 162 | private function __getBodyBlock($string) |
||
| 163 | { |
||
| 164 | if (preg_match('%(?i)(?<=<w:body>)[\s|\S]*?(?=</w:body>)%', $string, $matches)) { |
||
| 165 | return $matches[0]; |
||
| 166 | } else { |
||
| 167 | return ''; |
||
| 168 | } |
||
| 169 | } |
||
| 170 | |||
| 171 | /** |
||
| 172 | * 在某部分 XML 串中,将一个 Word 模板变量替换成表格,并合并到主文档上 |
||
| 173 | * |
||
| 174 | * @param string $documentPartXML 某段 XML 字串 |
||
| 175 | * @param string $var 变量名 |
||
| 176 | * @param array $array 只支持行列号的二维数组 |
||
| 177 | * @param array $mergeArray 合并单元格的数组,例如:['A1:B1', 'C1:C2'] |
||
| 178 | * @param array $styleArray 对应所有单元格的样式二维数组 |
||
| 179 | * |
||
| 180 | * @return void |
||
| 181 | */ |
||
| 182 | public function setTableFromPart($documentPartXML, $var, $array, $mergeArray = [], $styleArray = []) |
||
| 183 | { |
||
| 184 | if ($this->tagPos($var, $documentPartXML)) { |
||
| 185 | $phpWord = new PhpWord(); |
||
| 186 | $section = $phpWord->addSection(); |
||
| 187 | /** |
||
| 188 | * addTableStyle 有 bug…… |
||
| 189 | * @see https://github.com/PHPOffice/PHPWord/issues/629 |
||
| 190 | */ |
||
| 191 | $table = $section->addTable([ |
||
| 192 | 'unit' => TblWidth::PERCENT, |
||
| 193 | 'width' => 100 * 50, |
||
| 194 | 'borderColor' => '000000', |
||
| 195 | 'borderSize' => 9, |
||
| 196 | 'cellMarginLeft' => 150, |
||
| 197 | 'cellMarginRight' => 150, |
||
| 198 | ]); |
||
| 199 | $cindexs = []; |
||
| 200 | foreach ($array as $rowIndex => $row) { |
||
| 201 | // echo $rowIndex; 1 |
||
| 202 | $table->addRow(); |
||
| 203 | $i = 0; |
||
| 204 | if (empty($cindexs)) { |
||
| 205 | $cindexs = array_map(function ($cs) { |
||
| 206 | return Coordinate::columnIndexFromString($cs); |
||
| 207 | }, array_keys($row)); |
||
| 208 | } |
||
| 209 | foreach ($row as $c => $value) { |
||
| 210 | if ($i > 0) { |
||
| 211 | $i--; |
||
| 212 | continue; |
||
| 213 | } |
||
| 214 | // echo $c; A |
||
| 215 | $colIndex = Coordinate::columnIndexFromString($c); |
||
| 216 | $array = []; |
||
| 217 | $isContinue = false; |
||
| 218 | foreach ($mergeArray as $range) { |
||
| 219 | // 1,1 : 2,2 |
||
| 220 | list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($range); // A1:B2 |
||
| 221 | if ($rangeEnd[0] > $rangeStart[0]) { |
||
| 222 | if ($colIndex >= $rangeStart[0] && $colIndex <= $rangeEnd[0] && $rowIndex >= $rangeStart[1] && $rowIndex <= $rangeEnd[1]) { |
||
| 223 | $i = count(array_intersect(range($rangeStart[0], $rangeEnd[0]), $cindexs)) - 1; |
||
| 224 | $array = array_merge($array, ['gridSpan' => $i + 1]); |
||
| 225 | } |
||
| 226 | } |
||
| 227 | if ($rangeEnd[1] > $rangeStart[1]) { |
||
| 228 | if ($colIndex >= $rangeStart[0] && $colIndex <= $rangeEnd[0]) { |
||
| 229 | if ($rowIndex == $rangeStart[1]) { |
||
| 230 | $array = array_merge($array, ['vMerge' => 'restart']); |
||
| 231 | } elseif ($rowIndex > $rangeStart[1] && $rowIndex <= $rangeEnd[1]) { |
||
| 232 | $isContinue = true; |
||
| 233 | $array = array_merge($array, ['vMerge' => 'continue']); |
||
| 234 | } |
||
| 235 | } |
||
| 236 | } |
||
| 237 | if (!empty($array)) { |
||
| 238 | break; |
||
| 239 | } |
||
| 240 | } |
||
| 241 | $array = array_merge($array, [ |
||
| 242 | 'valign' => I::get($styleArray, $rowIndex . '.' . $c . '.valign', Jc::CENTER), |
||
| 243 | 'bgColor' => I::get($styleArray, $rowIndex . '.' . $c . '.bgColor'), |
||
| 244 | ]); |
||
| 245 | $alignment = I::get($styleArray, $rowIndex . '.' . $c . '.alignment', Jc::CENTER); |
||
| 246 | // 在高版本的 Word 里,不支持 JC::JUSTIFY 呢 |
||
| 247 | if (JC::JUSTIFY == $alignment) { |
||
| 248 | $alignment = JC::BOTH; |
||
| 249 | } |
||
| 250 | $cellStyle = I::get($styleArray, $rowIndex . '.' . $c); |
||
| 251 | if (true === $isContinue) { |
||
| 252 | $isContinue = false; |
||
| 253 | $table->addCell(null, $array); |
||
| 254 | } else { |
||
| 255 | $table->addCell(null, $array)->addText(Html::encode($value), $cellStyle, [ |
||
| 256 | 'alignment' => $alignment, |
||
| 257 | ]); |
||
| 258 | } |
||
| 259 | } |
||
| 260 | } |
||
| 261 | $objWriter = IOFactory::createWriter($phpWord); |
||
| 262 | $xml = $objWriter->getWriterPart('Document')->write(); |
||
| 263 | $this->replaceBlock($var, $this->__getBodyBlock($xml), $documentPartXML); |
||
| 264 | } |
||
| 265 | } |
||
| 266 | |||
| 267 | /** |
||
| 268 | * 在整个文档里,将一个 Word 模板变量替换成表格 |
||
| 269 | * |
||
| 270 | * @see https://github.com/PHPOffice/PHPWord/issues/1198 感谢提供思路 |
||
| 271 | * |
||
| 272 | * @param string $var 变量名 |
||
| 273 | * @param array $array 只支持行列号的二维数组 |
||
| 274 | * @param array $mergeArray 合并单元格的数组,例如:['A1:B1', 'C1:C2'] |
||
| 275 | * @param array $styleArray 对应所有单元格的样式二维数组 |
||
| 276 | * |
||
| 277 | * @return void |
||
| 278 | */ |
||
| 279 | public function setTable($var, $array, $mergeArray = [], $styleArray = []) |
||
| 280 | { |
||
| 281 | $this->setTableFromPart($this->tempDocumentMainPart, $var, $array, $mergeArray, $styleArray); |
||
| 282 | } |
||
| 283 | |||
| 284 | /** |
||
| 285 | * 在某部分 XML 串中,将一个 Word 模板变量替换成列表,并合并到主文档上 |
||
| 286 | * |
||
| 287 | * @param string $documentPartXML 某段 XML 字串 |
||
| 288 | * @param string $var 变量名,如 `list`,在 word 里应该写:${list}${/list} |
||
| 289 | * @param array $array 一维数组 |
||
| 290 | * @param int $depth 列表层级,从 0 开始。默认 0 |
||
| 291 | * |
||
| 292 | * @return void |
||
| 293 | */ |
||
| 294 | public function setListFromPart($documentPartXML, $var, $array, $depth = 0) |
||
| 295 | { |
||
| 296 | if ($this->tagPos($var, $documentPartXML)) { |
||
| 297 | $phpWord = new PhpWord(); |
||
| 298 | $section = $phpWord->addSection(); |
||
| 299 | foreach ($array as $item) { |
||
| 300 | $section->addListItem($item, $depth); |
||
| 301 | } |
||
| 302 | $objWriter = IOFactory::createWriter($phpWord); |
||
| 303 | $xml = $objWriter->getWriterPart('Document')->write(); |
||
| 304 | $this->replaceBlock($var, $this->__getBodyBlock($xml), $documentPartXML); |
||
| 305 | } |
||
| 306 | } |
||
| 307 | |||
| 308 | /** |
||
| 309 | * 在整个文档里,将一个 Word 模板变量替换成列表 |
||
| 310 | * |
||
| 311 | * @param string $var 变量名,如 `list`,在 word 里应该写:${list}${/list} |
||
| 312 | * @param array $array 一维数组 |
||
| 313 | * @param int $depth 列表层级,从 0 开始。默认 0 |
||
| 314 | * |
||
| 315 | * @todo 给列表加样式 |
||
| 316 | * |
||
| 317 | * @return void |
||
| 318 | */ |
||
| 319 | public function setList($var, $array, $depth = 0) |
||
| 320 | { |
||
| 321 | $this->setListFromPart($this->tempDocumentMainPart, $var, $array, $depth); |
||
| 322 | } |
||
| 323 | |||
| 324 | /** |
||
| 325 | * 是否强制提示更新字段(用于更新目录) |
||
| 326 | * |
||
| 327 | * @param boolean $isUpdate |
||
| 328 | * |
||
| 329 | * @return void |
||
| 330 | */ |
||
| 331 | public function setIsUpdateFields($isUpdate = true) |
||
| 332 | { |
||
| 333 | if (preg_match('/w:val=\"TOC\"/', $this->tempDocumentMainPart)) { |
||
| 334 | $string = $isUpdate ? 'true' : 'false'; |
||
| 335 | $matches = null; |
||
| 336 | if (preg_match('/<w:updateFields w:val=\"(true|false)\"\/>/', $this->_tempDocumentSettingPart, $matches)) { |
||
| 337 | $this->_tempDocumentSettingPart = str_replace($matches[0], '<w:updateFields w:val="' . $string . '"/>', $this->_tempDocumentSettingPart); |
||
| 338 | } else { |
||
| 339 | $this->_tempDocumentSettingPart = str_replace('</w:settings>', '<w:updateFields w:val="' . $string . '"/></w:settings>', $this->_tempDocumentSettingPart); |
||
| 340 | } |
||
| 341 | } |
||
| 342 | } |
||
| 343 | |||
| 344 | /** |
||
| 345 | * 替换块标签 |
||
| 346 | * 注:原方法因为贪婪模式可能无法正确匹配对应的块 |
||
| 347 | * |
||
| 348 | * @param string $blockname 变量名 |
||
| 349 | * @param string $replacement 替换字符串 |
||
| 350 | * @param string $documentPartXML xml 字符串,默认整个文档的 |
||
| 351 | * |
||
| 352 | * @return void |
||
| 353 | */ |
||
| 354 | public function replaceBlock($blockname, $replacement, $documentPartXML = null) |
||
| 355 | { |
||
| 356 | null === $documentPartXML && $documentPartXML = $this->tempDocumentMainPart; |
||
| 357 | // PHP7.0~7.2 会有 bug 导致匹配不到结果,例子参见 samples/php7preg_bug.php |
||
| 358 | Regular::jitOff(); |
||
| 359 | preg_match( |
||
| 360 | '/(<w:p ((?!<w:p ).)*?\${' . $blockname . '}.*?<\/w:p>)(.*?)(<w:p ((?!<w:p ).)*\${\/' . $blockname . '}.*?<\/w:p>)/is', |
||
| 361 | $documentPartXML, |
||
| 362 | $matches |
||
| 363 | ); |
||
| 364 | if (isset($matches[1])) { |
||
| 365 | $part = $this->setValueForPart($matches[1] . $matches[3] . $matches[4], $replacement, $documentPartXML, parent::MAXIMUM_REPLACEMENTS_DEFAULT); |
||
| 366 | $this->tempDocumentMainPart = $this->setValueForPart($documentPartXML, $part, $this->tempDocumentMainPart, parent::MAXIMUM_REPLACEMENTS_DEFAULT); |
||
| 367 | } |
||
| 368 | } |
||
| 369 | |||
| 370 | /** |
||
| 371 | * 在某段 XML 中替换变量,并合并到主文档上 |
||
| 372 | * |
||
| 373 | * @param string $documentPartXML 某段 XML 字串 |
||
| 374 | * @param string $search 待搜索的变量名 |
||
| 375 | * @param string $replace 替换的值 |
||
| 376 | * @param int $limit 替换次数,默认-1,表示替换全部 |
||
| 377 | * |
||
| 378 | * @return void |
||
| 379 | */ |
||
| 380 | public function setValueFromPart($documentPartXML, $search, $replace, $limit = parent::MAXIMUM_REPLACEMENTS_DEFAULT) |
||
| 381 | { |
||
| 382 | if ($this->tagPos($search, $documentPartXML)) { |
||
| 383 | $part = $this->setValueForPart(static::ensureMacroCompleted($search), static::ensureUtf8Encoded($replace), $documentPartXML, $limit); |
||
| 384 | $this->tempDocumentMainPart = $this->setValueForPart($documentPartXML, $part, $this->tempDocumentMainPart, $limit); |
||
| 385 | $this->setValue($search, $replace, $limit); |
||
| 386 | } |
||
| 390 |