├── .buildpath ├── .gitattributes ├── .gitignore ├── Autoloader.php ├── LICENSE.txt ├── README-CN.md ├── README.md ├── composer.json ├── src ├── Api │ ├── Base.php │ ├── GetWord.php │ └── ParseWord.php ├── Common │ ├── Bind.php │ ├── Build.php │ ├── Common.php │ ├── Log.php │ ├── PartBase.php │ └── Tree.php ├── Config │ └── Main.php ├── Edit │ └── Part │ │ ├── Charts.php │ │ ├── Comments.php │ │ ├── Document.php │ │ ├── Excel.php │ │ ├── Footer.php │ │ ├── Header.php │ │ ├── Numbering.php │ │ ├── Rels.php │ │ └── Styles.php ├── Read │ ├── Part │ │ └── ContentTypes.php │ └── Word.php ├── WordProcessor.php └── XmlTemple │ ├── XmlFromPhpword.php │ └── image.xml └── tests ├── Trace.php ├── function.php └── samples ├── api ├── index.php ├── r-temple.docx ├── simple data.csv └── temple.docx ├── block ├── index.php ├── r-temple.docx └── temple.docx ├── break page ├── index.php └── temple.docx ├── clone ├── index.php ├── r-temple.docx └── temple.docx ├── clonetable ├── img.png ├── index.php ├── r-temple.docx └── temple.docx ├── image ├── full.png ├── img.jpg ├── img2.bmp ├── img3.png ├── index.php ├── r-temple.docx ├── temple.docx ├── temple │ └── full.xml └── words.png ├── memory use ├── index.php ├── logo.jpg ├── word config.png ├── word result.png └── word.gif ├── merge table cells ├── index.php ├── r-temple.docx └── temple.docx ├── performance ├── 1750pages.docx ├── index.php ├── r-1750pages.docx ├── r-temple.docx └── temple.docx ├── phpword ├── index.php ├── logo.jpg ├── r-temple.docx └── temple.docx ├── remain comments ├── index.php ├── r-temple.docx └── temple.docx ├── simple for readme ├── img.png ├── index.php ├── logo.jpg ├── r-temple.docx ├── temple.docx ├── word config.png ├── word result.png └── word.gif ├── text ├── index.php ├── r-temple.docx └── temple.docx ├── toc ├── index.php ├── r-temple.docx └── temple.docx └── wps ├── index.php ├── r-word-temple.docx ├── r-wps-temple.docx ├── word-temple.docx └── wps-temple.docx /.buildpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | tests/tools/ export-ignore 4 | tests/Output/ export-ignore 5 | tests/Log/ export-ignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/* 2 | .project 3 | *.prefs 4 | temp-* 5 | 6 | tests/Output/* 7 | tests/Log/*.log 8 | 9 | -------------------------------------------------------------------------------- /Autoloader.php: -------------------------------------------------------------------------------- 1 | load($template); 39 | 40 | //赋值 41 | $TemplateProcessor->setValue('value', 'r-value'); 42 | 43 | //克隆并复制 44 | $TemplateProcessor->clones('people', 3); 45 | 46 | $TemplateProcessor->setValue('name#0', 'colin0'); 47 | $TemplateProcessor->setValue('name#1', [ 48 | ['text'=>'colin1','style'=>'style','type'=>MDWORD_TEXT], 49 | ['text'=>1,'type'=>MDWORD_BREAK], 50 | ['text'=>'86','style'=>'style','type'=>MDWORD_TEXT] 51 | ]); 52 | $TemplateProcessor->setValue('name#2', 'colin2'); 53 | 54 | $TemplateProcessor->setValue('sex#1', 'woman'); 55 | 56 | $TemplateProcessor->setValue('age#0', '280'); 57 | $TemplateProcessor->setValue('age#1', '281'); 58 | $TemplateProcessor->setValue('age#2', '282'); 59 | 60 | //图片复制 61 | $TemplateProcessor->setImageValue('image', dirname(__FILE__).'/logo.jpg'); 62 | 63 | //删除某行 64 | $TemplateProcessor->deleteP('style'); 65 | 66 | //保存 67 | $rtemplate = __DIR__.'/r-temple.docx'; 68 | $TemplateProcessor->saveAs($rtemplate); 69 | ``` 70 | 71 | + ### 结果 72 | ![image](https://user-images.githubusercontent.com/12422458/111026037-1d95a400-8423-11eb-81e2-941f6b854e34.png) 73 | 74 | + ### 动图 75 | ![image](https://user-images.githubusercontent.com/12422458/111026041-1ec6d100-8423-11eb-8e14-d8daf99a9704.gif) 76 | 77 | ## 性能情况([统计脚本](https://github.com/mkdreams/MDword/blob/main/tests/samples/performance/index.php)) 78 | | 测试项 | 用时(S) | 79 | | ---- | ---- | 80 | | 1页母版赋值100次 | 0.04 | 81 | | 1页母版赋值500次 | 0.16 | 82 | | 1页母版赋值1000次 | 0.33 | 83 | | 1页母版赋值10000次 | 7.80 | 84 | | 1750页母版赋值100次 | 4.61 | 85 | | 1750页母版赋值500次 | 4.94 | 86 | | 1750页母版赋值1000次 | 5.43 | 87 | | 1750页母版赋值10000次 | 17.39 | 88 | 89 | ## 内存使用情况([统计脚本](https://github.com/mkdreams/MDword/blob/main/tests/samples/memory%20use/index.php)) 90 | | 连续运行第几次 | 累积内存使用情况 | 备注 | 91 | | ---- | ---- | ---- | 92 | | 1 | 0.050590515136719 M | 首次需要加载PHP类 | 93 | | 2 | 0.050949096679688 M | | 94 | | 3 | 0.050949096679688 M | | 95 | | 4 | 0.050949096679688 M | | 96 | | 5 | 0.050949096679688 M | | 97 | | 6 | 0.050949096679688 M | | 98 | | 7 | 0.050949096679688 M | | 99 | | 8 | 0.050949096679688 M | | 100 | 101 | 102 | ## 更多案例 103 | - [简单的综合案例](https://github.com/mkdreams/MDword/tree/main/tests/samples/simple%20for%20readme) 104 | - [带式样的文字](https://github.com/mkdreams/MDword/tree/main/tests/samples/text) 105 | - [添加图片](https://github.com/mkdreams/MDword/tree/main/tests/samples/image) 106 | - [克隆](https://github.com/mkdreams/MDword/tree/main/tests/samples/clone) 107 | - [多种方式设置区块,解决无法添加批注问题](https://github.com/mkdreams/MDword/tree/main/tests/samples/block) 108 | - [PHPWORD写入到区块](https://github.com/mkdreams/MDword/tree/main/tests/samples/phpword) 109 | - [目录嵌入到表格](https://github.com/mkdreams/MDword/tree/main/tests/samples/toc) 110 | - [合并表格单元格](https://github.com/mkdreams/MDword/tree/main/tests/samples/merge%20table%20cells) 111 | 112 | 113 | 114 | ## 交流 115 | ### 请备注:MDword交流 116 | ![image](https://user-images.githubusercontent.com/12422458/111025926-5a14d000-8422-11eb-86a3-db8a0ad712f0.png) 117 | 118 | 119 | ## [项目计划](https://github.com/mkdreams/MDword/projects/1#column-10318470) 120 | 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MDword 2 | ## [中文文档](https://github.com/mkdreams/MDword/blob/main/README-CN.md) 3 | 4 | ## Project General Name 5 | Template: a word which will be revised. 6 | Block: the part which will be replaced or cloned. 7 | 8 | ## Project Introduction 9 | Main Use: to generate word dynamically. 10 | Advantage: focus only on dynamic data and logic, rather than adjusting the style, which can modulate the template with the help of office word. 11 | 12 | ## Comparison between MDword & PHPword 13 | + ### Similarities 14 | 1. PHP Package. 15 | 2. Both can be used to generate office word. 16 | 17 | + ### Differences 18 | 1. PHPword concentrates on writing elements one by one. However, 19 | it is more powerful and efficient for Mdword just to revise them on the template. 20 | 2. For updating text styles, adding covers, headers and footers, MDword just modifies the template by office word, while PHPword complicate the task-adjusting every element. 21 | 3. Directories(Table of content) can be automatically generated. 22 | 23 | ## Tutotials 24 | + ### Installation 25 | ``` 26 | // Method 1 27 | composer require mkdreams/mdword 28 | // Method 2, Autoloading Template 29 | require_once('Autoloader.php'); 30 | ``` 31 | 32 | + ### Add annotations or use “${value/}” to the template. Please note that there is a “/” at the end. 33 | ![image](https://user-images.githubusercontent.com/12422458/111026036-1c647700-8423-11eb-9df2-e9a2e5530007.png) 34 | + ### Invocation Methods (more and richer approaches, for example: [tests\samples\simple for readme](https://github.com/mkdreams/MDword/blob/main/tests/samples/simple%20for%20readme/index.php), such as catalog, sequence number, etc.) 35 | ``` 36 | // New class,load template 37 | $TemplateProcessor = new WordProcessor(); 38 | $template = 'temple.docx'; 39 | $TemplateProcessor->load($template); 40 | 41 | // Set Value 42 | $TemplateProcessor->setValue('value', 'r-value'); 43 | 44 | // Clone 45 | $TemplateProcessor->clones('people', 3); 46 | 47 | $TemplateProcessor->setValue('name#0', 'colin0'); 48 | $TemplateProcessor->setValue('name#1', [ 49 | ['text'=>'colin1','style'=>'style','type'=>MDWORD_TEXT], 50 | ['text'=>1,'type'=>MDWORD_BREAK], 51 | ['text'=>'86','style'=>'style','type'=>MDWORD_TEXT] 52 | ]); 53 | $TemplateProcessor->setValue('name#2', 'colin2'); 54 | 55 | $TemplateProcessor->setValue('sex#1', 'woman'); 56 | 57 | $TemplateProcessor->setValue('age#0', '280'); 58 | $TemplateProcessor->setValue('age#1', '281'); 59 | $TemplateProcessor->setValue('age#2', '282'); 60 | 61 | // set value for image 62 | $TemplateProcessor->setImageValue('image', dirname(__FILE__).'/logo.jpg'); 63 | 64 | // Delete a paragraph 65 | $TemplateProcessor->deleteP('style'); 66 | 67 | // Save 68 | $rtemplate = __DIR__.'/r-temple.docx'; 69 | $TemplateProcessor->saveAs($rtemplate); 70 | ``` 71 | 72 | + ### Result 73 | ![image](https://user-images.githubusercontent.com/12422458/111026037-1d95a400-8423-11eb-81e2-941f6b854e34.png) 74 | 75 | + ### GIFs 76 | ![image](https://user-images.githubusercontent.com/12422458/111026041-1ec6d100-8423-11eb-8e14-d8daf99a9704.gif) 77 | 78 | 79 | ## More samples 80 | - [Simple Comprehhensive cases](https://github.com/mkdreams/MDword/tree/main/tests/samples/simple%20for%20readme) 81 | 82 | - [Formatted texts](https://github.com/mkdreams/MDword/tree/main/tests/samples/text) 83 | 84 | - [Add images](https://github.com/mkdreams/MDword/tree/main/tests/samples/image) 85 | 86 | - [Clone](https://github.com/mkdreams/MDword/tree/main/tests/samples/clone) 87 | 88 | - [Many ways to set blocks, solving the lack of notes](https://github.com/mkdreams/MDword/tree/main/tests/samples/block) 89 | 90 | - [Write element that is written by PHPword to the block](https://github.com/mkdreams/MDword/tree/main/tests/samples/phpword) 91 | 92 | - [Put TOC in a table](https://github.com/mkdreams/MDword/tree/main/tests/samples/toc) 93 | - [Merge table cells](https://github.com/mkdreams/MDword/tree/main/tests/samples/merge%20table%20cells) 94 | 95 | ## Communication 96 | ### Note: Exchange idea on MDword. 97 | ![image](https://user-images.githubusercontent.com/12422458/111025926-5a14d000-8422-11eb-86a3-db8a0ad712f0.png) 98 | 99 | ## [Project Plans](https://github.com/mkdreams/MDword/projects/1#column-10318470) 100 | 101 | 102 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mkdreams/mdword", 3 | "description": "OFFICE WORD 动态数据 绑定数据 生成报告;OFFICE WORD Dynamic data binding data generation report.", 4 | "license": "Apache-2.0", 5 | "authors": [ 6 | { 7 | "name": "colin", 8 | "email": "chenlincolin@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.6.0" 13 | }, 14 | "require-dev": { 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "MDword\\": "src" 19 | } 20 | }, 21 | "minimum-stability": "dev" 22 | } 23 | -------------------------------------------------------------------------------- /src/Api/Base.php: -------------------------------------------------------------------------------- 1 | common = new Common(); 14 | 15 | $this->parseParameters(); 16 | $this->wordProcessor = new WordProcessor(); 17 | $this->wordProcessor->load($this->parameters['docUrl']); 18 | } 19 | 20 | private function parseParameters() { 21 | $this->parameters = [ 22 | 'docUrl'=>MDWORD_TEST_DIRECTORY.'/samples/api/temple.docx', 23 | 'datas'=>[ 24 | 'data1'=>[ 25 | 'type'=>'json', 26 | 'dataInfo'=>[ 27 | 'type'=>'local', 28 | 'data'=>json_encode([ 29 | ['price'=>100,'change'=>5,'changepercent'=>0.05], 30 | ['price'=>200,'change'=>-10,'changepercent'=>-0.05], 31 | ['price'=>500,'change'=>100,'changepercent'=>0.20], 32 | ]) 33 | ], 34 | ], 35 | 'data2'=>[ 36 | 'type'=>'json', 37 | 'dataInfo'=>[ 38 | 'type'=>'http', 39 | 'url'=>'http://restapi2.farseerbi.com/bds/ipoNews?accessToken=3eZF3JrHUtbd5kXc1CVVrLAQf7XUJVWs', 40 | 'headers'=>'', 41 | 'postData'=>['rows'=>10,'sLanguage'=>'TC','page'=>1], 42 | ], 43 | ], 44 | 'data3'=>[ 45 | 'type'=>'csv', 46 | 'dataInfo'=>[ 47 | 'type'=>'http', 48 | 'url'=>MDWORD_TEST_DIRECTORY.'/samples/api/simple data.csv', 49 | 'headers'=>'', 50 | 'postData'=>'', 51 | ], 52 | ], 53 | 'data4'=>[ 54 | 'type'=>'xml' 55 | ], 56 | ], 57 | 'binds'=>[ 58 | 'item'=>[ 59 | 'dataKeyName'=>'data1', 60 | 'keyList'=>[], 61 | 'childrens'=>[ 62 | 'stockprice'=>[ 63 | 'keyList'=>['price'], 64 | ], 65 | 'change'=>[ 66 | 'keyList'=>['change'], 67 | ], 68 | 'changepercent'=>[ 69 | 'keyList'=>['changepercent'], 70 | ], 71 | ] 72 | ], 73 | 'rows'=>[ 74 | 'dataKeyName'=>'data2', 75 | 'keyList'=>['rows'], 76 | 'childrens'=>[ 77 | 'title'=>[ 78 | 'keyList'=>['newsSubj'], 79 | ], 80 | 'company name'=>[ 81 | 'keyList'=>['stockName'], 82 | ], 83 | 'date'=>[ 84 | 'keyList'=>['newsDate'], 85 | ], 86 | ] 87 | ], 88 | 'rows2'=>[ 89 | 'dataKeyName'=>'data3', 90 | 'keyList'=>[], 91 | 'childrens'=>[ 92 | 'type2'=>[ 93 | 'keyList'=>[0], 94 | ], 95 | 'title2'=>[ 96 | 'keyList'=>[1], 97 | ], 98 | 'influence2'=>[ 99 | 'keyList'=>[6], 100 | ], 101 | 'date2'=>[ 102 | 'keyList'=>[3], 103 | ], 104 | ] 105 | ], 106 | 'two'=>2, 107 | 'box'=>'BOX', 108 | 'header'=>'MDWORD-HEADER', 109 | 'footer'=>'MDWORD-FOOTER', 110 | ] 111 | ]; 112 | } 113 | 114 | protected function error($text) { 115 | header('Content-Type:application/json; charset=utf-8'); 116 | $data = ['data'=>null,'messages'=>['message'=>$text,'type'=>'json'],'success'=>false]; 117 | exit(json_encode($data)); 118 | } 119 | 120 | protected function success($values,$text='') { 121 | header('Content-Type:application/json; charset=utf-8'); 122 | $data = ['data'=>$values,'messages'=>['message'=>$text,'type'=>'json'],'success'=>true]; 123 | exit(json_encode($data)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Api/GetWord.php: -------------------------------------------------------------------------------- 1 | parameters['binds']; 8 | 9 | $this->bind($binds); 10 | return $this->wordProcessor->saveAsContent(); 11 | } 12 | 13 | private function bind($binds,$pbind = null,$pBindName = null, $first = true) { 14 | foreach($binds as $blockName => $value) { 15 | if($first) { 16 | $pbind = null; 17 | } 18 | 19 | if(is_array($value)) { 20 | if(isset($value['dataKeyName'])) { 21 | /** 22 | * @var Bind $pbind 23 | */ 24 | $pbind = $this->wordProcessor->getBind($this->getData($value['dataKeyName'])); 25 | } 26 | 27 | if(is_null($pbind)) { 28 | $this->wordProcessor->setValue($blockName, $value); 29 | }else{ 30 | $pbind->bindValue($blockName, $value['keyList'], $pBindName); 31 | if(isset($value['childrens'])) { 32 | $this->bind($value['childrens'],$pbind,$blockName,false); 33 | } 34 | } 35 | }else{ 36 | $this->wordProcessor->setValue($blockName, $value); 37 | } 38 | } 39 | } 40 | 41 | private function getData($name) { 42 | static $datas = []; 43 | if(isset($datas[$name])) { 44 | return $datas[$name]; 45 | } 46 | 47 | if(isset($this->parameters['datas'][$name])) { 48 | return $this->parseData($this->parameters['datas'][$name]); 49 | } 50 | 51 | return null; 52 | } 53 | 54 | 55 | private function parseData($data) { 56 | $dataInfo = $data['dataInfo']; 57 | switch ($dataInfo['type']) { 58 | case 'local': 59 | return $this->formatConvert($dataInfo['data'],$data['type']); 60 | break; 61 | case 'http': 62 | if($dataInfo['headers'] === '' && $dataInfo['postData'] === '') { 63 | $content = file_get_contents($dataInfo['url']); 64 | }else{ 65 | $content = $this->common->curlSend($dataInfo['url'],$dataInfo['headers'],$dataInfo['postData']); 66 | } 67 | return $this->formatConvert($content,$data['type']); 68 | break; 69 | case 'excel': 70 | $data = $this->common->curlSend($data['url'],$data['headers'],$data['postData']); 71 | $data = json_decode($data,true); 72 | return $data; 73 | break; 74 | } 75 | } 76 | 77 | private function formatConvert($content,$orgFormat='json') { 78 | switch ($orgFormat) { 79 | case 'json': 80 | return json_decode($content,true); 81 | break; 82 | case 'csv': 83 | return $this->parseCsv($content); 84 | break; 85 | } 86 | } 87 | 88 | private function parseCsv($content) { 89 | $rows = explode("\r", $content); 90 | foreach($rows as $index => $row) { 91 | $row = trim($row); 92 | if($row === '') { 93 | continue; 94 | } 95 | $rows[$index] = explode(',', $row); 96 | } 97 | 98 | $data = []; 99 | foreach($rows as $index => $row) { 100 | if($index === 0) { 101 | continue; 102 | } 103 | 104 | foreach ($row as $key => $value) { 105 | $data[$index][$key] = $value; 106 | } 107 | } 108 | 109 | return $data; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Api/ParseWord.php: -------------------------------------------------------------------------------- 1 | wordProcessor->getBlockList(); 7 | $this->success($blocks); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Common/Bind.php: -------------------------------------------------------------------------------- 1 | wordProcessor = $wordProcessor; 20 | $this->data = $data; 21 | $this->pre = $pre; 22 | } 23 | 24 | public function bindValue($name,$keyList,$pBindName=null,$callbackOrValueType=null,$emptyCallBack=null) { 25 | //loop 26 | if(!is_null($pBindName) && isset(self::$binds[$pBindName])) { 27 | foreach(self::$binds[$pBindName] as $bind) { 28 | $bind->bindValue($name,$keyList,null,$callbackOrValueType,$emptyCallBack); 29 | } 30 | 31 | return $this; 32 | } 33 | 34 | if('INNER_VARS' !== $pBindName) { 35 | $data = $this->data; 36 | }else{ 37 | $data = $this->wordProcessor->getInnerVars(); 38 | } 39 | foreach($keyList as $key) { 40 | $data = $data[$key]; 41 | } 42 | 43 | if(is_array($data)) { 44 | $count = count($data); 45 | $this->wordProcessor->clones($name.$this->pre,$count); 46 | $i = 0; 47 | foreach($data as $subData) { 48 | if(!isset(self::$binds[$name])) { 49 | self::$binds[$name] = []; 50 | } 51 | self::$binds[$name][] = new Bind($this->wordProcessor, $subData, $this->pre.'#'.$i++); 52 | } 53 | 54 | if($count === 0 && !is_null($emptyCallBack)) { 55 | $this->wordProcessor->cloneTo($name.$this->pre,$emptyCallBack($data,$this->data),$count); 56 | } 57 | }else{ 58 | $type = MDWORD_TEXT; 59 | if(!is_null($callbackOrValueType)) { 60 | if(is_callable($callbackOrValueType)) { 61 | $data = $callbackOrValueType($data,$this->data,$this->pre); 62 | if(isset($data['overrideType'])) { 63 | $type = $data['overrideType']; 64 | $data = $data['overrideValue']; 65 | } 66 | }elseif(is_int($callbackOrValueType)) { 67 | $type = $callbackOrValueType; 68 | } 69 | } 70 | $this->wordProcessor->setValue($name.$this->pre,$data,$type); 71 | } 72 | 73 | return $this; 74 | } 75 | } -------------------------------------------------------------------------------- /src/Common/Build.php: -------------------------------------------------------------------------------- 1 | log = new Log(); 9 | } 10 | 11 | public function getDirFiles($dir,$callback=null) { 12 | if(!is_dir($dir)) { 13 | $this->log->writeLog('dir is not a dir! dir:'.$dir); 14 | return []; 15 | } 16 | $filesTemp = scandir($dir); 17 | $files = []; 18 | foreach ($filesTemp as $fileName){ 19 | if($fileName != '.' && $fileName != '..'){ 20 | if(!is_null($callback)) { 21 | $fileName = $callback($dir,$fileName); 22 | } 23 | $files[] = $fileName; 24 | } 25 | } 26 | 27 | 28 | return $files; 29 | } 30 | 31 | public function curlSend($url,$headers='',$post=[],$timeoutMs=30000) { 32 | $ch = curl_init(); 33 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);//https 34 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);//https 35 | curl_setopt($ch, CURLOPT_URL, $url); 36 | curl_setopt($ch, CURLOPT_HEADER, 0); 37 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 38 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 39 | curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeoutMs); 40 | 41 | if(!empty($post)) { 42 | curl_setopt($ch, CURLOPT_POST, 1);//post方式提交 43 | if(is_array($post)) 44 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));//要提交的信息 45 | else 46 | curl_setopt($ch, CURLOPT_POSTFIELDS, $post);//要提交的信息 47 | 48 | } 49 | 50 | $rs = curl_exec($ch); //执行cURL抓取页面内容 51 | curl_close($ch); 52 | 53 | 54 | return $rs; 55 | } 56 | } -------------------------------------------------------------------------------- /src/Common/Log.php: -------------------------------------------------------------------------------- 1 | logPath = dirname(dirname(dirname(__FILE__))).'/tests/Log/'.date('Ymd').'.log'; 9 | } 10 | 11 | public function writeLog($content) { 12 | if(!MDWORD_DEBUG) { 13 | return ; 14 | } 15 | 16 | $result = file_put_contents($this->logPath, date('H:i').' '.$content."\r\n", FILE_APPEND); 17 | if($result === false) { 18 | die('write file error! file:'.$this->logPath); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/Common/PartBase.php: -------------------------------------------------------------------------------- 1 | rootPath = dirname(__DIR__); 40 | 41 | $this->word = $word; 42 | } 43 | 44 | public function initNameSpaces() { 45 | $context = $this->DOMDocument->documentElement; 46 | $xpath = new \DOMXPath($this->DOMDocument); 47 | foreach( $xpath->query('namespace::*', $context) as $node ) { 48 | $this->xmlns[$node->localName] = $node->nodeValue; 49 | } 50 | } 51 | 52 | public function getExist($item,$name) { 53 | $itemTemps = $item->getElementsByTagName($name); 54 | 55 | if($itemTemps->length === 0) { 56 | return false; 57 | }else{ 58 | return true; 59 | } 60 | } 61 | 62 | public function getVal($item,$name,$ns='w') { 63 | $itemTemps = $item->getElementsByTagName($name); 64 | 65 | if($itemTemps->length === 0) { 66 | return null; 67 | }elseif($itemTemps->length === 1) { 68 | return $this->getAttr($itemTemps->item(0),'val',$ns); 69 | }else{ 70 | //todo 71 | } 72 | } 73 | 74 | public function getAttr($item,$name,$ns='w') { 75 | if(isset($this->xmlns[$ns])) { 76 | return $item->getAttributeNS($this->xmlns[$ns],$name); 77 | }else{ 78 | return $item->getAttribute($name); 79 | } 80 | } 81 | 82 | public function setAttr($item,$name,$value,$ns='w') { 83 | if(isset($this->xmlns[$ns])) { 84 | return $item->setAttributeNS($this->xmlns[$ns],$name,$value); 85 | }else{ 86 | return $item->setAttribute($name,$value); 87 | } 88 | } 89 | 90 | public function createElementNS($item,$name,$value,$ns='w') { 91 | if(isset($this->xmlns[$ns])) { 92 | return $this->DOMDocument->createElementNS($this->xmlns[$ns],$name,$value); 93 | }else{ 94 | return $this->DOMDocument->createElement($name,$value); 95 | } 96 | } 97 | 98 | public function hasAttr($item,$name,$ns='w') { 99 | return $item->hasAttributeNS($this->xmlns[$ns],$name); 100 | } 101 | 102 | public function removeAttr($item,$name) { 103 | $item->removeAttribute($name); 104 | } 105 | 106 | 107 | public function __get($name) { 108 | return $this->$name; 109 | } 110 | 111 | public function markDelete($item) { 112 | if(!is_null($item)) { 113 | $item->setAttribute('md',(++$this->id)); 114 | } 115 | } 116 | 117 | protected function removeMarkDelete($item) { 118 | if(!is_null($item)) { 119 | $item->removeAttribute('md'); 120 | } 121 | } 122 | 123 | public function deleteMarked() { 124 | $this->deleteByXpath('//*[@md]'); 125 | } 126 | 127 | 128 | public function deleteByXpath($xpath) { 129 | $DOMXPath = new \DOMXPath($this->DOMDocument); 130 | $context = $this->DOMDocument->documentElement; 131 | $DOMXPath->registerNamespace('w', $this->xmlns['w']); 132 | $nodes = $DOMXPath->query($xpath, $context); 133 | foreach( $nodes as $node ) { 134 | $node->parentNode->removeChild($node); 135 | } 136 | } 137 | 138 | public function insertAfter($copy,$targetNode) { 139 | if($nextSibling = $targetNode->nextSibling) { 140 | if($parentNode = $nextSibling->parentNode) { 141 | $parentNode->insertBefore($copy,$nextSibling); 142 | } 143 | }else{ 144 | if($parentNode = $targetNode->parentNode) { 145 | $parentNode->appendChild($copy); 146 | } 147 | } 148 | } 149 | 150 | public function appendChild($parentNode,$targetNode) { 151 | $parentNode->appendChild($targetNode); 152 | } 153 | 154 | public function insertBefore($copy,$targetNode) { 155 | if($parentNode = $targetNode->parentNode) { 156 | $parentNode->insertBefore($copy,$targetNode); 157 | } 158 | } 159 | 160 | public function removeChild($item) { 161 | $parentNode = $item->parentNode; 162 | $parentNode->removeChild($item); 163 | } 164 | 165 | function pathRelToAbs($RelUrl, $PrefixUrl = '', $SuffixUrl = '') 166 | { 167 | $RelUrlRep = str_replace('\\', '/', $RelUrl); 168 | 169 | $UrlArr = explode('/', $RelUrlRep); 170 | 171 | $NewUrlArr = array(); 172 | 173 | foreach ($UrlArr as $value) { 174 | 175 | if ($value == '..' && ! empty($NewUrlArr)) { 176 | 177 | array_pop($NewUrlArr); 178 | } else if ($value != '..' && $value != '.' && $value != '') { 179 | 180 | $NewUrlArr[] = $value; 181 | } 182 | } 183 | 184 | $UrlStr = ! empty($NewUrlArr) ? implode('/', $NewUrlArr) : '/'; 185 | 186 | return $PrefixUrl . $UrlStr . $SuffixUrl; 187 | } 188 | 189 | public static function getTempDir() 190 | { 191 | $tempDir = sys_get_temp_dir(); 192 | 193 | if (!empty(self::$tempDir)) { 194 | $tempDir = self::$tempDir; 195 | } 196 | 197 | return $tempDir; 198 | } 199 | 200 | protected function parserRange($range) { 201 | $rangeArr = preg_split('/\!|\:/', $range); 202 | $count = count($rangeArr); 203 | $rangInfo = []; 204 | $match = []; 205 | if($count == 2) { 206 | $rangInfo[0] = $rangeArr[0]; 207 | preg_match('/([a-z]+?)[\$]*?([0-9]+?)/i', $rangeArr[1],$match); 208 | $rangInfo[1] = []; 209 | $rangInfo[2] = []; 210 | $rangInfo[1][0] = $match[1]; 211 | $rangInfo[2][0] = $match[2]; 212 | }elseif($count == 3) { 213 | $rangInfo[0] = $rangeArr[0]; 214 | preg_match('/([a-z]+?)[\$]*?([0-9]+?)/i', $rangeArr[1],$match); 215 | $rangInfo[1] = []; 216 | $rangInfo[2] = []; 217 | $rangInfo[1][0] = $match[1]; 218 | $rangInfo[2][0] = $match[2]; 219 | 220 | preg_match('/([a-z]+?)[\$]*?([0-9]+?)/i', $rangeArr[2],$match); 221 | $rangInfo[1][1] = $match[1]; 222 | $rangInfo[2][1] = $match[2]; 223 | }else{ 224 | var_dump('parserRange 数量不对!'); 225 | } 226 | 227 | $rangInfo[3] = $range; 228 | return $rangInfo; 229 | } 230 | 231 | protected function initRels($xmlType=19) { 232 | $partInfo = pathinfo($this->partName); 233 | $partNameRel = $partInfo['dirname'].'/_rels/'.$partInfo['basename'].'.rels'; 234 | $this->rels = new Rels($this->word, $this->word->getXmlDom($partNameRel)); 235 | $this->rels->partName = $partNameRel; 236 | $this->rels->partInfo = $partInfo; 237 | $this->word->parts[$xmlType][] = ['PartName'=>$this->rels->partName,'DOMElement'=>$this->rels->DOMDocument]; 238 | } 239 | 240 | public function createNodeByXml($xmlname,$callback=null) 241 | { 242 | $filename = MDWORD_SRC_DIRECTORY.'/XmlTemple/'.$xmlname.'.xml'; 243 | if(is_file($filename)) { 244 | $xml = file_get_contents($filename); 245 | }else{ 246 | $xml = $xmlname; 247 | } 248 | 249 | if(strpos($xml, '' 251 | .$xml.''; 252 | } 253 | 254 | 255 | $dom = new \DOMDocument(); 256 | $dom->loadXML($xml,LIBXML_NOBLANKS); 257 | 258 | if($callback === null) { 259 | return $this->DOMDocument->importNode($dom->documentElement->firstChild,true); 260 | }else{ 261 | return $this->DOMDocument->importNode($callback($dom->documentElement),true); 262 | } 263 | } 264 | 265 | protected function initChartRels($relArr) { 266 | $partInfo = pathinfo($relArr['PartName']); 267 | $this->rels = new Rels($this->word, $relArr['dom']); 268 | $this->rels->partName = $relArr['relName']; 269 | $this->rels->partInfo = $partInfo; 270 | 271 | $this->word->parts[19][] = ['PartName'=>$this->rels->partName,'DOMElement'=>$this->rels->DOMDocument]; 272 | } 273 | 274 | 275 | protected function initCommentRange() { 276 | if(isset($this->DOMDocument->documentElement->tagList['w:commentRangeStart'])) { 277 | $commentRangeStartItems = $this->DOMDocument->documentElement->tagList['w:commentRangeStart']; 278 | }else{ 279 | $commentRangeStartItems = []; 280 | } 281 | $tempBlocks = []; 282 | foreach($commentRangeStartItems as $commentRangeStartItem) { 283 | $id = $this->getAttr($commentRangeStartItem, 'id'); 284 | $commentRangeEndItem = $this->getCommentRangeEnd($this->DOMDocument,$id); 285 | $name = $this->commentsblocks[$id]; 286 | $traces = $this->getRangeTrace($id,$commentRangeStartItem, $commentRangeEndItem); 287 | if(!isset($tempBlocks[$name])) { 288 | $tempBlocks[$name] = []; 289 | $size = 0; 290 | }else{ 291 | $size = sizeof($tempBlocks[$name])+1; 292 | } 293 | 294 | $tempOneBlock = array_map(function($trace) use ($name,$size) { 295 | $this->domIdxToName[$trace->idxBegin][] = [$size,$name]; 296 | return $trace->idxBegin; 297 | }, $traces); 298 | 299 | if(strpos($id,'r') === 0) { 300 | foreach($tempOneBlock as $nodeId) { 301 | if(!isset($this->domList[$nodeId])) { 302 | continue; 303 | } 304 | $ts = $this->domList[$nodeId]->getElementsByTagName('t'); 305 | foreach($ts as $t) { 306 | $t->nodeValue = ''; 307 | } 308 | } 309 | } 310 | 311 | $tempBlocks[$name][] = $tempOneBlock; 312 | } 313 | 314 | return $tempBlocks; 315 | } 316 | 317 | protected function treeToList($node) { 318 | static $index = 0; 319 | 320 | if(is_null($node)) { 321 | return $index; 322 | } 323 | $node->idxBegin = $index; 324 | $node->tagList = []; 325 | $this->domList[$index++] = $node; 326 | if(($node->hasChildNodes())) { 327 | foreach($node->childNodes as $childNode) { 328 | if($childNode->nodeType !== 3) { 329 | $node->tagList[$childNode->tagName][] = $childNode; 330 | $tags = $this->treeToList($childNode); 331 | foreach($tags as $tag => $vals) { 332 | foreach($vals as $val) { 333 | $node->tagList[$tag][] = $val; 334 | if ($tag === 'a:blip') { 335 | $rId = $this->getAttr($val,'r:embed'); 336 | $this->rIdToNode[$rId] = $val; 337 | } 338 | } 339 | } 340 | } 341 | } 342 | } 343 | 344 | $node->idxEnd = $index-1; 345 | 346 | return $node->tagList; 347 | } 348 | 349 | public function treeToListCallback($node,$callback) { 350 | if(is_null($node)) { 351 | return ; 352 | } 353 | 354 | if(($node->hasChildNodes())) { 355 | foreach($node->childNodes as $childNode) { 356 | if($childNode->nodeType !== 3) { 357 | $this->treeToListCallback($callback($childNode),$callback); 358 | } 359 | } 360 | } 361 | } 362 | 363 | public function getTextContent($nodes) { 364 | $text = ''; 365 | $this->treeToListCallback($nodes,function($node) use (&$text) { 366 | //jump delete 367 | if($this->getAttr($node,'md',null)) { 368 | return null; 369 | } 370 | 371 | if($node->localName === 't') { 372 | $text .= $node->textContent; 373 | return null; 374 | } 375 | 376 | return $node; 377 | }); 378 | 379 | return $text; 380 | } 381 | 382 | 383 | protected function replace($node, $targetNode) { 384 | if($parentNode = $targetNode->parentNode) { 385 | $parentNode->replaceChild($node, $targetNode); 386 | } 387 | } 388 | 389 | protected function getTreeToListBeginIdOldToNew($node,$beginId,$main=true) { 390 | static $beginIdOldToNew = []; 391 | static $index = 0; 392 | if($main) { 393 | $beginIdOldToNew = []; 394 | $index = $beginId; 395 | } 396 | 397 | if(!isset($beginIdOldToNew[$node->idxBegin])) { 398 | $beginIdOldToNew[$node->idxBegin] = $index; 399 | } 400 | 401 | $index++; 402 | if(($node->hasChildNodes())) { 403 | foreach($node->childNodes as $childNode) { 404 | if($childNode->nodeType !== 3) { 405 | $this->getTreeToListBeginIdOldToNew($childNode,0,false); 406 | } 407 | } 408 | 409 | } 410 | 411 | return $beginIdOldToNew; 412 | } 413 | 414 | protected function getCommentRangeEnd($parentNode,$id) { 415 | if(isset($parentNode->tagList['w:commentRangeEnd'])) { 416 | $commentRangeEndItems = $parentNode->tagList['w:commentRangeEnd']; 417 | }else{ 418 | $commentRangeEndItems = []; 419 | } 420 | foreach($commentRangeEndItems as $commentRangeEndItem) { 421 | $eid = $this->getAttr($commentRangeEndItem, 'id'); 422 | 423 | if($id === $eid) { 424 | return $commentRangeEndItem; 425 | } 426 | } 427 | 428 | return null; 429 | } 430 | 431 | protected function getRangeTrace($id,$commentRangeStartItem,$commentRangeEndItem) { 432 | $delTags = ['commentRangeStart'=>0]; 433 | $traces = []; 434 | $startParentNode = $parentNode = $commentRangeStartItem; 435 | $parentNodeCount = 0; 436 | while($parentNode = $parentNode->parentNode) { 437 | $commentRangeEndItem = $this->getCommentRangeEnd($parentNode,$id); 438 | if(is_null($commentRangeEndItem)) { 439 | $startParentNode = $parentNode; 440 | }else{ 441 | break; 442 | } 443 | $parentNodeCount++; 444 | } 445 | 446 | if(!isset($delTags[$startParentNode->localName])) { 447 | $traces[] = $startParentNode; 448 | } 449 | 450 | $nextNodeCount = 0; 451 | $nextSibling = $startParentNode->nextSibling; 452 | $nextNodeCount++; 453 | while(true) { 454 | if(is_null($nextSibling)) { 455 | break; 456 | } 457 | 458 | if($nextSibling === $commentRangeEndItem) { 459 | break; 460 | } 461 | 462 | if($this->getCommentRangeEnd($nextSibling,$id) !== null) { 463 | $childNodes = $nextSibling->childNodes; 464 | $find = false; 465 | $preCount = 0; 466 | $endCount = 0; 467 | $childNodesTemp = []; 468 | foreach ($childNodes as $childNode) { 469 | if($find === false) { 470 | $childNodesTemp[] = $childNode; 471 | } 472 | 473 | if($find === false && ($childNode === $commentRangeEndItem || $this->getCommentRangeEnd($childNode,$id) !== null)) { 474 | $find = true; 475 | continue; 476 | } 477 | 478 | if($this->isNeedSpace($childNode)) { 479 | if($find) { 480 | $endCount++; 481 | }else{ 482 | $preCount++; 483 | } 484 | } 485 | } 486 | 487 | if($endCount === 0) { 488 | if(!isset($delTags[$nextSibling->localName])) { 489 | $traces[] = $nextSibling; 490 | } 491 | }elseif(count($traces) === 0) { 492 | foreach($childNodesTemp as $trace) { 493 | if(!isset($delTags[$trace->localName])) { 494 | $traces[] = $trace; 495 | } 496 | } 497 | } 498 | 499 | break; 500 | }else{ 501 | if(!isset($delTags[$nextSibling->localName])) { 502 | $traces[] = $nextSibling; 503 | } 504 | } 505 | 506 | $nextSibling = $nextSibling->nextSibling; 507 | $nextNodeCount++; 508 | } 509 | 510 | return array_values($traces); 511 | } 512 | 513 | protected function isNeedSpace($node) { 514 | $ts = $node->getElementsByTagName('t'); 515 | if($ts->length > 0) { 516 | return true; 517 | }else{ 518 | return false; 519 | } 520 | } 521 | 522 | protected function showXml($node) { 523 | echo $this->DOMDocument->saveXML($node);exit; 524 | } 525 | 526 | protected function htmlspecialcharsBase($string,$needConvertSpecialFont = true) { 527 | $string = $this->my_html_entity_decode($string); 528 | $string = $this->filterUtf8($this->filterSpecailCodeForWord($string)); 529 | 530 | $string = $this->controlCharacterPHP2OOXML($string); 531 | //preg string build by https://github.com/mkdreams/MDfontSubChar 532 | 533 | if($needConvertSpecialFont === true) { 534 | //SimSun-ExtB 535 | $string = preg_replace_callback('/(\{\%\{\d\}\%\}|)[\x{20000}-\x{2a6d6}\x{2a700}-\x{2b734}\x{2b740}-\x{2b81d}\x{2b8b8}-\x{2b8b9}\x{2bac7}-\x{2bac8}\x{2bb5f}-\x{2bb60}\x{2bb62}-\x{2bb63}\x{2bb7c}-\x{2bb7d}\x{2bb83}-\x{2bb84}\x{2bc1b}-\x{2bc1c}\x{2bd77}\x{2bd87}\x{2bdf7}\x{2be29}\x{2c029}-\x{2c02a}\x{2c0a9}\x{2c0ca}\x{2c1d5}\x{2c1d9}\x{2c1f9}\x{2c27c}\x{2c288}\x{2c2a4}\x{2c317}\x{2c35b}\x{2c361}\x{2c364}\x{2c488}\x{2c494}\x{2c497}\x{2c542}\x{2c613}\x{2c618}\x{2c621}\x{2c629}\x{2c62b}-\x{2c62d}\x{2c62f}\x{2c642}\x{2c64a}-\x{2c64b}\x{2c72c}\x{2c72f}\x{2c79f}\x{2c7c1}\x{2c7fd}\x{2c8d9}\x{2c8de}\x{2c8e1}\x{2c8f3}\x{2c907}\x{2c90a}\x{2c91d}\x{2ca02}\x{2ca0e}\x{2ca7d}\x{2caa9}\x{2cb29}\x{2cb2d}-\x{2cb2e}\x{2cb31}\x{2cb38}-\x{2cb39}\x{2cb3b}\x{2cb3f}\x{2cb41}\x{2cb4a}\x{2cb4e}\x{2cb5a}-\x{2cb5b}\x{2cb64}\x{2cb69}\x{2cb6c}\x{2cb6f}\x{2cb73}\x{2cb76}\x{2cb78}\x{2cb7c}\x{2cbb1}\x{2cbbf}-\x{2cbc0}\x{2cbce}\x{2cc56}\x{2cc5f}\x{2ccf5}-\x{2ccf6}\x{2ccfd}\x{2ccff}\x{2cd02}-\x{2cd03}\x{2cd0a}\x{2cd8b}\x{2cd8d}\x{2cd8f}-\x{2cd90}\x{2cd9f}-\x{2cda0}\x{2cda8}\x{2cdad}-\x{2cdae}\x{2cdd5}\x{2ce18}\x{2ce1a}\x{2ce23}\x{2ce26}\x{2ce2a}\x{2ce7c}\x{2ce88}\x{2ce93}]+/u', function($match){ 536 | if($match[1] === '') { 537 | return '{%{0}%}'.$match[0].'{%{/0}%}'; 538 | }else{ 539 | return $match[0]; 540 | } 541 | }, $string); 542 | 543 | //Cambria Math 544 | $string = preg_replace_callback('/(\{\%\{\d\}\%\}|)[\x{0}\x{d}\x{100}\x{102}-\x{112}\x{114}-\x{11a}\x{11c}-\x{12a}\x{12c}-\x{143}\x{145}-\x{147}\x{149}-\x{14c}\x{14e}-\x{151}\x{154}-\x{15f}\x{162}-\x{16a}\x{16c}-\x{177}\x{179}-\x{191}\x{193}-\x{1cd}\x{1cf}\x{1d1}\x{1d3}\x{1d5}\x{1d7}\x{1d9}\x{1db}\x{1dd}-\x{1f8}\x{1fa}-\x{250}\x{252}-\x{260}\x{262}-\x{2c5}\x{2c8}\x{2cc}-\x{2d8}\x{2da}-\x{2db}\x{2dd}-\x{377}\x{37a}-\x{37f}\x{384}-\x{38a}\x{38c}\x{38e}-\x{390}\x{3aa}-\x{3b0}\x{3c2}\x{3ca}-\x{400}\x{402}-\x{40f}\x{450}\x{452}-\x{52f}\x{531}-\x{556}\x{559}-\x{55f}\x{561}-\x{587}\x{589}-\x{58a}\x{58d}-\x{58f}\x{e3f}\x{1d00}-\x{1dca}\x{1dfe}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fc4}\x{1fc6}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fdd}-\x{1fef}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffe}\x{2000}-\x{200f}\x{2011}-\x{2012}\x{2017}\x{201b}\x{201f}\x{2024}\x{202f}\x{2034}\x{203c}-\x{203e}\x{2044}-\x{2046}\x{2057}\x{205e}-\x{205f}\x{2061}-\x{2063}\x{2070}-\x{2071}\x{2074}-\x{208e}\x{2090}-\x{209c}\x{20a0}-\x{20ab}\x{20ad}-\x{20b5}\x{20b8}-\x{20ba}\x{20bc}-\x{20bf}\x{20d0}-\x{20df}\x{20e1}\x{20e5}-\x{20e6}\x{20e8}-\x{20ea}\x{2100}-\x{2102}\x{2104}\x{2106}-\x{2108}\x{210a}-\x{2115}\x{2117}-\x{2120}\x{2123}-\x{214f}\x{2153}-\x{215e}\x{2183}-\x{2184}\x{2194}-\x{2195}\x{219a}-\x{2207}\x{2209}-\x{220e}\x{2210}\x{2212}-\x{2214}\x{2216}-\x{2219}\x{221b}-\x{221c}\x{2221}-\x{2222}\x{2224}\x{2226}\x{222c}-\x{222d}\x{222f}-\x{2233}\x{2238}-\x{223c}\x{223e}-\x{2247}\x{2249}-\x{224b}\x{224d}-\x{2251}\x{2253}-\x{225f}\x{2262}-\x{2263}\x{2268}-\x{226d}\x{2270}-\x{2294}\x{2296}-\x{2298}\x{229a}-\x{22a4}\x{22a6}-\x{22be}\x{22c0}-\x{2311}\x{2313}-\x{232a}\x{2330}-\x{23cf}\x{23dc}-\x{23e0}\x{246a}-\x{2473}\x{24ea}-\x{24f4}\x{24ff}\x{2592}\x{25a2}-\x{25b1}\x{25b4}-\x{25bb}\x{25be}-\x{25c5}\x{25c8}-\x{25ca}\x{25cc}-\x{25cd}\x{25d0}-\x{25e1}\x{25e6}-\x{25ff}\x{2660}-\x{2663}\x{2666}\x{2720}\x{2776}-\x{277f}\x{27c0}-\x{27ff}\x{2900}-\x{2aff}\x{2b04}\x{2b06}-\x{2b07}\x{2b0c}-\x{2b0d}\x{2b1a}\x{2c60}-\x{2c7f}\x{2e17}\x{a64c}-\x{a64d}\x{a717}-\x{a71a}\x{a720}-\x{a721}\x{fb00}-\x{fb04}\x{fb13}-\x{fb17}\x{fe00}\x{1d400}-\x{1d454}\x{1d456}-\x{1d49c}\x{1d49e}-\x{1d49f}\x{1d4a2}\x{1d4a5}-\x{1d4a6}\x{1d4a9}-\x{1d4ac}\x{1d4ae}-\x{1d4b9}\x{1d4bb}\x{1d4bd}-\x{1d4c0}\x{1d4c2}-\x{1d4c3}\x{1d4c5}-\x{1d505}\x{1d507}-\x{1d50a}\x{1d50d}-\x{1d514}\x{1d516}-\x{1d51c}\x{1d51e}-\x{1d539}\x{1d53b}-\x{1d53e}\x{1d540}-\x{1d544}\x{1d546}\x{1d54a}-\x{1d550}\x{1d552}-\x{1d6a5}\x{1d6a8}-\x{1d7cb}\x{1d7ce}-\x{1d7ff}\x{1d4c1}]+/u', function($match){ 545 | if($match[1] === '') { 546 | return '{%{1}%}'.$match[0].'{%{/1}%}'; 547 | }else{ 548 | return $match[0]; 549 | } 550 | }, $string); 551 | } 552 | 553 | return htmlspecialchars($string,ENT_COMPAT, 'UTF-8'); 554 | } 555 | 556 | protected function my_html_entity_decode($string) { 557 | static $stringMd5 = ''; 558 | 559 | $md5 = md5($string); 560 | if($stringMd5 !== $md5) { 561 | $stringMd5 = $md5; 562 | $string = html_entity_decode($string); 563 | $string = $this->my_html_entity_decode($string); 564 | }else{ 565 | $stringMd5 = ''; 566 | } 567 | return $string; 568 | } 569 | 570 | private function filterUtf8($string) 571 | { 572 | if ($string) { 573 | preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/s', $string, $matches); 574 | return implode('', $matches[0]); 575 | } 576 | return $string; 577 | } 578 | 579 | private function filterSpecailCodeForWord($string) { 580 | $string = str_replace(array('￾'),'', $string); 581 | $string = preg_replace('/[\x14-\x1F]|[\x08]/i', '', $string); 582 | return $string; 583 | } 584 | 585 | /** 586 | * Convert from PHP control character to OpenXML escaped control character 587 | * 588 | * @param string $value Value to escape 589 | * @return string 590 | */ 591 | public static function controlCharacterPHP2OOXML($value = '') 592 | { 593 | if (empty(self::$controlCharacters)) { 594 | self::buildControlCharacters(); 595 | } 596 | 597 | return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); 598 | } 599 | 600 | /** 601 | * Build control characters array. 602 | * 603 | * @return void 604 | */ 605 | private static function buildControlCharacters() 606 | { 607 | for ($i = 0; $i <= 19; ++$i) { 608 | if ($i != 9 && $i != 10 && $i != 13) { 609 | $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; 610 | $replace = chr($i); 611 | self::$controlCharacters[$find] = $replace; 612 | } 613 | } 614 | } 615 | 616 | /** 617 | * @param \DOMDocument $rPr 618 | * @param \DOMDocument $default 619 | */ 620 | public function mergerPr($rPr,$default) { 621 | if(is_null($rPr)) { 622 | return null; 623 | } 624 | if(is_null($default)) { 625 | return null; 626 | } 627 | 628 | foreach($default->childNodes as $node) { 629 | $localName = $node->localName; 630 | $items = $rPr->getElementsByTagName($localName); 631 | if($items->length > 0) { 632 | $item = $items->item(0); 633 | $rPrAttrs = $this->getAttributes($item); 634 | foreach($node->attributes as $attribute) { 635 | if(!isset($rPrAttrs[$attribute->nodeName]) && !isset($rPrAttrs[$attribute->nodeName.'Theme'])) { 636 | $this->setAttr($item, $attribute->localName, $attribute->value); 637 | } 638 | } 639 | }else{ 640 | $copyNode = $this->createElementNS($rPr,$node->nodeName,null); 641 | foreach($node->attributes as $attribute) { 642 | $this->setAttr($copyNode, $attribute->localName, $attribute->value); 643 | } 644 | $this->appendChild($rPr, $copyNode); 645 | } 646 | } 647 | 648 | return $rPr; 649 | } 650 | 651 | private function getAttributes($node) { 652 | $attrs = []; 653 | foreach($node->attributes as $attribute) { 654 | $attrs[$attribute->nodeName] = [$attribute->prefix,$attribute->name,$attribute->value]; 655 | } 656 | 657 | return $attrs; 658 | } 659 | 660 | public function free() { 661 | $bindValueFunc = new \ReflectionMethod($this,'treeToList'); 662 | $statics = $bindValueFunc->getStaticVariables(); 663 | $statics['index'] = 0; 664 | 665 | $bindValueFunc = new \ReflectionMethod($this,'getTreeToListBeginIdOldToNew'); 666 | $statics = $bindValueFunc->getStaticVariables(); 667 | $statics['beginIdOldToNew'] = []; 668 | $statics['index'] = []; 669 | } 670 | 671 | public function getIndex($pNode,$subNode) { 672 | $tag = $subNode->localName; 673 | $tagNodes = $pNode->getElementsByTagName($tag); 674 | $idx = -1; 675 | foreach($tagNodes as $index => $tagNode) { 676 | if($tagNode === $subNode) { 677 | $idx = $index; 678 | break; 679 | } 680 | } 681 | 682 | return $idx; 683 | } 684 | } -------------------------------------------------------------------------------- /src/Common/Tree.php: -------------------------------------------------------------------------------- 1 | DOMDocument = $DOMDocument; 13 | } 14 | 15 | /** 16 | * 17 | * @param string $chartFilenameRel 18 | * @param [] $datas 19 | * @param \DOMDocument $domDocument 20 | * @param string $type 21 | */ 22 | public function chartRelUpdateByType($datas,$type='str') { 23 | $refs = $this->DOMDocument->getElementsByTagName($type.'Ref'); 24 | if($refs->length > 0) { 25 | for($i =0; $i < $refs->length; $i++) { 26 | $item = $refs->item($i); 27 | $f = $item->getElementsByTagName('f')->item(0); 28 | $cache = $item->getElementsByTagName($type.'Cache')->item(0); 29 | 30 | foreach($datas as $data) { 31 | $range = $f->nodeValue; 32 | $rangInfo = $this->parserRange($range); 33 | $rangTempInfo = $data[0]; 34 | $value = $data[1]; 35 | $action = $data[2]; 36 | 37 | //表不一样,跳过 38 | if($rangTempInfo[0] !== $rangInfo[0]) { 39 | continue; 40 | } 41 | // var_dump($rangInfo,$rangTempInfo,$value,$action); 42 | //赋值 43 | if($action == 'set') { 44 | if( 45 | isset($rangInfo[1][1]) && $rangInfo[1][0] <= $rangTempInfo[1][0] && $rangTempInfo[1][0] <= $rangInfo[1][1] 46 | && $rangInfo[2][0] <= $rangTempInfo[2][0] && $rangTempInfo[2][0] <= $rangInfo[2][1] 47 | ) { 48 | $idx = $rangTempInfo[2][0] - $rangInfo[2][0]; 49 | $cache->getElementsByTagName('pt')->item($idx)->firstChild->nodeValue = $value; 50 | }elseif($rangInfo[1][0] == $rangTempInfo[1][0] && $rangInfo[2][0] == $rangTempInfo[2][0]) { 51 | $idx = $rangTempInfo[2][0] - $rangInfo[2][0]; 52 | $cache->getElementsByTagName('pt')->item($idx)->firstChild->nodeValue = $value; 53 | } 54 | }elseif($action == 'ext') {//扩展取值范围 55 | if( 56 | $rangInfo[1][0] === $rangTempInfo[1][0] && $rangInfo[1][1] === $rangTempInfo[1][1] 57 | && $rangInfo[2][0] === $rangTempInfo[2][0] && $rangInfo[2][1] === $rangTempInfo[2][1] 58 | ) { 59 | $f->nodeValue = $value[3]; 60 | //ptCount update 61 | $ptCount = $value[2][1]-$value[2][0]+1; 62 | $cache->getElementsByTagName('ptCount')->item(0)->setAttribute('val',$ptCount); 63 | 64 | $pts = $cache->getElementsByTagName('pt'); 65 | 66 | 67 | for($j = $pts->length;$j < $ptCount;$j++) { 68 | $vNode = $this->DOMDocument->createElementNS('http://schemas.openxmlformats.org/drawingml/2006/chart','v','null'); 69 | $ptNode = $this->DOMDocument->createElementNS('http://schemas.openxmlformats.org/drawingml/2006/chart','pt'); 70 | $ptNode->setAttribute('idx', $j); 71 | $ptNode->appendChild($vNode); 72 | 73 | $cache->appendChild($ptNode); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Edit/Part/Comments.php: -------------------------------------------------------------------------------- 1 | DOMDocument = $DOMDocument; 13 | $this->initNameSpaces(); 14 | $this->blocks = $this->getBlocks(); 15 | } 16 | 17 | private function deleteBlock($block) { 18 | $beginNode = $block[0]['node']; 19 | $endNode = $block[1]['node']; 20 | 21 | $parentNode = $beginNode->parentNode; 22 | 23 | if(!is_null($beginNode)) { 24 | $parentNode->removeChild($beginNode); 25 | } 26 | if(!is_null($endNode)) { 27 | $parentNode->removeChild($endNode); 28 | } 29 | } 30 | 31 | private function getBlocks() { 32 | $items = $this->DOMDocument->getElementsByTagName('comment'); 33 | 34 | $preIsDollar = 0; 35 | $beginNode = null; 36 | $endNode = null; 37 | $blockName = ''; 38 | 39 | $blocks = []; 40 | foreach ($items as $item) { 41 | $text = $item->nodeValue; 42 | 43 | if(MDWORD_BIND_TYPE === 2) { 44 | $blocks[$this->getAttr($item, 'id')] = trim($text); 45 | continue; 46 | } 47 | 48 | //default MDWORD_BIND_TYPE === 1 49 | $textArr = []; 50 | preg_match_all("/./u",$text,$textArr); 51 | if(!is_array($textArr)) { 52 | continue; 53 | } 54 | 55 | foreach($textArr[0] as $word) { 56 | if($word == '') { 57 | continue; 58 | } 59 | 60 | if($word === '$') { 61 | $preIsDollar = 1; 62 | }elseif($preIsDollar == 1 && $word === '{') { 63 | $beginNode = $item; 64 | $preIsDollar = 0; 65 | }elseif(!is_null($beginNode) && $word === '}') { 66 | $endNode = $item; 67 | }elseif(!is_null($beginNode)){ 68 | $blockName .= $word; 69 | $preIsDollar = 0; 70 | }else{ 71 | $preIsDollar = 0; 72 | } 73 | 74 | if(!is_null($beginNode) && !is_null($endNode)) { 75 | $blocks[$this->getAttr($item, 'id')] = $blockName; 76 | $blockName = ''; 77 | $beginNode = null; 78 | $endNode = null; 79 | } 80 | } 81 | } 82 | 83 | return $blocks; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Edit/Part/Excel.php: -------------------------------------------------------------------------------- 1 | word = $word; 18 | 19 | if(class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) { 20 | $this->spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); 21 | }else{ 22 | $this->spreadsheet = new \PHPExcel_Reader_Excel2007(); 23 | } 24 | 25 | $partName = $this->pathRelToAbs($partName); 26 | $content = $this->word->zip->getFromName($partName); 27 | $this->tempDocumentFilename = tempnam($this->getTempDir(), 'MDwordExcelPart'); 28 | file_put_contents($this->tempDocumentFilename, $content); 29 | 30 | $this->word->files[] = ['type'=>'excel','PartName'=>$partName,'this'=>$this]; 31 | 32 | $this->spreadsheet = $this->spreadsheet->load($this->tempDocumentFilename); 33 | } 34 | 35 | public function changeExcelValues($datas) { 36 | foreach($datas as $data) { 37 | if($data[2] === 'set') { 38 | $currentSheet = $this->spreadsheet->getSheetByName($data[0][0]); 39 | $currentSheet->setCellValue($data[0][1][0].$data[0][2][0],$data[1]); 40 | } 41 | } 42 | } 43 | 44 | public function preDealDatas ($datas) { 45 | $setDatas = []; 46 | $extDatas = []; 47 | foreach($datas as $data) { 48 | if($data[2] == 'set') { 49 | $data[0] = $this->parserRange($data[0]); 50 | $setDatas[] = $data; 51 | }else if($data[2] == 'ext') { 52 | $data[0] = $this->parserRange($data[0]); 53 | $data[1] = $this->parserRange($data[1]); 54 | $extDatas[] = $data; 55 | } 56 | } 57 | 58 | return array_merge($extDatas,$setDatas); 59 | } 60 | 61 | public function getContent() { 62 | $this->PHPExcel_Writer = new \PHPExcel_Writer_Excel2007($this->spreadsheet); 63 | $this->PHPExcel_Writer->save($this->tempDocumentFilename); 64 | $content = file_get_contents($this->tempDocumentFilename); 65 | @unlink($this->tempDocumentFilename); 66 | return $content; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Edit/Part/Footer.php: -------------------------------------------------------------------------------- 1 | DOMDocument = $DOMDocument; 15 | $this->initNameSpaces(); 16 | } 17 | 18 | public function getAbstractNumById($numId) { 19 | if(is_null($this->abstractNums)) { 20 | $tempnums = $this->DOMDocument->getElementsByTagName('abstractNum'); 21 | $nums = $this->DOMDocument->getElementsByTagName('num'); 22 | foreach($nums as $num) { 23 | $abstractNumId = intval($this->getVal($num,'abstractNumId')); 24 | $this->numIdToAbstractNumId[intval($this->getAttr($num,'numId'))] = [$abstractNumId,$num]; 25 | } 26 | $this->abstractNums = []; 27 | foreach($tempnums as $abstractNum) { 28 | $abstractNumId = $this->getAttr($abstractNum,'abstractNumId'); 29 | $numInfo = ['lvls'=>[],'abstractNumId'=>$abstractNumId,'text'=>[]]; 30 | $lvls = $abstractNum->getElementsByTagName('lvl'); 31 | foreach($lvls as $lvl) { 32 | $ilvl = intval($this->getAttr($lvl, 'ilvl')); 33 | $start = intval($this->getVal($lvl, 'start')); 34 | $numFmt = $this->getVal($lvl, 'numFmt'); 35 | $lvlText = $this->getVal($lvl, 'lvlText'); 36 | $numInfo['lvls'][$ilvl] = ['lvl'=>$lvl,'start'=>$start,'isLgl'=>$this->getExist($lvl,'isLgl'),'numFmt'=>$numFmt,'lvlText'=>$lvlText]; 37 | } 38 | 39 | $this->abstractNums[$abstractNumId] = $numInfo; 40 | } 41 | } 42 | 43 | return $this->abstractNums[$this->numIdToAbstractNumId[$numId][0]]; 44 | } 45 | 46 | public function getText($numId,$ilvl) { 47 | static $numIdRecored = []; 48 | $num = $this->getAbstractNumById($numId); 49 | $lvlInfo = $num['lvls'][$ilvl]; 50 | $lvl = $lvlInfo['lvl']; 51 | 52 | if(is_null($lvl)) { 53 | return ''; 54 | } 55 | 56 | $abstractNumId = $num['abstractNumId']; 57 | $start = $lvlInfo['start']; 58 | $numFmt = $lvlInfo['numFmt']; 59 | $lvlText = $lvlInfo['lvlText']; 60 | 61 | if(!isset($numIdRecored[$numId])) { 62 | $numIdIsFirst = true; 63 | }else{ 64 | $numIdIsFirst = false; 65 | } 66 | $numIdRecored[$numId] = true; 67 | if($numIdIsFirst === true) { 68 | $numNode = $this->numIdToAbstractNumId[$numId][1]; 69 | $lvlOverrides = $numNode->getElementsByTagName('lvlOverride'); 70 | if($lvlOverrides->length > 0) { 71 | foreach($lvlOverrides as $lvlOverride) { 72 | $ilvlTemp = $this->getAttr($lvlOverride,'ilvl'); 73 | $startOverrideTemp = $this->getVal($lvlOverride,'startOverride'); 74 | $this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['index'] = $startOverrideTemp; 75 | } 76 | } 77 | } 78 | 79 | foreach($this->abstractNums[$abstractNumId]['text'] as $ilvlTemp => $val) { 80 | if($ilvlTemp > $ilvl) { 81 | unset($this->abstractNums[$abstractNumId]['text'][$ilvlTemp]); 82 | } 83 | } 84 | 85 | if(!isset($this->abstractNums[$abstractNumId]['text'][$ilvl])) { 86 | $this->abstractNums[$abstractNumId]['text'][$ilvl] = []; 87 | $this->abstractNums[$abstractNumId]['text'][$ilvl]['index'] = $start; 88 | } 89 | 90 | $this->abstractNums[$abstractNumId]['text'][$ilvl]['text'] = $this->getTextByIndex($numFmt,$this->abstractNums[$abstractNumId]['text'][$ilvl]['index']); 91 | $text = preg_replace_callback('/\%(\d+)/i',function($match) use($abstractNumId,$num,$ilvl){ 92 | $ilvlTemp = $match[1]-1; 93 | if(!isset($this->abstractNums[$abstractNumId]['text'][$ilvlTemp])) { 94 | $this->abstractNums[$abstractNumId]['text'][$ilvlTemp] = []; 95 | $this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['index'] = $num['lvls'][$ilvlTemp]['start']; 96 | $this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['text'] = $this->getTextByIndex($num['lvls'][$ilvlTemp]['numFmt'],$this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['index']); 97 | $this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['index']++; 98 | } 99 | 100 | if($num['lvls'][$ilvl]['isLgl'] === true) { 101 | return $this->getTextByIndex($num['lvls'][$ilvl]['numFmt'],$this->abstractNums[$abstractNumId]['text'][$ilvl]['index']); 102 | }else{ 103 | return $this->abstractNums[$abstractNumId]['text'][$ilvlTemp]['text']; 104 | } 105 | },$lvlText); 106 | 107 | $this->abstractNums[$abstractNumId]['text'][$ilvl]['index']++; 108 | 109 | return $text; 110 | } 111 | 112 | private function getTextByIndex($numFmt,$index) { 113 | //$enum = array('bullet', 'decimal', 'upperRoman', 'lowerRoman', 'upperLetter', 'lowerLetter'); 114 | switch($numFmt) { 115 | case 'decimal': 116 | return $index; 117 | break; 118 | case 'upperLetter': 119 | return chr(64+$index); 120 | break; 121 | case 'lowerLetter': 122 | return chr(96+$index); 123 | break; 124 | case 'upperRoman': 125 | $nf = new \NumberFormatter('@numbers=roman', \NumberFormatter::DECIMAL); 126 | return $nf->format($index); 127 | case 'lowerRoman': 128 | $nf = new \NumberFormatter('@numbers=roman', \NumberFormatter::DECIMAL); 129 | return strtolower($nf->format($index)); 130 | case 'chineseCountingThousand': 131 | $nf = new \NumberFormatter('zh_CN', \NumberFormatter::SPELLOUT); 132 | return $nf->format($index); 133 | break; 134 | case 'ideographTraditional': 135 | $list = ["","甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]; 136 | return isset($list[$index])?$list[$index]:($index+1); 137 | break; 138 | case 'ideographZodiac': 139 | $list = ["","子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]; 140 | return isset($list[$index])?$list[$index]:($index+1); 141 | break; 142 | case 'chineseLegalSimplified': 143 | $list = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; 144 | return isset($list[$index])?$list[$index]:($index+1); 145 | break; 146 | default: 147 | return $index+1; 148 | break; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Edit/Part/Rels.php: -------------------------------------------------------------------------------- 1 | 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', 18 | 1 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings', 19 | 2 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', 20 | 3 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', 21 | 4 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', 22 | 5 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering', 23 | 6 => 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended', 24 | 7 => 'http://schemas.microsoft.com/office/2011/relationships/people', 25 | 8 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', 26 | 9 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable', 27 | 10 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings', 28 | 11 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes', 29 | 12 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml', 30 | 13 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes', 31 | 14 => 'http://schemas.microsoft.com/office/2016/09/relationships/commentsIds', 32 | 15 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/package', 33 | 16 => 'http://schemas.microsoft.com/office/2011/relationships/chartColorStyle', 34 | 17 => 'http://schemas.microsoft.com/office/2011/relationships/chartStyle', 35 | 18 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer', 36 | 19 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header', 37 | 20 => 'http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible', 38 | 21 => 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', 39 | )//--RELATIONSHIPTYPES-- 40 | ; 41 | 42 | public function __construct($word,\DOMDocument $DOMDocument) { 43 | parent::__construct($word); 44 | $this->DOMDocument = $DOMDocument; 45 | 46 | $Relationships = $this->DOMDocument->getElementsByTagName('Relationship'); 47 | foreach ($Relationships as $Relationship) { 48 | if(!in_array($type = $this->getAttr($Relationship, 'Type'), $this->relationshipTypes)) { 49 | $this->relationshipTypes[] = $type; 50 | } 51 | 52 | $rid = $this->getAttr($Relationship, 'Id'); 53 | $this->ridToTarget[$rid] = $Relationship; 54 | 55 | $id = intval(str_replace('rId', '', $rid)); 56 | if($id > $this->rIdMax) { 57 | $this->rIdMax = $id; 58 | } 59 | 60 | if($this->relationshipTypes[2] === $this->getAttr($Relationship, 'Type')) { 61 | $md5 = md5($this->word->zip->getFromName('word/'.$this->getAttr($Relationship, 'Target'))); 62 | $this->imageMd5ToRid[$md5][] = $rid; 63 | } 64 | } 65 | 66 | if(MDWORD_DEBUG) { 67 | $build = new Build(); 68 | $build->replace('RELATIONSHIPTYPES', $this->relationshipTypes, __FILE__); 69 | } 70 | } 71 | 72 | public function replace($rid,$file) { 73 | $Relationships = $this->DOMDocument->getElementsByTagName('Relationship'); 74 | foreach ($Relationships as $Relationship) { 75 | if($Relationship->getAttribute('Id') === $rid) { 76 | $type = $this->getAttr($Relationship, 'Type'); 77 | $oldTarget = $Relationship->getAttribute('Target'); 78 | switch ($type) { 79 | case $this->relationshipTypes[2]: 80 | $imageInfo = @getimagesize($file); 81 | if($imageInfo === false) { 82 | $this->word->log->writeLog('image not invalid! image:'.$file); 83 | return false; 84 | } 85 | 86 | $mimeArr = explode('/', $imageInfo['mime'],2); 87 | $Extension = $mimeArr[1]; 88 | 89 | $target = 'media/replace'.pathinfo($file, PATHINFO_FILENAME).'.'.$Extension; 90 | $this->word->Content_Types->addDefault($Extension, $imageInfo['mime']); 91 | break; 92 | } 93 | 94 | //删除旧文件 95 | $oldValue = $this->partInfo['dirname'].'/'.$oldTarget; 96 | $this->word->zip->deleteName($oldValue); 97 | 98 | //替换 99 | $Relationship->setAttribute('Target',$target); 100 | $target = $this->partInfo['dirname'].'/'.$target; 101 | $this->word->zip->addFromString($target, file_get_contents($file)); 102 | } 103 | } 104 | } 105 | 106 | public function cloneRels($rid) { 107 | return $this->insert($rid,'clone'); 108 | } 109 | 110 | public function insert($file,$fileType) { 111 | $this->rIdMax = $this->rIdMax + 1; 112 | 113 | switch ($fileType) { 114 | case MDWORD_IMG: 115 | $type = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'; 116 | $imageInfo = @getimagesize($file); 117 | if($imageInfo === false) { 118 | return false; 119 | } 120 | $mimeArr = explode('/', $imageInfo['mime'],2); 121 | $Extension = $mimeArr[1]; 122 | 123 | $target = 'media/image'.$this->rIdMax.'.'.$Extension; 124 | $rId = 'rId'.$this->rIdMax; 125 | $Relationship = $this->createNodeByXml(''); 126 | if($this->DOMDocument->getElementsByTagName('Relationships')->length == 0){ 127 | $Relationships = $this->createNodeByXml(''); 128 | $this->DOMDocument->encoding = 'UTF-8'; 129 | $this->DOMDocument->appendChild($Relationships); 130 | } 131 | $this->DOMDocument->getElementsByTagName('Relationships')->item(0)->appendChild($Relationship); 132 | 133 | $target = $this->partInfo['dirname'].'/'.$target; 134 | $this->word->zip->addFromString($target, file_get_contents($file)); 135 | 136 | $this->word->Content_Types->addDefault($Extension, $imageInfo['mime']); 137 | return ['rId'=>$rId,'imageInfo'=>$imageInfo]; 138 | break; 139 | case 'clone': 140 | $rid = $file; 141 | $Relationship = $this->ridToTarget[$rid]; 142 | $type = $this->getAttr($Relationship, 'Type'); 143 | $oldTarget = $Relationship->getAttribute('Target'); 144 | $c = $this->word->zip->getFromName($this->partInfo['dirname'].'/'.$oldTarget); 145 | $target = preg_replace_callback('/\d+?(?=\.)/i',function($match) { 146 | return $this->rIdMax; 147 | },$oldTarget); 148 | $rId = 'rId'.$this->rIdMax; 149 | 150 | $Relationship = $this->createNodeByXml(''); 151 | if($this->DOMDocument->getElementsByTagName('Relationships')->length == 0){ 152 | $Relationships = $this->createNodeByXml(''); 153 | $this->DOMDocument->encoding = 'UTF-8'; 154 | $this->DOMDocument->appendChild($Relationships); 155 | } 156 | $this->DOMDocument->getElementsByTagName('Relationships')->item(0)->appendChild($Relationship); 157 | $target = $this->partInfo['dirname'].'/'.$target; 158 | $this->word->zip->addFromString($target, $c); 159 | if($type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/header') { 160 | $this->word->Content_Types->addOverride('/'.$target, 'application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml'); 161 | }elseif($type === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer') { 162 | $this->word->Content_Types->addOverride('/'.$target, 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml'); 163 | } 164 | return ['rId'=>$rId,'partName'=>$target,'xml'=>$c]; 165 | break; 166 | } 167 | } 168 | 169 | public function getTarget($rid=null) { 170 | $Relationships = $this->DOMDocument->getElementsByTagName('Relationship'); 171 | foreach ($Relationships as $Relationship) { 172 | if(is_null($rid)) { 173 | return $this->partInfo['dirname'].'/'.$Relationship->getAttribute('Target'); 174 | } 175 | 176 | if($Relationship->getAttribute('Id') === $rid) { 177 | return $this->partInfo['dirname'].'/'.$Relationship->getAttribute('Target'); 178 | } 179 | } 180 | 181 | return null; 182 | } 183 | 184 | public function setNewChartRels($chartCount){ 185 | $Relationships = $this->DOMDocument->getElementsByTagName('Relationship'); 186 | $tempMaxId = 0; 187 | $chartNum = 0; 188 | foreach ($Relationships as $Relationship) { 189 | $target = $Relationship->getAttribute('Target'); 190 | if(strpos($target,'charts') !== false){ 191 | $chartNum++; 192 | // $Relationship->parentNode->removeChild($Relationship); 193 | } 194 | $rId = $Relationship->getAttribute('Id'); 195 | preg_match('/(\d+)/',$rId,$match); 196 | if($match[1] > $tempMaxId){ 197 | $tempMaxId = $match[1]; 198 | } 199 | } 200 | $Relationships2 = $this->DOMDocument->getElementsByTagName('Relationship'); 201 | $chartRid = []; 202 | for($i = 0 ; $i < $chartCount ; $i++){ 203 | $chartNum++; 204 | $tempMaxId++; 205 | $copy = clone $Relationships[0]; 206 | $copy->setAttribute('Id','rId'.$tempMaxId); 207 | $chartRid[] = $tempMaxId; 208 | $copy->setAttribute('Type',$this->relationshipTypes[0]); 209 | $copy->setAttribute('Target','charts/chart'.$chartNum.'.xml'); 210 | $Relationships[0]->parentNode->appendChild($copy); 211 | } 212 | return $chartRid; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Edit/Part/Styles.php: -------------------------------------------------------------------------------- 1 | DOMDocument = $DOMDocument; 14 | $this->initNameSpaces(); 15 | } 16 | 17 | public function getStyleById($id) { 18 | if(is_null($this->styles)) { 19 | $tempStyles = $this->DOMDocument->getElementsByTagName('style'); 20 | $this->styles = []; 21 | foreach($tempStyles as $style) { 22 | $styleId = $this->getAttr($style, 'styleId'); 23 | $this->styles[$styleId] = $style; 24 | } 25 | } 26 | 27 | return $this->styles[$id]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Read/Part/ContentTypes.php: -------------------------------------------------------------------------------- 1 | 'application/vnd.openxmlformats-package.relationships+xml', 16 | 1 => 'application/xml', 17 | 2 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml', 18 | 3 => 'application/vnd.openxmlformats-officedocument.customXmlProperties+xml', 19 | 4 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml', 20 | 5 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml', 21 | 6 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml', 22 | 7 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml', 23 | 8 => 'application/vnd.openxmlformats-officedocument.theme+xml', 24 | 9 => 'application/vnd.openxmlformats-package.core-properties+xml', 25 | 10 => 'application/vnd.openxmlformats-officedocument.extended-properties+xml', 26 | 11 => 'application/octet-stream', 27 | 12 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml', 28 | 13 => 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml', 29 | 14 => 'image/png', 30 | 15 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml', 31 | 16 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml', 32 | 17 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml', 33 | 18 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.people+xml', 34 | 19 => 'rels', 35 | 20 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml', 36 | 21 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml', 37 | 22 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml', 38 | 23 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml', 39 | 24 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 40 | 25 => 'application/vnd.ms-office.chartstyle+xml', 41 | 26 => 'application/vnd.ms-office.chartcolorstyle+xml', 42 | 27 => 'image/jpeg', 43 | 28 => 'image/x-wmf', 44 | 29 => 'application/vnd.openxmlformats-officedocument.oleObject', 45 | 30 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml', 46 | 31 => 'image/x-ms-bmp', 47 | )//--CONTENTTYPES-- 48 | ; 49 | /** 50 | * @param \DOMDocument $DOMDocument 51 | */ 52 | public function __construct(\DOMDocument $DOMDocument) { 53 | parent::__construct(); 54 | 55 | $this->DOMDocument = $DOMDocument; 56 | $this->parse(); 57 | } 58 | 59 | public function parse() { 60 | $Types = $this->DOMDocument->getElementsByTagName('Types')->item(0); 61 | $childrens = $Types->getElementsByTagName('*'); 62 | foreach($childrens as $children) { 63 | $this->paseItem($children); 64 | } 65 | 66 | if(MDWORD_DEBUG) { 67 | $build = new Build(); 68 | $build->replace('CONTENTTYPES', $this->contentTypes, __FILE__); 69 | } 70 | } 71 | 72 | public function getPartNames() { 73 | return $this->partNames; 74 | } 75 | 76 | private function paseItem(\DOMElement $item) { 77 | $ContentType = $item->getAttribute('ContentType'); 78 | $pos = array_search($ContentType,$this->contentTypes); 79 | if($pos === false) { 80 | $this->contentTypes[] = $ContentType; 81 | $pos = sizeof($this->contentTypes) - 1; 82 | } 83 | 84 | switch ($item->tagName) { 85 | case 'Default': 86 | $Extension = $item->getAttribute('Extension'); 87 | $this->defaults[$Extension] = ['Extension'=>$Extension,'ContentType'=>$pos]; 88 | break; 89 | case 'Override': 90 | $PartName = ltrim($item->getAttribute('PartName'),'/'); 91 | $this->overrides[$PartName] = ['PartName'=>$PartName,'ContentType'=>$pos]; 92 | break; 93 | } 94 | } 95 | 96 | public function addDefault($Extension,$ContentType) { 97 | if(!isset($this->defaults[$Extension])) { 98 | $pos = array_search($ContentType,$this->contentTypes); 99 | if($pos === false) { 100 | $this->word->log->writeLog('content type not find! type:'.$ContentType); 101 | }else{ 102 | $this->defaults[$Extension] = ['Extension'=>$Extension,'ContentType'=>$pos]; 103 | } 104 | 105 | $node = $this->createNodeByXml(''); 106 | $Types = $this->DOMDocument->getElementsByTagName('Types')->item(0); 107 | $this->insertBefore($node, $Types->firstChild); 108 | } 109 | } 110 | 111 | public function addOverride($PartName='word/document.xml',$ContentTypeIdx=2) { 112 | if(is_string($ContentTypeIdx)) { 113 | $pos = array_search($ContentTypeIdx,$this->contentTypes); 114 | if($pos === false) { 115 | $this->contentTypes[] = $ContentTypeIdx; 116 | $pos = sizeof($this->contentTypes) - 1; 117 | } 118 | $ContentTypeIdx = $pos; 119 | } 120 | $this->overrides[$PartName] = ['PartName'=>$PartName,'ContentType'=>$ContentTypeIdx]; 121 | 122 | $overrides = $this->DOMDocument->getElementsByTagName('Override'); 123 | $lastOverride = $overrides->item($overrides->length-1); 124 | $copy = clone $lastOverride; 125 | $this->setAttr($copy,'PartName',$PartName,''); 126 | $this->setAttr($copy,'ContentType',$this->contentTypes[$ContentTypeIdx],''); 127 | $this->insertAfter($copy,$lastOverride); 128 | } 129 | 130 | public function setContent_types($newOverrides){ 131 | $this->overrides = array_merge($this->overrides,$newOverrides); 132 | $xlsDefault = ['Extension' => 'xlsx','ContentType' =>11]; 133 | foreach($this->defaults as $default){ 134 | $defaultArr[] = $default['Extension']; 135 | } 136 | if(!array_search('xlsx',$defaultArr)){ 137 | $this->defaults[] = $xlsDefault; 138 | $defaults = $this->DOMDocument->getElementsByTagName('Default'); 139 | $copy = clone $defaults[0]; 140 | $copy->setAttribute('Extension',$xlsDefault['Extension']); 141 | $copy->setAttribute('ContentType',$this->contentTypes[$xlsDefault['ContentType']]); 142 | $defaults[0]->parentNode->appendChild($copy); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Read/Word.php: -------------------------------------------------------------------------------- 1 | log = new Log(); 66 | } 67 | 68 | 69 | public function load($archive) { 70 | if(!is_file($archive)) { 71 | throw new \Exception('Template not exist!'); 72 | } 73 | 74 | $this->tempDocumentFilename = tempnam($this->getTempDir(), 'MDword'); 75 | if (false === $this->tempDocumentFilename) { 76 | throw new \Exception('temp path make failed!'); 77 | } 78 | 79 | 80 | if (false === file_put_contents($this->tempDocumentFilename, file_get_contents($archive)) || !is_file($this->tempDocumentFilename)) { 81 | throw new \Exception($archive.' copy file failed!'); 82 | } 83 | 84 | $this->zip = new \ZipArchive(); 85 | $this->zip->open($this->tempDocumentFilename); 86 | 87 | $this->read(); 88 | 89 | //update needUpdateParts 90 | if(isset($this->parts[22]) && isset($this->blocks[22])) { 91 | foreach($this->blocks[22] as $partName => $value) { 92 | $this->needUpdateParts[$partName] = ['func'=>'getHeaderEdit','partName'=>$partName]; 93 | } 94 | } 95 | 96 | $this->needUpdateParts['word/document.xml'] = ['func'=>'getDocumentEdit','partName'=>'word/document.xml']; 97 | 98 | if(isset($this->parts[23]) && isset($this->blocks[23])) { 99 | foreach($this->blocks[23] as $partName => $value) { 100 | $this->needUpdateParts[$partName] = ['func'=>'getFooterEdit','partName'=>$partName]; 101 | } 102 | } 103 | } 104 | 105 | private function read() { 106 | $this->Content_Types = new ContentTypes($this->getXmlDom('[Content_Types].xml')); 107 | $this->Content_Types->word = $this; 108 | foreach ($this->Content_Types->overrides as $part) { 109 | if(in_array($part['ContentType'], [2,22,23])) {//document footer header 110 | $standardXmlFunc = function($xml) use($part) { 111 | $xml = $this->standardXml($xml,$part['ContentType'],$part['PartName']); 112 | return $xml; 113 | }; 114 | }else{ 115 | $standardXmlFunc = null; 116 | } 117 | 118 | if($part['ContentType'] === 14) {//image/png 119 | $this->parts[$part['ContentType']][] = ['PartName'=>$part['PartName'],'DOMElement'=>$part['PartName']]; 120 | }else{ 121 | $this->parts[$part['ContentType']][] = ['PartName'=>$part['PartName'],'DOMElement'=>$this->getXmlDom($part['PartName'],$standardXmlFunc)]; 122 | } 123 | } 124 | } 125 | 126 | public static function getTempDir() 127 | { 128 | $tempDir = sys_get_temp_dir(); 129 | 130 | if (!empty(self::$tempDir)) { 131 | $tempDir = self::$tempDir; 132 | } 133 | 134 | return $tempDir; 135 | } 136 | 137 | public function save($remainComments = false) 138 | { 139 | //clean before update 140 | $this->deleteComments($remainComments); 141 | 142 | //update Toc 143 | $this->documentEdit->updateToc(); 144 | 145 | $this->deleteComments($remainComments); 146 | 147 | foreach($this->parts as $type => $list ) { 148 | foreach($list as $part) { 149 | if(is_object($part['DOMElement'])) { 150 | if(empty($part['DOMElement']->documentElement)){ 151 | continue; 152 | } 153 | //delete space before break page 154 | if($type === 2) { 155 | $this->zip->addFromString($part['PartName'], $this->SimSunExtBSupport($this->autoDeleteSpaceBeforeBreakPage($part['DOMElement']->saveXML()))); 156 | }else{ 157 | //add MDword flag 158 | if($type === 9) { 159 | $description = $part['DOMElement']->getElementsByTagName('description')->item(0); 160 | if(is_null($description)) { 161 | $el = $part['DOMElement']->createElement('dc:description','Made with MDword'); 162 | if($coreProperties = $part['DOMElement']->getElementsByTagName('coreProperties')->item(0)) { 163 | $coreProperties->appendChild($el); 164 | } 165 | }else{ 166 | $description->nodeValue='Made with MDword'; 167 | } 168 | 169 | } 170 | $this->zip->addFromString($part['PartName'], $this->SimSunExtBSupport($part['DOMElement']->saveXML())); 171 | } 172 | } 173 | } 174 | } 175 | 176 | foreach($this->files as $part) { 177 | if(isset($part['this'])) { 178 | $this->zip->addFromString($part['PartName'], $part['this']->getContent()); 179 | }else{ 180 | $this->zip->deleteName($part['PartName']); 181 | } 182 | } 183 | 184 | $this->zip->addFromString('[Content_Types].xml', $this->Content_Types->DOMDocument->saveXML()); 185 | 186 | if (false === $this->zip->close()) { 187 | throw new \Exception('Could not close zip file.'); 188 | } 189 | 190 | // trigger_error('debug',E_USER_ERROR);exit; 191 | 192 | if(MDWORD_DEBUG === true) { 193 | $this->zip->open($this->tempDocumentFilename); 194 | $this->zip->extractTo(MDWORD_GENERATED_DIRECTORY); 195 | $this->zip->close(); 196 | } 197 | 198 | return $this->tempDocumentFilename; 199 | } 200 | 201 | public function saveForTrace() 202 | { 203 | foreach($this->parts as $type => $list ) { 204 | foreach($list as $part) { 205 | if(is_object($part['DOMElement'])) { 206 | //delete document end space 207 | if($type === 2) { 208 | $this->zip->addFromString($part['PartName'], $this->autoDeleteSpaceBeforeBreakPage($part['DOMElement']->saveXML())); 209 | }else{ 210 | $this->zip->addFromString($part['PartName'], $part['DOMElement']->saveXML()); 211 | } 212 | } 213 | } 214 | } 215 | 216 | foreach($this->files as $part) { 217 | if(isset($part['this'])) { 218 | $this->zip->addFromString($part['PartName'], $part['this']->getContent()); 219 | }else{ 220 | $this->zip->deleteName($part['PartName']); 221 | } 222 | } 223 | 224 | $this->zip->addFromString('[Content_Types].xml', $this->Content_Types->DOMDocument->saveXML()); 225 | 226 | 227 | $this->zip->open($this->tempDocumentFilename); 228 | $this->zip->extractTo(MDWORD_GENERATED_DIRECTORY); 229 | 230 | return $this->tempDocumentFilename; 231 | } 232 | 233 | 234 | public function standardXml($xml,$ContentType,$PartName) { 235 | $xml = preg_replace_callback('/\$[^$]*?\{[\s\S]+?\}/i', function($match){ 236 | return preg_replace('/\s/', '', strip_tags($match[0])); 237 | }, $xml); 238 | 239 | static $commentId = 0; 240 | $nameToCommendId = []; 241 | $xml = preg_replace_callback('/(<[w|m]\:r[> ](?:(?!<[w|m]:r[> ])[\S\s])*?<[w|m]\:t[ ]{0,1}[^>]*?>)([^><]*?)(<\/[w|m]\:t>[\s\S]*?<\/[w|m]\:r>)/i', function($matchs) use(&$commentId,&$nameToCommendId,$ContentType,$PartName){ 242 | return preg_replace_callback('/\$\{([\s\S]+?)\}/i', function($match) use(&$commentId,&$nameToCommendId,$matchs,$ContentType,$PartName){ 243 | $name = $match[1]; 244 | $length = strlen($name); 245 | if($name[$length-1] === '/') { 246 | $name = trim($name,'/'); 247 | $this->blocks[$ContentType][$PartName]['r'.$commentId] = $name; 248 | return $matchs[3].''.$matchs[1].$match[0].$matchs[3].''.$matchs[1]; 249 | //end 250 | }elseif($name[0] === '/') { 251 | $name = trim($name,'/'); 252 | return $match[0].$matchs[3].''.$matchs[1]; 253 | //start 254 | }else{ 255 | $name = trim($name,'/'); 256 | $this->blocks[$ContentType][$PartName]['r'.$commentId] = $name; 257 | $nameToCommendId[$name] = $commentId; 258 | return $matchs[3].''.$matchs[1].$match[0]; 259 | } 260 | 261 | }, $matchs[0]); 262 | }, $xml); 263 | 264 | return $xml; 265 | } 266 | 267 | private function autoDeleteSpaceBeforeBreakPage($xml) { 268 | ini_set("pcre.backtrack_limit",-1);//回溯bug fixed,-1代表不做限制 269 | $xml = preg_replace_callback('/\<\/w\:p\>(?:\(?:(?!\<\/w\:t\>|\|\)*?\]*?\>\\\<\/w\:r\>\<\/w\:p\>/i', function($match) { 270 | 271 | preg_match_all('/\]*?\>(.+?)\<\/w\:p\>/i', $match[0],$subMatch); 272 | if(!empty($subMatch[1])) { 273 | return implode('', $subMatch[1]).''; 274 | } 275 | 276 | return ''; 277 | }, $xml); 278 | 279 | $xml = preg_replace('/\\/','',$xml); 280 | 281 | return $xml; 282 | } 283 | 284 | private function SimSunExtBSupport($xml) { 285 | $fontMap = [ 286 | 0=>'', 287 | 1=>'' 288 | ]; 289 | 290 | return preg_replace_callback('/(]+?>|>))(?:(?!]+?>|>)).)*?\{%\{\d\}%\}(?:(?!]+?>|>)).)*?(<\/w:r>)/i',function($match) use($fontMap) { 291 | preg_match('/(]+?>|>)[\s\S]+?)()([\s\S]*<\/w:rPr>)|(]+?>|>)[\s\S]+?)<\/w:rPr>/i',$match[0],$fontMatch); 292 | if(isset($fontMatch[0])) { 293 | if(isset($fontMatch[4])) { 294 | $rPrB = $fontMatch[4]; 295 | $rPrE = ''; 296 | $orgFont = ''; 297 | }else{ 298 | $rPrB = $fontMatch[1]; 299 | $orgFont = $fontMatch[2]; 300 | $rPrE = $fontMatch[3]; 301 | } 302 | }else{ 303 | $rPrB = ''; 304 | $orgFont = ''; 305 | $rPrE = ''; 306 | } 307 | 308 | return preg_replace_callback('/\{%\{(\d)\}%\}([\s\S]+?)\{%\{\/\d\}%\}/i',function($match2) use($fontMap,$match,$orgFont,$rPrB,$rPrE) { 309 | $font = $fontMap[intval($match2[1])]; 310 | return ''. 311 | $match[1].$rPrB.$font.$rPrE.''.$match2[2].'' 312 | .$match[1].($orgFont===''?$rPrB.$rPrE:$rPrB.$orgFont.$rPrE).''; 313 | },$match[0]); 314 | },$xml); 315 | } 316 | 317 | private function deleteComments($remainComments = true) { 318 | if($remainComments) { 319 | $edit = $this->commentsEdit[0]; 320 | $willDeleted = []; 321 | $usedCommentIds = $this->wordProcessor->getUsedCommentIds(); 322 | $edit->treeToListCallback($edit->DOMDocument,function($node) use($edit,$usedCommentIds,&$willDeleted) { 323 | if($node->localName == 'comment' && isset($usedCommentIds[$edit->getAttr($node,'id')])) { 324 | $willDeleted[] = $node; 325 | }else{ 326 | return $node; 327 | } 328 | }); 329 | 330 | foreach($willDeleted as $node) { 331 | $edit->removeChild($node); 332 | } 333 | }else{ 334 | $parts = []; 335 | if(isset($this->parts[15])) { 336 | $parts = array_merge($parts,$this->parts[15]); 337 | unset($this->parts[15]); 338 | } 339 | if(isset($this->parts[16])) { 340 | $parts = array_merge($parts,$this->parts[16]); 341 | unset($this->parts[16]); 342 | } 343 | if(isset($this->parts[17])) { 344 | $parts = array_merge($parts,$this->parts[17]); 345 | unset($this->parts[17]); 346 | } 347 | foreach($parts as $part) { 348 | $this->zip->deleteName($part['PartName']); 349 | } 350 | } 351 | 352 | foreach($this->needUpdateParts as $part) { 353 | $func = $part['func']; 354 | switch ($func) { 355 | case 'getHeaderEdit': 356 | $this->deleteMded($this->headerEdits[$part['partName']],$remainComments); 357 | break; 358 | case 'getDocumentEdit': 359 | $this->deleteMded($this->wordProcessor->getDocumentEdit(),$remainComments); 360 | break; 361 | case 'getFooterEdit': 362 | $this->deleteMded($this->footerEdits[$part['partName']],$remainComments); 363 | break; 364 | } 365 | } 366 | 367 | //test 368 | // echo $this->documentEdit->DOMDocument->saveXML();exit; 369 | } 370 | 371 | private function deleteMded($edit,$remainComments=false) { 372 | $deleteTags = [ 373 | 'commentRangeStart'=>1, 374 | 'commentRangeEnd'=>1, 375 | 'commentReference'=>1 376 | ]; 377 | 378 | $willDeleted = []; 379 | $usedCommentIds = $this->wordProcessor->getUsedCommentIds(); 380 | if($remainComments) { 381 | $edit->treeToListCallback($edit->DOMDocument,function($node) use($edit,$deleteTags,$usedCommentIds,&$willDeleted) { 382 | if($edit->getAttr($node,'md',null)) { 383 | $willDeleted[] = $node; 384 | }else{ 385 | $id = $edit->getAttr($node,'id'); 386 | if(isset($deleteTags[$node->localName]) && $id[0] !== 'r' && isset($usedCommentIds[$id])) { 387 | $willDeleted[] = $node; 388 | }else{ 389 | return $node; 390 | } 391 | } 392 | }); 393 | }else{ 394 | $edit->treeToListCallback($edit->DOMDocument,function($node) use($edit,$deleteTags,$usedCommentIds,&$willDeleted) { 395 | if($edit->getAttr($node,'md',null) || isset($deleteTags[$node->localName])) { 396 | $willDeleted[] = $node; 397 | }else{ 398 | return $node; 399 | } 400 | }); 401 | } 402 | 403 | foreach($willDeleted as $node) { 404 | $edit->removeChild($node); 405 | } 406 | } 407 | 408 | /** 409 | * 410 | * @param string $filename 411 | * @return \DOMDocument 412 | */ 413 | public function getXmlDom($filename,$standardXmlFunc=null) { 414 | if(is_null($filename) && !is_null($standardXmlFunc)) { 415 | $xml = $standardXmlFunc(); 416 | }else{ 417 | $xml = $this->zip->getFromName($filename); 418 | if(!is_null($standardXmlFunc)) { 419 | $xml = $standardXmlFunc($xml); 420 | } 421 | } 422 | $domDocument = new \DOMDocument(); 423 | $domDocument->loadXML($xml); 424 | if(MDWORD_DEBUG === true) { 425 | $domDocument->formatOutput = true; 426 | } 427 | 428 | return $domDocument; 429 | } 430 | 431 | protected function getZipFiles() { 432 | static $pathIndx = null; 433 | 434 | if(is_null($pathIndx)) { 435 | for($i=0;$i < $this->zip->numFiles; $i++) { 436 | $name = $this->zip->getNameIndex($i); 437 | $nameArr = explode('/', $name); 438 | 439 | $item = &$pathIndx; 440 | foreach ($nameArr as $pathOrName) { 441 | if(!isset($item[$pathOrName])) { 442 | $item[$pathOrName] = []; 443 | } 444 | $item = &$item[$pathOrName]; 445 | } 446 | } 447 | unset($item); 448 | } 449 | 450 | return $pathIndx; 451 | } 452 | 453 | public function getChartParts(){ 454 | if(isset($this->parts[13])){ 455 | $chartPartsArr[13] = $this->getPartsDetail($this->parts[13]); 456 | } 457 | if(isset($this->parts[25])){ 458 | $chartPartsArr[25] = $this->parts[25]; 459 | } 460 | if(isset($this->parts[26])){ 461 | $chartPartsArr[26] = $this->parts[26]; 462 | } 463 | return $chartPartsArr; 464 | } 465 | 466 | public function getPartsDetail($parts){ 467 | foreach($parts as &$part){ 468 | $partInfo = pathinfo($part['PartName']); 469 | $partNameRel = $partInfo['dirname'].'/_rels/'.$partInfo['basename'].'.rels'; 470 | $chartRelDom = $this->getXmlDom($partNameRel); 471 | if(!empty($chartRelDom)){ 472 | $part['chartRelDom'] = $chartRelDom; 473 | } 474 | $Relationships = $chartRelDom->getElementsByTagName('Relationship'); 475 | foreach($Relationships as $Relationship){ 476 | $target = $Relationship->getAttribute('Target'); 477 | if(strpos($target,'xlsx') != false){ 478 | $part['embeddings']['xml'] = $this->zip->getFromName(str_replace('../','word/',$target)); 479 | preg_match('/embeddings\/([\s\S]+)/',$target,$match); 480 | $part['embeddings']['name'] = $match[1]; 481 | } 482 | } 483 | } 484 | return $parts; 485 | } 486 | 487 | public function setChartParts($chartparts){ 488 | foreach($chartparts as $key=>&$part){ 489 | if($key==13){ 490 | if(!empty($this->parts[13])){ 491 | foreach($this->parts[13] as $keypart){ 492 | $partName = $keypart['PartName']; 493 | preg_match('/(\d+)/',$partName,$match); 494 | $chartKey = $match[1]; 495 | } 496 | foreach($part as &$p){ 497 | $chartKey++; 498 | preg_match('/(\d+)/',$p['PartName'],$partmatch); 499 | $p['PartName'] = str_replace($partmatch[1],$chartKey,$p['PartName']); 500 | $styleKey[$partmatch[1]] = $chartKey; 501 | } 502 | foreach($chartparts[25] as &$stylepart){ 503 | preg_match('/(\d+)/',$stylepart['PartName'],$styleMatch); 504 | $stylepart['PartName'] = str_replace($styleMatch[1],$styleKey[$styleMatch[1]],$stylepart['PartName']); 505 | } 506 | foreach($chartparts[26] as &$colorepart){ 507 | preg_match('/(\d+)/',$colorepart['PartName'],$colorMatch); 508 | $colorepart['PartName'] = str_replace($colorMatch[1],$styleKey[$colorMatch[1]],$colorepart['PartName']); 509 | } 510 | $this->parts[13] = array_merge($this->parts[13],$chartparts[13]); 511 | }else{ 512 | $this->parts[13] = $chartparts[13]; 513 | } 514 | }else{ 515 | $this->parts[$key] = empty($this->parts[$key])?$chartparts[$key]:array_merge($this->parts[$key],$chartparts[$key]); 516 | } 517 | } 518 | } 519 | 520 | public function getContentTypes(){ 521 | $contentTypes = $this->Content_Types->overrides; 522 | 523 | return $contentTypes; 524 | } 525 | 526 | public function getChartEmbeddings(){ 527 | $fileList = $this->getZipFiles(); 528 | $relArr = $fileList['word']['charts']['_rels']; 529 | $embeddingsList = $fileList['word']['embeddings']; 530 | //$this->documentEdit->setChartRelValue($relArr); 531 | foreach($embeddingsList as $embeddingKey=>$val){ 532 | $embeddingsXml[$embeddingKey] = $this->zip->getFromName('word/embeddings/'.$embeddingKey); 533 | } 534 | return $embeddingsXml; 535 | } 536 | 537 | public function setContentTypes(){ 538 | foreach($this->parts[13] as $part){ 539 | $partArr['PartName'] = $part['PartName']; 540 | $partArr['ContentType'] = 13; 541 | if(array_search($partArr,$this->Content_Types->overrides) === false){ 542 | $newOverrides[] = $partArr; 543 | } 544 | } 545 | foreach($this->parts[25] as $part){ 546 | $partArr['PartName'] = $part['PartName']; 547 | $partArr['ContentType'] = 25; 548 | if(array_search($partArr,$this->Content_Types->overrides) === false){ 549 | $newOverrides[] = $partArr; 550 | } 551 | } 552 | foreach($this->parts[26] as $part){ 553 | $partArr['PartName'] = $part['PartName']; 554 | $partArr['ContentType'] = 26; 555 | if(array_search($partArr,$this->Content_Types->overrides) === false){ 556 | $newOverrides[] = $partArr; 557 | } 558 | } 559 | $this->Content_Types->setContent_types($newOverrides); 560 | $this->writeContentTypeXml(); 561 | 562 | } 563 | 564 | public function writeContentTypeXml(){ 565 | $Relationships = $this->Content_Types->DOMDocument->getElementsByTagName('Override'); 566 | foreach($this->Content_Types->overrides as $key=>$overrides){ 567 | if(!isset($Relationships[$key])){ 568 | $copy = clone $Relationships[0]; 569 | $copy->setAttribute('PartName','/'.$overrides['PartName']); 570 | $copy->setAttribute('ContentType',$this->Content_Types->contentTypes[$overrides['ContentType']]); 571 | $Relationships[0]->parentNode->appendChild($copy); 572 | } 573 | } 574 | $this->zip->addFromString('[Content_Types].xml', $this->Content_Types->DOMDocument->saveXML()); 575 | } 576 | 577 | 578 | public function updateChartRel(){ 579 | foreach($this->parts[13] as $part){ 580 | if(isset($part['chartRelDom'])){ 581 | $Relationships = $part['chartRelDom']->getElementsByTagName('Relationship'); 582 | preg_match('/chart(\d+)/',$part['PartName'],$match); 583 | foreach($Relationships as $Relationship){ 584 | $target = $Relationship->getAttribute('Target'); 585 | $Relationship->setAttribute('Target',preg_replace('/(\d+)/',$match[1],$target)); 586 | } 587 | $relArr['PartName'] = $part['PartName']; 588 | $relArr['relName'] = 'word/charts/_rels/'.$match[0].'.xml.rels'; 589 | $relArr['dom'] = $part['chartRelDom']; 590 | 591 | $this->documentEdit->setChartRel($relArr); 592 | } 593 | } 594 | } 595 | 596 | public function free() { 597 | unset($this->Content_Types->word); 598 | unset($this->Content_Types); 599 | 600 | unset($this->zip,$this->log); 601 | $this->documentEdit->free(); 602 | 603 | unset($this->documentEdit->DOMDocument); 604 | unset($this->documentEdit->commentsEdit); 605 | 606 | unset($this->commentsEdit); 607 | unset($this->stylesEdit); 608 | 609 | //fixed bug:not use unset 610 | // unset($this->documentEdit); 611 | $this->documentEdit = null; 612 | } 613 | } 614 | -------------------------------------------------------------------------------- /src/WordProcessor.php: -------------------------------------------------------------------------------- 1 | wordProcessor = $this; 27 | $reader->load($zip); 28 | $this->words[++$this->wordsIndex] = $reader; 29 | 30 | $this->words[$this->wordsIndex]->commentsEdit = []; 31 | foreach ($this->words[$this->wordsIndex]->parts[15] as $part15) { 32 | $comments = $part15['DOMElement']; 33 | $Comment = new Comments($this->words[$this->wordsIndex],$comments); 34 | $Comment->partName = $part15['PartName']; 35 | $Comment->word = $this->words[$this->wordsIndex]; 36 | $this->words[$this->wordsIndex]->commentsEdit[] = $Comment; 37 | } 38 | 39 | return $this->words[$this->wordsIndex]; 40 | } 41 | 42 | /** 43 | * @return \MDword\Common\Bind 44 | */ 45 | public function getBind($data) { 46 | $bind = new Bind($this,$data); 47 | return $bind; 48 | } 49 | 50 | public function setValue($name, $value, $type=MDWORD_TEXT) { 51 | $updateCount = 0; 52 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 53 | $func = $part['func']; 54 | $partName = $part['partName']; 55 | /** 56 | * @var Document $documentEdit 57 | */ 58 | $documentEdit = $this->$func($partName); 59 | $updateCount += $documentEdit->setValue($name, $value, $type); 60 | } 61 | 62 | return $updateCount; 63 | } 64 | 65 | public function setValues($values,$pre='') { 66 | $updateCount = 0; 67 | foreach ($values as $index => $valueArr) { 68 | foreach($valueArr as $name => $value) { 69 | if(is_array($value)) { 70 | $updateCount += $this->setValues($value,'#'.$index); 71 | }else{ 72 | $updateCount += $this->setValue($name.$pre.'#'.$index, $value); 73 | } 74 | } 75 | } 76 | return $updateCount; 77 | } 78 | 79 | public function getInnerVars() { 80 | return ['levels'=>$this->words[$this->wordsIndex]->documentEdit->getLevels()]; 81 | } 82 | 83 | /** 84 | * delete section include the block。(only in document.xml) 85 | * @param string $name 86 | */ 87 | public function deleteSection($name) { 88 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 89 | $func = $part['func']; 90 | $partName = $part['partName']; 91 | /** 92 | * @var Document $documentEdit 93 | */ 94 | $documentEdit = $this->$func($partName); 95 | $documentEdit->setValue($name, 'section',MDWORD_DELETE); 96 | } 97 | } 98 | 99 | /** 100 | * delete p include the block 101 | * @param string $name 102 | */ 103 | public function deleteP($name) { 104 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 105 | $func = $part['func']; 106 | $partName = $part['partName']; 107 | /** 108 | * @var Document $documentEdit 109 | */ 110 | $documentEdit = $this->$func($partName); 111 | $documentEdit->setValue($name, 'p',MDWORD_DELETE); 112 | } 113 | } 114 | 115 | /** 116 | * delete tbl include the block 117 | * @param string $name 118 | */ 119 | public function deleteTbl($name) { 120 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 121 | $func = $part['func']; 122 | $partName = $part['partName']; 123 | /** 124 | * @var Document $documentEdit 125 | */ 126 | $documentEdit = $this->$func($partName); 127 | $documentEdit->setValue($name, 'tbl',MDWORD_DELETE); 128 | } 129 | } 130 | 131 | /** 132 | * delete tr include the block 133 | * @param string $name 134 | */ 135 | public function deleteTr($name) { 136 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 137 | $func = $part['func']; 138 | $partName = $part['partName']; 139 | /** 140 | * @var Document $documentEdit 141 | */ 142 | $documentEdit = $this->$func($partName); 143 | $documentEdit->setValue($name, 'tr',MDWORD_DELETE); 144 | } 145 | } 146 | 147 | /** 148 | * delete block 149 | * @param string $name 150 | */ 151 | public function delete($name) { 152 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 153 | $func = $part['func']; 154 | $partName = $part['partName']; 155 | /** 156 | * @var Document $documentEdit 157 | */ 158 | $documentEdit = $this->$func($partName); 159 | $documentEdit->setValue($name, '',MDWORD_TEXT); 160 | } 161 | } 162 | 163 | public function setImageValue($name, $value) { 164 | if(strlen($name) === 32) {//media md5 165 | $includeImageEdits = []; 166 | 167 | $documentEdit = $this->getDocumentEdit(); 168 | $rids = $documentEdit->getRidByMd5($name); 169 | if(!empty($rids)) { 170 | $includeImageEdits[] = [$documentEdit,$rids]; 171 | } 172 | 173 | foreach ($this->words[$this->wordsIndex]->parts[22] as $part) { 174 | $partName = $part['PartName']; 175 | $headerEdit = $this->getHeaderEdit($partName); 176 | $rids = $headerEdit->getRidByMd5($name); 177 | if(!empty($rids)) { 178 | $includeImageEdits[] = [$headerEdit,$rids]; 179 | $this->words[$this->wordsIndex]->needUpdateParts[$partName] = ['func'=>'getHeaderEdit','partName'=>$partName]; 180 | } 181 | } 182 | 183 | foreach ($this->words[$this->wordsIndex]->parts[23] as $part) { 184 | $partName = $part['PartName']; 185 | $footerEdit = $this->getFooterEdit($partName); 186 | $rids = $footerEdit->getRidByMd5($name); 187 | if(!empty($rids)) { 188 | $includeImageEdits[] = [$footerEdit,$rids]; 189 | $this->words[$this->wordsIndex]->needUpdateParts[$partName] = ['func'=>'getFooterEdit','partName'=>$partName]; 190 | } 191 | } 192 | 193 | $edit = null; 194 | foreach($includeImageEdits as list($edit,$rids)) { 195 | /** 196 | * @var Document $edit 197 | */ 198 | if (is_array($value)) { 199 | $edit->setValue($rids, $value, MDWORD_TEXT); 200 | }else{ 201 | $edit->setValue($rids, $value, MDWORD_IMG); 202 | } 203 | } 204 | 205 | return ; 206 | } 207 | 208 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 209 | $func = $part['func']; 210 | $partName = $part['partName']; 211 | /** 212 | * @var Document $documentEdit 213 | */ 214 | $documentEdit = $this->$func($partName); 215 | $documentEdit->setValue($name, $value,MDWORD_IMG); 216 | } 217 | } 218 | 219 | /** 220 | * @param string $name 221 | * ['text','link'] 222 | * @param array $value 223 | */ 224 | public function setLinkValue($name, $value) { 225 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 226 | $func = $part['func']; 227 | $partName = $part['partName']; 228 | /** 229 | * @var Document $documentEdit 230 | */ 231 | $documentEdit = $this->$func($partName); 232 | $documentEdit->setValue($name, $value[0],MDWORD_TEXT); 233 | $documentEdit->setValue($name, $value[1],MDWORD_LINK); 234 | } 235 | } 236 | 237 | // /** 238 | // * @param string $name 239 | // * @param array $datas 240 | // * change value ['A1',9,'set'] 241 | // * extention range ['$A$1:$A$5','$A$1:$A$10','ext'] 242 | // */ 243 | // public function setExcelValues($name='',$datas=[]) { 244 | // $documentEdit = $this->getDocumentEdit(); 245 | // $documentEdit->setValue($name, $datas, 'excel'); 246 | // } 247 | 248 | /** 249 | * clone p 250 | * @param string $name 251 | * @param int $count 252 | */ 253 | public function cloneP($name,$count=1) { 254 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 255 | $func = $part['func']; 256 | $partName = $part['partName']; 257 | /** 258 | * @var Document $documentEdit 259 | */ 260 | $documentEdit = $this->$func($partName); 261 | $documentEdit->setValue($name, $count, MDWORD_CLONEP); 262 | } 263 | } 264 | /** 265 | * clone section 266 | * @param string $name 267 | * @param int|array $count 1|[1,$nameTo] 268 | */ 269 | public function cloneSection($name,$count=1) { 270 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 271 | $func = $part['func']; 272 | $partName = $part['partName']; 273 | /** 274 | * @var Document $documentEdit 275 | */ 276 | $documentEdit = $this->$func($partName); 277 | $documentEdit->setValue($name, $count, MDWORD_CLONESECTION); 278 | } 279 | } 280 | /** 281 | * clone 282 | * @param string $name 283 | * @param int $count 284 | */ 285 | public function clones($name,$count=1) { 286 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 287 | $func = $part['func']; 288 | $partName = $part['partName']; 289 | /** 290 | * @var Document $documentEdit 291 | */ 292 | $documentEdit = $this->$func($partName); 293 | $documentEdit->setValue($name, $count, MDWORD_CLONE); 294 | } 295 | } 296 | /** 297 | * clone 298 | * @param string $name 299 | * @param int $count 300 | */ 301 | public function cloneTo($nameTo,$name) { 302 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 303 | $func = $part['func']; 304 | $partName = $part['partName']; 305 | /** 306 | * @var Document $documentEdit 307 | */ 308 | $documentEdit = $this->$func($partName); 309 | $documentEdit->setValue($nameTo, $name, MDWORD_CLONETO); 310 | } 311 | } 312 | 313 | 314 | public function setBreakValue($name, $value) { 315 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 316 | $func = $part['func']; 317 | $partName = $part['partName']; 318 | /** 319 | * @var Document $documentEdit 320 | */ 321 | $documentEdit = $this->$func($partName); 322 | $documentEdit->setValue($name, $value,MDWORD_BREAK); 323 | } 324 | } 325 | 326 | 327 | public function setBreakPageValue($name, $value=1) { 328 | foreach($this->words[$this->wordsIndex]->needUpdateParts as $part) { 329 | $func = $part['func']; 330 | $partName = $part['partName']; 331 | /** 332 | * @var Document $documentEdit 333 | */ 334 | $documentEdit = $this->$func($partName); 335 | $documentEdit->setValue($name, $value,MDWORD_PAGE_BREAK); 336 | } 337 | } 338 | 339 | /** 340 | * update toc 341 | */ 342 | public function updateToc() { 343 | $documentEdit = $this->getDocumentEdit(); 344 | $documentEdit->updateToc(); 345 | } 346 | 347 | /** 348 | * @var Document 349 | */ 350 | public function getHeaderEdit($partName='word/header1.xml') { 351 | if(!isset($this->words[$this->wordsIndex]->headerEdits[$partName])) { 352 | $index = $this->getPartsIndexByPartName(22,$partName); 353 | $document = $this->words[$this->wordsIndex]->parts[22][$index]['DOMElement']; 354 | $blocks = $this->words[$this->wordsIndex]->blocks[22][$partName];//header not add comment 355 | $blocks = $blocks?$blocks:[]; 356 | $headerEdit = new Header($this->words[$this->wordsIndex],$document,$blocks); 357 | $this->words[$this->wordsIndex]->headerEdits[$partName] = $headerEdit; 358 | $this->words[$this->wordsIndex]->headerEdits[$partName]->partName = $this->words[$this->wordsIndex]->parts[22][$index]['PartName']; 359 | }else{ 360 | $headerEdit = $this->words[$this->wordsIndex]->headerEdits[$partName]; 361 | } 362 | 363 | return $headerEdit; 364 | } 365 | 366 | public function getDocumentEdit($partName='word/document.xml') { 367 | if(is_null($this->words[$this->wordsIndex]->documentEdit)) { 368 | $index = $this->getPartsIndexByPartName(2,$partName); 369 | $document = $this->words[$this->wordsIndex]->parts[2][$index]['DOMElement']; 370 | if(isset($this->words[$this->wordsIndex]->blocks[2]) && isset($this->words[$this->wordsIndex]->blocks[2][$partName])) { 371 | $blocks = $this->words[$this->wordsIndex]->blocks[2][$partName]; 372 | }else{ 373 | $blocks = []; 374 | } 375 | 376 | foreach($this->words[$this->wordsIndex]->commentsEdit as $comments) { 377 | if($comments->partName === 'word/comments.xml') { 378 | if(isset($comments->blocks)) { 379 | $blocks = $this->my_array_merge($blocks,$comments->blocks); 380 | } 381 | } 382 | } 383 | $documentEdit = new Document($this->words[$this->wordsIndex],$document,$blocks); 384 | $this->words[$this->wordsIndex]->documentEdit = $documentEdit; 385 | $this->words[$this->wordsIndex]->documentEdit->partName = $this->words[$this->wordsIndex]->parts[2][$index]['PartName']; 386 | }else{ 387 | $documentEdit = $this->words[$this->wordsIndex]->documentEdit; 388 | } 389 | 390 | return $documentEdit; 391 | } 392 | 393 | /** 394 | * @var Document 395 | */ 396 | public function getFooterEdit($partName='word/footer1.xml') { 397 | if(!isset($this->words[$this->wordsIndex]->footerEdits[$partName])) { 398 | $index = $this->getPartsIndexByPartName(23,$partName); 399 | $document = $this->words[$this->wordsIndex]->parts[23][$index]['DOMElement']; 400 | $blocks = $this->words[$this->wordsIndex]->blocks[23][$partName];//footer not add comment 401 | $blocks = $blocks?$blocks:[]; 402 | // var_dump($partName,$document,$blocks); 403 | $footerEdit = new Footer($this->words[$this->wordsIndex],$document,$blocks); 404 | $this->words[$this->wordsIndex]->footerEdits[$partName] = $footerEdit; 405 | $this->words[$this->wordsIndex]->footerEdits[$partName]->partName = $this->words[$this->wordsIndex]->parts[23][$index]['PartName']; 406 | }else{ 407 | $footerEdit = $this->words[$this->wordsIndex]->footerEdits[$partName]; 408 | } 409 | return $footerEdit; 410 | } 411 | 412 | private function getPartsIndexByPartName($type,$partName) { 413 | static $data = []; 414 | 415 | if(!isset($data[$partName])) { 416 | $data = []; 417 | foreach($this->words[$this->wordsIndex]->parts as $part) { 418 | foreach($part as $index => $val) { 419 | $data[$val['PartName']] = $index; 420 | } 421 | } 422 | } 423 | 424 | return $data[$partName]; 425 | } 426 | 427 | /** 428 | * @return Styles 429 | */ 430 | public function getStylesEdit() { 431 | $stylesEdit = $this->words[$this->wordsIndex]->stylesEdit; 432 | if(is_null($stylesEdit)) { 433 | $document = $this->words[$this->wordsIndex]->parts[4][0]['DOMElement']; 434 | $stylesEdit = new Styles($this->words[$this->wordsIndex],$document); 435 | $this->words[$this->wordsIndex]->stylesEdit = $stylesEdit; 436 | $this->words[$this->wordsIndex]->stylesEdit->partName = $this->words[$this->wordsIndex]->parts[4][0]['PartName']; 437 | } 438 | 439 | return $stylesEdit; 440 | } 441 | 442 | /** 443 | * @return Numbering 444 | */ 445 | public function getNumberingEdit() { 446 | $numberingEdit = $this->words[$this->wordsIndex]->numberingEdit; 447 | if(is_null($numberingEdit) && isset($this->words[$this->wordsIndex]->parts[12])) { 448 | $document = $this->words[$this->wordsIndex]->parts[12][0]['DOMElement']; 449 | $numberingEdit = new Numbering($this->words[$this->wordsIndex],$document); 450 | $this->words[$this->wordsIndex]->numberingEdit = $numberingEdit; 451 | $this->words[$this->wordsIndex]->numberingEdit->partName = $this->words[$this->wordsIndex]->parts[12][0]['PartName']; 452 | } 453 | 454 | return $numberingEdit; 455 | } 456 | 457 | public function saveAs($fileName,$remainComments=false,$autoClean=true) 458 | { 459 | $tempFileName = $this->words[$this->wordsIndex]->save($remainComments); 460 | if (file_exists($fileName)) { 461 | unlink($fileName); 462 | } 463 | 464 | file_put_contents($fileName, file_get_contents($tempFileName)); 465 | unlink($tempFileName); 466 | 467 | if($autoClean === true) { 468 | $this->free(); 469 | } 470 | } 471 | 472 | public function saveAsContent() { 473 | $tempFileName = $this->words[$this->wordsIndex]->save(); 474 | $content = file_get_contents($tempFileName); 475 | unlink($tempFileName); 476 | return $content; 477 | } 478 | 479 | public function saveAsToPathForTrace($dir,$baseName) 480 | { 481 | static $idx = 0; 482 | $word = $this->words[$this->wordsIndex]; 483 | $tempFileName = $word->saveForTrace(); 484 | 485 | $fileName = $dir.'/'.$baseName.'-'.str_pad($idx++,3,"0",STR_PAD_LEFT).'.docx'; 486 | 487 | if (file_exists($fileName)) { 488 | unlink($fileName); 489 | } 490 | copy($tempFileName, $fileName); 491 | 492 | $WordProcessor = new WordProcessor(); 493 | $WordProcessor->isForTrace = true; 494 | $WordProcessor->load($fileName); 495 | $WordProcessor->saveAs($fileName); 496 | } 497 | 498 | public function setChartValue($name,$fileName) 499 | { 500 | $reader = new Word(); 501 | $reader->load($fileName); 502 | $this->words[++$this->wordsIndex] = $reader; 503 | $documentEdit = $this->getDocumentEdit(); 504 | 505 | $documentChart = $documentEdit->getDocumentChart(); 506 | 507 | $chartparts = $this->words[$this->wordsIndex]->getChartParts(); 508 | $embeddings = $this->words[$this->wordsIndex]->getChartEmbeddings(); 509 | 510 | $this->words[--$this->wordsIndex]->setChartParts($chartparts); 511 | $documentEdit = $this->getDocumentEdit(); 512 | $documentEdit->setDocumentChart($name,$documentChart); 513 | $this->words[$this->wordsIndex]->updateChartRel(); 514 | $this->words[$this->wordsIndex]->setContentTypes(); 515 | $this->setEmbeddings(); 516 | 517 | } 518 | 519 | public function setEmbeddings(){ 520 | $this->words[$this->wordsIndex]->parts[13]; 521 | foreach($this->words[$this->wordsIndex]->parts[13] as $part){ 522 | if(!empty($part['embeddings'])){ 523 | preg_match('/(\d+)/',$part['PartName'],$match); 524 | $fileName = preg_replace('/(\d+)/',$match[1],$part['embeddings']['name']); 525 | $this->words[$this->wordsIndex]->zip->addFromString('word/embeddings/'.$fileName, $part['embeddings']['xml']); 526 | } 527 | } 528 | } 529 | 530 | public function my_array_merge($arr,$arr2) { 531 | foreach($arr2 as $key => $val) { 532 | $arr[$key] = $val; 533 | } 534 | 535 | return $arr; 536 | } 537 | 538 | public function getMedies() { 539 | $word = $this->words[$this->wordsIndex]; 540 | $numFiles = $word->zip->numFiles; 541 | $showList = []; 542 | for ($i = 0; $i < $numFiles; $i++) { 543 | $name = $word->zip->getNameIndex($i); 544 | if(strpos($name, 'media') > 0) { 545 | $content = $word->zip->getFromIndex($i); 546 | $showList['medias'][] = [ 547 | 'md5' => md5($content), 548 | 'name' => $word->zip->getNameIndex($i), 549 | 'content' => $content, 550 | ]; 551 | } 552 | } 553 | 554 | return $showList['medias']; 555 | } 556 | 557 | public function showMedies() { 558 | $showList = $this->getMedies(); 559 | 560 | foreach($showList as $medias) { 561 | foreach($medias as $media) { 562 | var_dump($media); 563 | echo '
'; 564 | } 565 | 566 | } 567 | } 568 | 569 | public function getBlockList() { 570 | /** 571 | * @var Word $word 572 | */ 573 | $word = $this->words[$this->wordsIndex]; 574 | $tree = []; 575 | foreach($word->parts as $type => $parts) { 576 | 577 | switch ($type) { 578 | case 2: 579 | $func = 'getDocumentEdit'; 580 | break; 581 | case 22: 582 | $func = 'getHeaderEdit'; 583 | break; 584 | case 23: 585 | $func = 'getFooterEdit'; 586 | break; 587 | default: 588 | continue 2; 589 | break; 590 | } 591 | 592 | foreach($parts as $part) { 593 | /** 594 | * @var Document $document 595 | */ 596 | $document = $this->$func($part['PartName']); 597 | $tree[$type][] = $document->getBlockTree(); 598 | } 599 | } 600 | 601 | return $tree; 602 | } 603 | 604 | public function getUsedCommentIds() { 605 | /** 606 | * @var Word $word 607 | */ 608 | $word = $this->words[$this->wordsIndex]; 609 | static $r = null; 610 | 611 | if($r !== null) { 612 | return $r; 613 | } 614 | 615 | $r = []; 616 | 617 | foreach($word->parts as $type => $parts) { 618 | 619 | switch ($type) { 620 | case 2: 621 | $func = 'getDocumentEdit'; 622 | break; 623 | case 22: 624 | $func = 'getHeaderEdit'; 625 | break; 626 | case 23: 627 | $func = 'getFooterEdit'; 628 | break; 629 | default: 630 | continue 2; 631 | break; 632 | } 633 | 634 | foreach($parts as $part) { 635 | /** 636 | * @var Document $document 637 | */ 638 | $document = $this->$func($part['PartName']); 639 | foreach($document->commentsblocks as $id => $comment) { 640 | if(isset($document->usedBlock[$comment])) { 641 | $r[$id] = 1; 642 | } 643 | } 644 | } 645 | } 646 | 647 | return $r; 648 | } 649 | 650 | public function free(){ 651 | foreach($this->words as $key => $word) { 652 | $word->free(); 653 | unset($this->words[$key]); 654 | } 655 | } 656 | 657 | //删除tmp下文件 658 | public function deleteTmpFile(){ 659 | $word = $this->words[$this->wordsIndex]; 660 | $word->deleteTmpFile(); 661 | } 662 | } 663 | -------------------------------------------------------------------------------- /src/XmlTemple/XmlFromPhpword.php: -------------------------------------------------------------------------------- 1 | phpWord = $phpWord; 21 | $this->document = $document; 22 | 23 | // Create parts 24 | $this->parts = array( 25 | 'ContentTypes' => '[Content_Types].xml', 26 | 'Rels' => '_rels/.rels', 27 | 'DocPropsApp' => 'docProps/app.xml', 28 | 'DocPropsCore' => 'docProps/core.xml', 29 | 'DocPropsCustom' => 'docProps/custom.xml', 30 | 'RelsDocument' => 'word/_rels/document.xml.rels', 31 | 'Document' => 'word/document.xml', 32 | 'Comments' => 'word/comments.xml', 33 | 'Styles' => 'word/styles.xml', 34 | 'Numbering' => 'word/numbering.xml', 35 | 'Settings' => 'word/settings.xml', 36 | 'WebSettings' => 'word/webSettings.xml', 37 | 'FontTable' => 'word/fontTable.xml', 38 | 'Theme' => 'word/theme/theme1.xml', 39 | 'RelsPart' => '', 40 | 'Header' => '', 41 | 'Footer' => '', 42 | 'Footnotes' => '', 43 | 'Endnotes' => '', 44 | 'Chart' => '', 45 | ); 46 | foreach (array_keys($this->parts) as $partName) { 47 | $partClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Part\\' . $partName; 48 | if (class_exists($partClass)) { 49 | /** @var \PhpOffice\PhpWord\Writer\Word2007\Part\AbstractPart $part Type hint */ 50 | $part = new $partClass(); 51 | $part->setParentWriter($this); 52 | $this->writerParts[strtolower($partName)] = $part; 53 | } 54 | } 55 | } 56 | 57 | private function writeStyles() { 58 | $skipTags = ['Normal'=>1,'Footnote Reference'=>1]; 59 | $stylesEdit = $this->document->word->wordProcessor->getStylesEdit(); 60 | 61 | $stylesXml = $this->getPartXml('styles'); 62 | $stylesNode = $stylesEdit->createNodeByXml($stylesXml,function($documentElement) { 63 | return $documentElement; 64 | }); 65 | 66 | $names = []; 67 | $styles = $stylesNode->getElementsByTagName('style'); 68 | foreach($styles as $key => $style) { 69 | $nameNode = $style->getElementsByTagName('name')->item(0); 70 | $name = $stylesEdit->getAttr($nameNode, 'val'); 71 | if(isset($skipTags[$name])) { 72 | continue; 73 | } 74 | 75 | $newName = 'PHPWORD'.$key.$name; 76 | $names[$name] = $newName; 77 | $stylesEdit->setAttr($nameNode, 'val', $newName); 78 | 79 | $copy = clone $style; 80 | $stylesEdit->DOMDocument->documentElement->appendChild($copy); 81 | } 82 | 83 | return $names; 84 | } 85 | 86 | private function writeImages() { 87 | $sectionMedia = Media::getElements('section'); 88 | $rids = []; 89 | foreach($sectionMedia as $media) { 90 | if($media['type'] === 'image') { 91 | $refInfo = $this->document->updateRef($media['source'],null,MDWORD_IMG); 92 | $rids['r:id="rId'.($media['rID']+6).'"'] = 'r:id="'.$refInfo['rId'].'"'; 93 | } 94 | } 95 | 96 | return $rids; 97 | } 98 | 99 | private function getPartXml($partName) { 100 | $xml = $this->writerParts[$partName]->write(); 101 | return $xml; 102 | } 103 | 104 | public function createNodesByBodyXml() { 105 | $styleNames = $this->writeStyles(); 106 | $imageRids = $this->writeImages(); 107 | 108 | $xml = $this->getPartXml('document'); 109 | $xml = str_replace( 110 | array_merge(array_keys($styleNames),array_keys($imageRids)), 111 | array_merge(array_values($styleNames),array_values($imageRids)), 112 | $xml); 113 | $body = $this->document->createNodeByXml($xml,function($documentElement) { 114 | return $documentElement->getElementsByTagName('body')->item(0); 115 | }); 116 | 117 | $sectPrs = $body->getElementsByTagName('sectPr'); 118 | if($sectPrs->length > 0) { 119 | $this->document->markDelete($sectPrs->item($sectPrs->length-1)); 120 | } 121 | 122 | return $body->childNodes; 123 | } 124 | 125 | /** 126 | * Save document by name. 127 | * 128 | * @param string $filename 129 | */ 130 | public function save($filename = null) 131 | { 132 | $filename = $this->getTempFile($filename); 133 | $zip = $this->getZipArchive($filename); 134 | $phpWord = $this->getPhpWord(); 135 | // var_dump($this->parts);exit; 136 | 137 | // Content types 138 | $this->contentTypes['default'] = array( 139 | 'rels' => 'application/vnd.openxmlformats-package.relationships+xml', 140 | 'xml' => 'application/xml', 141 | ); 142 | 143 | // Add section media files 144 | $sectionMedia = Media::getElements('section'); 145 | if (!empty($sectionMedia)) { 146 | $this->addFilesToPackage($zip, $sectionMedia); 147 | $this->registerContentTypes($sectionMedia); 148 | foreach ($sectionMedia as $element) { 149 | $this->relationships[] = $element; 150 | } 151 | } 152 | 153 | // Add header/footer media files & relations 154 | $this->addHeaderFooterMedia($zip, 'header'); 155 | $this->addHeaderFooterMedia($zip, 'footer'); 156 | 157 | // Add header/footer contents 158 | $rId = Media::countElements('section') + 6; // @see Rels::writeDocRels for 6 first elements 159 | $sections = $phpWord->getSections(); 160 | // var_dump($this->parts);exit; 161 | foreach ($sections as $section) { 162 | $this->addHeaderFooterContent($section, $zip, 'header', $rId); 163 | $this->addHeaderFooterContent($section, $zip, 'footer', $rId); 164 | } 165 | 166 | $this->addNotes($zip, $rId, 'footnote'); 167 | $this->addNotes($zip, $rId, 'endnote'); 168 | $this->addComments($zip, $rId); 169 | $this->addChart($zip, $rId); 170 | 171 | // var_dump($this->parts);exit; 172 | // echo $this->writerParts['document']->write();exit; 173 | echo $this->writerParts['styles']->write();exit; 174 | 175 | // Write parts 176 | foreach ($this->parts as $partName => $fileName) { 177 | if ($fileName != '') { 178 | $zip->addFromString($fileName, $this->getWriterPart($partName)->write()); 179 | } 180 | } 181 | 182 | // Close zip archive and cleanup temp file 183 | $zip->close(); 184 | $this->cleanupTempFile(); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/XmlTemple/image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /tests/Trace.php: -------------------------------------------------------------------------------- 1 | setValue('$codeName', \$code, 'text', false); 26 | \$this->word->wordProcessor->saveAsToPathForTrace('$dir', '$baseName'); 27 | CODE; 28 | $build->replace('SAVE-ANIMALCODE', $SAVEANIMALCODE, MDWORD_SRC_DIRECTORY.'/Edit/Part/Document.php'); 29 | 30 | require $runSampleFile; 31 | 32 | $build->replace('SAVE-ANIMALCODE', '', MDWORD_SRC_DIRECTORY.'/Edit/Part/Document.php'); 33 | 34 | $words = $common->getDirFiles($dir,function($dir,$wordName) { 35 | return base64_encode(file_get_contents($dir.DIRECTORY_SEPARATOR.$wordName)); 36 | }); 37 | 38 | $imagesStream = $common->CurlSend($wordToImageApi,'',['words'=>json_encode($words)],180000); 39 | $images = json_decode($imagesStream,true); 40 | $frames = $durations = []; 41 | if(!is_array($images['images'])) { 42 | echo $imagesStream;exit; 43 | } 44 | foreach($images['images'] as $image) { 45 | $frames[] = base64_decode($image); 46 | $durations[] = 500; 47 | } 48 | 49 | $gc = new GifCreator(); 50 | $gifBinary = $gc->create($frames, $durations, 0); 51 | 52 | file_put_contents($dir.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.$baseName.'.gif',$gifBinary); 53 | 54 | 55 | function getCodeByTrace($file) { 56 | $traces = debug_backtrace(null,5); 57 | $code = ''; 58 | foreach($traces as $trace) { 59 | if($trace['file'] == $file) { 60 | foreach ($trace['args'] as $key => $arg) { 61 | if(is_array($arg)) { 62 | $trace['args'][$key] = preg_replace('/\s/i', '', var_export($arg,true)); 63 | } 64 | } 65 | $code = $trace['line'].' '.$trace['class'].$trace['type'].$trace['function'].'('.implode(',', $trace['args']).')'; 66 | break; 67 | } 68 | } 69 | 70 | return $code; 71 | } 72 | 73 | /** 74 | * from https://www.cnblogs.com/lixihuan/p/11906094.html 75 | * Create an animated GIF from multiple images 76 | */ 77 | class GifCreator 78 | { 79 | /** 80 | * @var string The gif string source (old: this->GIF) 81 | */ 82 | private $gif; 83 | 84 | /** 85 | * @var string Encoder version (old: this->VER) 86 | */ 87 | private $version; 88 | 89 | /** 90 | * @var boolean Check the image is build or not (old: this->IMG) 91 | */ 92 | private $imgBuilt; 93 | 94 | /** 95 | * @var array Frames string sources (old: this->BUF) 96 | */ 97 | private $frameSources; 98 | 99 | /** 100 | * @var integer Gif loop (old: this->LOP) 101 | */ 102 | private $loop; 103 | 104 | /** 105 | * @var integer Gif dis (old: this->DIS) 106 | */ 107 | private $dis; 108 | 109 | /** 110 | * @var integer Gif color (old: this->COL) 111 | */ 112 | private $colour; 113 | 114 | /** 115 | * @var array (old: this->ERR) 116 | */ 117 | private $errors; 118 | 119 | // Methods 120 | // =================================================================================== 121 | 122 | /** 123 | * Constructor 124 | */ 125 | public function __construct() 126 | { 127 | $this->reset(); 128 | 129 | // Static data 130 | $this->version = 'GifCreator: Under development'; 131 | $this->errors = array( 132 | 'ERR00' => 'Does not supported function for only one image.', 133 | 'ERR01' => 'Source is not a GIF image.', 134 | 'ERR02' => 'You have to give resource image variables, image URL or image binary sources in $frames array.', 135 | 'ERR03' => 'Does not make animation from animated GIF source.', 136 | ); 137 | } 138 | 139 | /** 140 | * Create the GIF string (old: GIFEncoder) 141 | * 142 | * @param array $frames An array of frame: can be file paths, resource image variables, binary sources or image URLs 143 | * @param array $durations An array containing the duration of each frame 144 | * @param integer $loop Number of GIF loops before stopping animation (Set 0 to get an infinite loop) 145 | * 146 | * @return string The GIF string source 147 | */ 148 | public function create($frames = array(), $durations = array(), $loop = 0) 149 | { 150 | if (!is_array($frames) && !is_array($durations)) { 151 | 152 | throw new \Exception($this->version.': '.$this->errors['ERR00']); 153 | } 154 | 155 | $this->loop = ($loop > -1) ? $loop : 0; 156 | $this->dis = 2; 157 | 158 | for ($i = 0; $i < count($frames); $i++) { 159 | 160 | if (is_resource($frames[$i])) { // Resource var 161 | 162 | $resourceImg = $frames[$i]; 163 | 164 | ob_start(); 165 | imagegif($frames[$i]); 166 | $this->frameSources[] = ob_get_contents(); 167 | ob_end_clean(); 168 | 169 | } elseif (is_string($frames[$i])) { // File path or URL or Binary source code 170 | 171 | if (@file_exists($frames[$i]) || filter_var($frames[$i], FILTER_VALIDATE_URL)) { // File path 172 | 173 | $frames[$i] = file_get_contents($frames[$i]); 174 | } 175 | 176 | $resourceImg = imagecreatefromstring($frames[$i]); 177 | 178 | ob_start(); 179 | imagegif($resourceImg); 180 | $this->frameSources[] = ob_get_contents(); 181 | ob_end_clean(); 182 | 183 | } else { // Fail 184 | 185 | throw new \Exception($this->version.': '.$this->errors['ERR02']); 186 | } 187 | 188 | if ($i == 0) { 189 | 190 | $colour = imagecolortransparent($resourceImg); 191 | } 192 | 193 | if (substr($this->frameSources[$i], 0, 6) != 'GIF87a' && substr($this->frameSources[$i], 0, 6) != 'GIF89a') { 194 | 195 | throw new \Exception($this->version.': '.$i.' '.$this->errors['ERR01']); 196 | } 197 | 198 | for ($j = (13 + 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))), $k = TRUE; $k; $j++) { 199 | 200 | switch ($this->frameSources[$i] { $j }) { 201 | 202 | case '!': 203 | 204 | if ((substr($this->frameSources[$i], ($j + 3), 8)) == 'NETSCAPE') { 205 | 206 | throw new \Exception($this->version.': '.$this->errors['ERR03'].' ('.($i + 1).' source).'); 207 | } 208 | 209 | break; 210 | 211 | case ';': 212 | 213 | $k = false; 214 | break; 215 | } 216 | } 217 | 218 | unset($resourceImg); 219 | } 220 | 221 | if (isset($colour)) { 222 | 223 | $this->colour = $colour; 224 | 225 | } else { 226 | 227 | $red = $green = $blue = 0; 228 | $this->colour = ($red > -1 && $green > -1 && $blue > -1) ? ($red | ($green << 8) | ($blue << 16)) : -1; 229 | } 230 | 231 | $this->gifAddHeader(); 232 | //d(count($this->frameSources)); 233 | for ($i = 0; $i < count($this->frameSources); $i++) { 234 | $this->addGifFrames($i, $durations[$i]); 235 | } 236 | 237 | $this->gifAddFooter(); 238 | 239 | return $this->gif; 240 | } 241 | 242 | // Internals 243 | // =================================================================================== 244 | 245 | /** 246 | * Add the header gif string in its source (old: GIFAddHeader) 247 | */ 248 | public function gifAddHeader() 249 | { 250 | $cmap = 0; 251 | 252 | if (ord($this->frameSources[0] { 10 }) & 0x80) { 253 | 254 | $cmap = 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07)); 255 | 256 | $this->gif .= substr($this->frameSources[0], 6, 7); 257 | $this->gif .= substr($this->frameSources[0], 13, $cmap); 258 | $this->gif .= "!\377\13NETSCAPE2.0\3\1".$this->encodeAsciiToChar($this->loop)."\0"; 259 | } 260 | } 261 | 262 | /** 263 | * Add the frame sources to the GIF string (old: GIFAddFrames) 264 | * 265 | * @param integer $i 266 | * @param integer $d 267 | */ 268 | public function addGifFrames($i, $d) 269 | { 270 | 271 | $Locals_str = 13 + 3 * (2 << (ord($this->frameSources[ $i ] { 10 }) & 0x07)); 272 | 273 | $Locals_end = strlen($this->frameSources[$i]) - $Locals_str - 1; 274 | $Locals_tmp = substr($this->frameSources[$i], $Locals_str, $Locals_end); 275 | 276 | $Global_len = 2 << (ord($this->frameSources[0 ] { 10 }) & 0x07); 277 | $Locals_len = 2 << (ord($this->frameSources[$i] { 10 }) & 0x07); 278 | 279 | $Global_rgb = substr($this->frameSources[0], 13, 3 * (2 << (ord($this->frameSources[0] { 10 }) & 0x07))); 280 | $Locals_rgb = substr($this->frameSources[$i], 13, 3 * (2 << (ord($this->frameSources[$i] { 10 }) & 0x07))); 281 | 282 | $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 0).chr(($d >> 0 ) & 0xFF).chr(($d >> 8) & 0xFF)."\x0\x0"; 283 | 284 | if ($this->colour > -1 && ord($this->frameSources[$i] { 10 }) & 0x80) { 285 | 286 | for ($j = 0; $j < (2 << (ord($this->frameSources[$i] { 10 } ) & 0x07)); $j++) { 287 | 288 | if (ord($Locals_rgb { 3 * $j + 0 }) == (($this->colour >> 16) & 0xFF) && 289 | ord($Locals_rgb { 3 * $j + 1 }) == (($this->colour >> 8) & 0xFF) && 290 | ord($Locals_rgb { 3 * $j + 2 }) == (($this->colour >> 0) & 0xFF) 291 | ) { 292 | $Locals_ext = "!\xF9\x04".chr(($this->dis << 2) + 1).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF).chr($j)."\x0"; 293 | break; 294 | } 295 | } 296 | } 297 | 298 | switch ($Locals_tmp { 0 }) { 299 | 300 | case '!': 301 | 302 | $Locals_img = substr($Locals_tmp, 8, 10); 303 | $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18); 304 | 305 | break; 306 | 307 | case ',': 308 | 309 | $Locals_img = substr($Locals_tmp, 0, 10); 310 | $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10); 311 | 312 | break; 313 | } 314 | 315 | if (ord($this->frameSources[$i] { 10 }) & 0x80 && $this->imgBuilt) { 316 | 317 | if ($Global_len == $Locals_len) { 318 | 319 | if ($this->gifBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) { 320 | 321 | $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; 322 | 323 | } else { 324 | 325 | $byte = ord($Locals_img { 9 }); 326 | $byte |= 0x80; 327 | $byte &= 0xF8; 328 | $byte |= (ord($this->frameSources[0] { 10 }) & 0x07); 329 | $Locals_img { 9 } = chr($byte); 330 | $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; 331 | } 332 | 333 | } else { 334 | 335 | $byte = ord($Locals_img { 9 }); 336 | $byte |= 0x80; 337 | $byte &= 0xF8; 338 | $byte |= (ord($this->frameSources[$i] { 10 }) & 0x07); 339 | $Locals_img { 9 } = chr($byte); 340 | $this->gif .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; 341 | } 342 | 343 | } else { 344 | 345 | $this->gif .= $Locals_ext.$Locals_img.$Locals_tmp; 346 | } 347 | 348 | $this->imgBuilt = true; 349 | } 350 | 351 | /** 352 | * Add the gif string footer char (old: GIFAddFooter) 353 | */ 354 | public function gifAddFooter() 355 | { 356 | $this->gif .= ';'; 357 | } 358 | 359 | /** 360 | * Compare two block and return the version (old: GIFBlockCompare) 361 | * 362 | * @param string $globalBlock 363 | * @param string $localBlock 364 | * @param integer $length 365 | * 366 | * @return integer 367 | */ 368 | public function gifBlockCompare($globalBlock, $localBlock, $length) 369 | { 370 | for ($i = 0; $i < $length; $i++) { 371 | 372 | if ($globalBlock { 3 * $i + 0 } != $localBlock { 3 * $i + 0 } || 373 | $globalBlock { 3 * $i + 1 } != $localBlock { 3 * $i + 1 } || 374 | $globalBlock { 3 * $i + 2 } != $localBlock { 3 * $i + 2 }) { 375 | 376 | return 0; 377 | } 378 | } 379 | 380 | return 1; 381 | } 382 | 383 | /** 384 | * Encode an ASCII char into a string char (old: GIFWord) 385 | * 386 | * $param integer $char ASCII char 387 | * 388 | * @return string 389 | */ 390 | public function encodeAsciiToChar($char) 391 | { 392 | return (chr($char & 0xFF).chr(($char >> 8) & 0xFF)); 393 | } 394 | 395 | /** 396 | * Reset and clean the current object 397 | */ 398 | public function reset() 399 | { 400 | $this->frameSources; 401 | $this->gif = 'GIF89a'; // the GIF header 402 | $this->imgBuilt = false; 403 | $this->loop = 0; 404 | $this->dis = 2; 405 | $this->colour = -1; 406 | } 407 | 408 | // Getter / Setter 409 | // =================================================================================== 410 | 411 | /** 412 | * Get the final GIF image string (old: GetAnimation) 413 | * 414 | * @return string 415 | */ 416 | public function getGif() 417 | { 418 | return $this->gif; 419 | } 420 | } -------------------------------------------------------------------------------- /tests/function.php: -------------------------------------------------------------------------------- 1 | $v) { 19 | if($k === 0) { 20 | continue; 21 | } 22 | 23 | $line = ''; 24 | foreach($v as $str) { 25 | $line .= '| '.$str.' '; 26 | } 27 | $line .= '|'; 28 | 29 | $lines[] = $line; 30 | } 31 | 32 | return implode("\r\n",$lines); 33 | } 34 | -------------------------------------------------------------------------------- /tests/samples/api/index.php: -------------------------------------------------------------------------------- 1 | getBlockList(); 10 | 11 | // $getWord = new GetWord(); 12 | // $content = $getWord->build(); 13 | // file_put_contents($rtemplate, $content); -------------------------------------------------------------------------------- /tests/samples/api/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/api/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/api/simple data.csv: -------------------------------------------------------------------------------- 1 | 类型,标题,链接,日期,来源,作者,内容属性,内容关注点,原/转载 2 | 网络新闻,网络新闻 - 张家口全力建设国家级可再生能源示范区 可再生能源发电总量占比近半,http://finance.eastmoney.com/a/202009071621903636.html,2020/9/7,东方财富网,DF524,正面,-,- 3 | 报刊,报刊 - 可再生能源发电总量占比近半,http://hbrb.hebnews.cn/pc/paper/c/202009/07/content_53373.html,2020/9/7,河北日报,,中性,-,- 4 | 财经,财经 - 【美股插水】 道指收市泻807点 纳指重挫5% 收市后纳指期货再插1%(不断更新),https://inews.hket.com/article/2739564/%E3%80%90%E7%BE%8E%E8%82%A1%E6%8F%92%E6%B0%B4%E3%80%91%20%E9%81%93%E6%8C%87%E6%94%B6%E5%B8%82%E7%80%89807%E9%BB%9E%E3%80%80%E7%B4%8D%E6%8C%87%E9%87%8D%E6%8C%AB5-%E3%80%80%E6%94%B6%E5%B8%82%E5%BE%8C%E7%B4%8D%E6%8C%87%E6%9C%9F%E8%B2%A8%E5%86%8D%E6%8F%921-%EF%BC%88%E4%B8%8D%E6%96%B7%E6%9B%B4%E6%96%B0%EF%BC%89,2020/9/4,香港经济日报即时新闻,,负面,-,- 5 | -------------------------------------------------------------------------------- /tests/samples/api/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/api/temple.docx -------------------------------------------------------------------------------- /tests/samples/block/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | $TemplateProcessor->setValue('two', 2); 14 | $TemplateProcessor->setValue('box', 'BOX'); 15 | 16 | $TemplateProcessor->setValue('header', 'MDWORD-HEADER'); 17 | 18 | $TemplateProcessor->setValue('footer', 'MDWORD-FOOTER'); 19 | 20 | $datas = [ 21 | ['price'=>100,'change'=>5,'changepercent'=>0.05], 22 | ['price'=>200,'change'=>-10,'changepercent'=>-0.05], 23 | ['price'=>500,'change'=>100,'changepercent'=>0.20], 24 | ]; 25 | $bind = $TemplateProcessor->getBind($datas); 26 | $bind->bindValue('item',[]) 27 | ->bindValue('stockprice',['price'],'item') 28 | ->bindValue('change',['change'],'item',function($value) { 29 | return [['type'=>MDWORD_TEXT,'text'=>$value]]; 30 | }) 31 | ->bindValue('changepercent',['changepercent'],'item',function($value) { 32 | return [['type'=>MDWORD_TEXT,'text'=>($value*100).'%']]; 33 | }) 34 | ; 35 | 36 | $TemplateProcessor->setValue('b', 'B'); 37 | $TemplateProcessor->setValue('2', 'two'); 38 | 39 | $TemplateProcessor->saveAs($rtemplate); 40 | 41 | -------------------------------------------------------------------------------- /tests/samples/block/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/block/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/block/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/block/temple.docx -------------------------------------------------------------------------------- /tests/samples/break page/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | $TemplateProcessor->setValue('value', 'r-value'); 14 | $TemplateProcessor->setValue('value', 'r-value2'); 15 | 16 | $TemplateProcessor->setImageValue('image', dirname(__FILE__).'/logo.jpg'); 17 | 18 | $TemplateProcessor->clones('people', 3); 19 | 20 | $TemplateProcessor->setValue('name#0', 'colin0'); 21 | $TemplateProcessor->setValue('name#1', [['type'=>MDWORD_PAGE_BREAK]]); 22 | $TemplateProcessor->setValue('name#2', 'colin2'); 23 | 24 | $TemplateProcessor->setValue('sex#1', 'woman'); 25 | 26 | $TemplateProcessor->setValue('age#0', '280'); 27 | $TemplateProcessor->setValue('age#1', '281'); 28 | $TemplateProcessor->setValue('age#2', '282'); 29 | 30 | $TemplateProcessor->deleteP('style'); 31 | 32 | $TemplateProcessor->saveAs($rtemplate); 33 | 34 | -------------------------------------------------------------------------------- /tests/samples/break page/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/break page/temple.docx -------------------------------------------------------------------------------- /tests/samples/clone/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | $datas = [ 14 | ['title'=>'MDword','date'=>date('Y-m-d'),'link'=>'https://github.com/mkdreams/MDword','content'=>'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.'], 15 | ['title'=>'Wake up India, you\'re harming yourself','date'=>'2020-08-05','link'=>'http://epaper.chinadaily.com.cn/a/202008/06/WS5f2b56e4a3107831ec7540e6.html','content'=>'On Tuesday, reports said that the Indian government had announced a ban on Baidu and Weibo, two popular smartphone apps developed in China.
Combined with the recent ban on short video sharing apps such as TikTok and Kwai, and social media app WeChat, India has now blocked its residents from using almost all popular Chinese apps.
That apart, in the past few months, India has provoked border clashes with China, set limitations on Chinese enterprises and imposed higher tariffs on some products imported from China.'], 16 | ]; 17 | $bind = $TemplateProcessor->getBind($datas); 18 | $bind->bindValue('item',[]) 19 | ->bindValue('title',['title'],'item') 20 | ->bindValue('date',['date'],'item') 21 | ->bindValue('link',['link'],'item',function($value) { 22 | return [['type'=>MDWORD_LINK,'text'=>$value,'link'=>$value]]; 23 | }) 24 | ->bindValue('content',['content'],'item',function($value) { 25 | $valueArr = explode('
', $value); 26 | $texts = []; 27 | foreach($valueArr as $text) { 28 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>$text]; 29 | $texts[] = ['type'=>MDWORD_BREAK,'text'=>2]; 30 | } 31 | 32 | return $texts; 33 | }) 34 | ; 35 | 36 | $bind = $TemplateProcessor->getBind($datas); 37 | $bind->bindValue('tableitem',[]) 38 | ->bindValue('tabletitle',['title'],'tableitem',function($value,$row) { 39 | return [['type'=>MDWORD_LINK,'text'=>$value,'link'=>$row['link']]]; 40 | }) 41 | ->bindValue('tabledate',['date'],'tableitem') 42 | ->bindValue('tablecontent',['content'],'tableitem',function($value) { 43 | $valueArr = explode('
', $value); 44 | $texts = []; 45 | $textsCount = count($valueArr)-1; 46 | foreach($valueArr as $key => $text) { 47 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>'  '.$text]; 48 | if($key < $textsCount) { 49 | $texts[] = ['type'=>MDWORD_BREAK,'text'=>1]; 50 | } 51 | } 52 | 53 | return $texts; 54 | }) 55 | ; 56 | 57 | 58 | 59 | $TemplateProcessor->cloneP('titletext',2); 60 | $TemplateProcessor->setValue('titletext#0',[['type'=>MDWORD_TEXT,'text'=>'clone titletext#0','pstyle'=>'titlestyle-1','style'=>'titlestyle-1']]); 61 | $TemplateProcessor->setValue('titletext#1',[['type'=>MDWORD_TEXT,'text'=>'clone titletext#1','pstyle'=>'titlestyle-1','style'=>'titlestyle-1']]); 62 | $TemplateProcessor->setValue('titletext-sub',[['type'=>MDWORD_TEXT,'text'=>'titletext-sub','pstyle'=>'titlestyle-2']]); 63 | 64 | $TemplateProcessor->cloneSection('section2',2); 65 | $TemplateProcessor->setValue('section2#0','section2#012345'); 66 | $TemplateProcessor->setValue('section2#1','section2#1'); 67 | $TemplateProcessor->setValue('header2#1','header2#1'); 68 | $TemplateProcessor->deleteSection('section2#0'); 69 | $TemplateProcessor->saveAs($rtemplate); 70 | 71 | -------------------------------------------------------------------------------- /tests/samples/clone/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/clone/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/clone/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/clone/temple.docx -------------------------------------------------------------------------------- /tests/samples/clonetable/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/clonetable/img.png -------------------------------------------------------------------------------- /tests/samples/clonetable/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | $datas = [ 14 | ['title'=>'有效成分1信息','A1'=>'中文通用名','B1'=>'中文通用名1','C1'=>'生物活性','D1'=>'生物活性1','F1'=>'结构式','F2'=> dirname(__FILE__).'/img.png'], 15 | ['title'=>'有效成分2信息','A1'=>'中文通用名','B1'=>'中文通用名2','C1'=>'生物活性','D1'=>'生物活性2','F1'=>'结构式','F2'=> dirname(__FILE__).'/img.png'], 16 | ]; 17 | 18 | 19 | $TemplateProcessor->clones('table',count($datas)); 20 | foreach($datas as $key => $data) { 21 | foreach($data as $vk => $v) { 22 | if($vk === 'F2') { 23 | $TemplateProcessor->setValue($vk.'#'.$key,[['type'=>MDWORD_IMG,'text'=>$v]]); 24 | }else{ 25 | $TemplateProcessor->setValue($vk.'#'.$key,[['type'=>MDWORD_TEXT,'text'=>$v]]); 26 | } 27 | } 28 | } 29 | 30 | $TemplateProcessor->saveAs($rtemplate); 31 | 32 | -------------------------------------------------------------------------------- /tests/samples/clonetable/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/clonetable/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/clonetable/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/clonetable/temple.docx -------------------------------------------------------------------------------- /tests/samples/image/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/full.png -------------------------------------------------------------------------------- /tests/samples/image/img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/img.jpg -------------------------------------------------------------------------------- /tests/samples/image/img2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/img2.bmp -------------------------------------------------------------------------------- /tests/samples/image/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/img3.png -------------------------------------------------------------------------------- /tests/samples/image/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | 14 | $TemplateProcessor->clones('row',3); 15 | $TemplateProcessor->setValue('rowIndex#0',0); 16 | $TemplateProcessor->setValue('rowIndex#1',1); 17 | $TemplateProcessor->setValue('rowIndex#2',2); 18 | $TemplateProcessor->setValue('rowIndex#2',3); 19 | $TemplateProcessor->setValue('rowIndex#2',4); 20 | 21 | $TemplateProcessor->setImageValue('rowImage#0',dirname(__FILE__).'/img.jpg'); 22 | $TemplateProcessor->setImageValue('rowImage#1',dirname(__FILE__).'/img2.bmp'); 23 | 24 | $rows = [ 25 | ['index'=>2,'image'=> dirname(__FILE__).'/img.jpg'], 26 | ['index'=>3,'image'=> dirname(__FILE__).'/img3.png'], 27 | ['index'=>4,'image'=> dirname(__FILE__).'/img3.png'], 28 | ]; 29 | 30 | $bind = $TemplateProcessor->getBind($rows); 31 | $bind->bindValue('row#2',[]) 32 | ->bindValue('rowIndex#2',['index'],'row#2') 33 | ->bindValue('rowImage#2',['image'],'row#2',MDWORD_IMG) 34 | ; 35 | 36 | $TemplateProcessor->setImageValue('rowImage#2#0',dirname(__FILE__).'/img.jpg'); 37 | 38 | $TemplateProcessor->setValue('image insert', [['text' => dirname(__FILE__).'/words.png','type' => MDWORD_IMG,'width'=>300]]); 39 | 40 | $TemplateProcessor->setImageValue('image replace', dirname(__FILE__).'/img.jpg'); 41 | 42 | //replace image by md5 43 | $Medies = $TemplateProcessor->getMedies(); 44 | foreach($Medies as $Medie) { 45 | if('a2a9f8b099d7c5764da685b628468052' === $Medie['md5']) { 46 | $TemplateProcessor->setImageValue($Medie['md5'], dirname(__FILE__).'/img.jpg'); 47 | } 48 | } 49 | 50 | $TemplateProcessor->setValue("full", [['text' => dirname(__FILE__).'/full.png','temple'=>file_get_contents(dirname(__FILE__).'/temple/full.xml'),'type' => MDWORD_IMG,'width'=>'100%']]); 51 | $TemplateProcessor->setValue("full2", [ 52 | ['type' => MDWORD_TEXT,'text'=> "This is text."], 53 | ['type' => MDWORD_PAGE_BREAK,'replace'=>false], 54 | ['text' => dirname(__FILE__).'/full.png','temple'=>file_get_contents(dirname(__FILE__).'/temple/full.xml'),'type' => MDWORD_IMG,'width'=>'100%'], 55 | ['type' => MDWORD_PAGE_BREAK,'replace'=>false], 56 | ['text' => dirname(__FILE__).'/full.png','temple'=>file_get_contents(dirname(__FILE__).'/temple/full.xml'),'type' => MDWORD_IMG,'width'=>'100%'], 57 | ['type' => MDWORD_PAGE_BREAK,'replace'=>false], 58 | ['type' => MDWORD_TEXT,'text'=> "This is text2."], 59 | ['type' => MDWORD_PAGE_BREAK,'replace'=>false], 60 | ['type' => MDWORD_TEXT,'text'=> "This is below."], 61 | ['text' => dirname(__FILE__).'/full.png','temple'=>file_get_contents(dirname(__FILE__).'/temple/full.xml'),'type' => MDWORD_IMG,'width'=>'100%'], 62 | ]); 63 | 64 | 65 | $TemplateProcessor->saveAs($rtemplate); 66 | 67 | -------------------------------------------------------------------------------- /tests/samples/image/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/image/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/temple.docx -------------------------------------------------------------------------------- /tests/samples/image/temple/full.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | left 6 | 7 | 8 | top 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 0 52 | 53 | 54 | 0 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/samples/image/words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/image/words.png -------------------------------------------------------------------------------- /tests/samples/memory use/index.php: -------------------------------------------------------------------------------- 1 | $v) { 15 | $markdown .= "\r\n| ".($k+1)." | ".$v." M | ".($k==0?"首次需要加载PHP类":(""))." |"; 16 | } 17 | echo $markdown; 18 | // var_dump($nowMemorys); 19 | 20 | 21 | function main($template,$rtemplate,$bm) { 22 | $TemplateProcessor = new WordProcessor(); 23 | $TemplateProcessor->load($template); 24 | 25 | //simple set value 26 | $TemplateProcessor->setValue('address', 'addr'); 27 | 28 | //simple set value 29 | $TemplateProcessor->setValue('value', 'r-value'); 30 | $TemplateProcessor->setValue('value', 'r-value2'); 31 | 32 | //image 33 | $TemplateProcessor->setImageValue('image', dirname(__FILE__).'/logo.jpg'); 34 | 35 | 36 | //table 37 | $TemplateProcessor->clones('people', 3); 38 | 39 | $TemplateProcessor->setValue('name#0', 'colin0'); 40 | $TemplateProcessor->setValue('name#1', [['text'=>'colin1','style'=>'style','type'=>MDWORD_TEXT]]); 41 | $TemplateProcessor->setValue('name#2', 'colin2'); 42 | 43 | $TemplateProcessor->setValue('sex#1', 'woman'); 44 | 45 | $TemplateProcessor->setValue('age#0', '280'); 46 | $TemplateProcessor->setValue('age#1', '281'); 47 | $TemplateProcessor->setValue('age#2', '282'); 48 | 49 | // list 50 | $TemplateProcessor->cloneP('item',3); 51 | $TemplateProcessor->setValue('item#0','ITEM1'); 52 | $TemplateProcessor->setValue('item#1','ITEM2'); 53 | $TemplateProcessor->setValue('item#2','ITEM3'); 54 | 55 | // TOC and bind data 56 | $redWords = 'WORD'; 57 | $datas = [ 58 | ['title'=>'MDword Github','date'=>date('Y-m-d'),'link'=>'https://github.com/mkdreams/MDword','content'=>'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.'], 59 | ['title'=>'Wake up India, you\'re harming yourself','date'=>'2020-08-05','link'=>'http://epaper.chinadaily.com.cn/a/202008/06/WS5f2b56e4a3107831ec7540e6.html','content'=>'On Tuesday, reports said that the Indian government had announced a ban on Baidu and Weibo, two popular smartphone apps developed in China.
Combined with the recent ban on short video sharing apps such as TikTok and Kwai, and social media app WeChat, India has now blocked its residents from using almost all popular Chinese apps.
That apart, in the past few months, India has provoked border clashes with China, set limitations on Chinese enterprises and imposed higher tariffs on some products imported from China.'], 60 | ]; 61 | $bind = $TemplateProcessor->getBind($datas); 62 | 63 | $bind->bindValue('news',[]) 64 | ->bindValue('title',['title'],'news') 65 | ->bindValue('date',['date'],'news') 66 | ->bindValue('link',['link'],'news',function($value) { 67 | return [['type'=>MDWORD_LINK,'text'=>$value,'link'=>$value]]; 68 | }) 69 | ->bindValue('content',['content'],'news',function($value) use($redWords) { 70 | $valueArr = explode('
', $value); 71 | $texts = []; 72 | foreach($valueArr as $text) { 73 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>$text]; 74 | $tempTexts = explode($redWords,$text); 75 | foreach($tempTexts as $idx => $tempText) { 76 | 77 | if($idx != 0) { 78 | $texts[] = ['type'=>MDWORD_TEXT,'style'=>'red','text'=>$redWords]; 79 | } 80 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>$tempText]; 81 | } 82 | 83 | $texts[] = ['type'=>MDWORD_BREAK,'text'=>2]; 84 | } 85 | 86 | return $texts; 87 | }) 88 | ; 89 | $bind::$binds = []; 90 | 91 | $TemplateProcessor->deleteP('style'); 92 | $TemplateProcessor->deleteP('red'); 93 | $TemplateProcessor->saveAs($rtemplate); 94 | return (memory_get_usage()-$bm)/1024/1024; 95 | } 96 | 97 | 98 | -------------------------------------------------------------------------------- /tests/samples/memory use/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/memory use/logo.jpg -------------------------------------------------------------------------------- /tests/samples/memory use/word config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/memory use/word config.png -------------------------------------------------------------------------------- /tests/samples/memory use/word result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/memory use/word result.png -------------------------------------------------------------------------------- /tests/samples/memory use/word.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/memory use/word.gif -------------------------------------------------------------------------------- /tests/samples/merge table cells/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | //table 14 | $TemplateProcessor->clones('people', 5); 15 | 16 | $TemplateProcessor->setValue('name#0', 'colin0'); 17 | $TemplateProcessor->setValue('name#1', [['text' => 'colin1', 'table_style' => ['vMerge'=>3,'gridSpan'=>2], 'type' => MDWORD_TEXT]]); 18 | $TemplateProcessor->setValue('name#2', 'colin2'); 19 | 20 | $TemplateProcessor->setValue('sex#1', 'woman'); 21 | 22 | $TemplateProcessor->setValue('age#0', [['text' => '280', 'table_style' => ['gridSpan'=>2], 'type' => MDWORD_TEXT]]); 23 | $TemplateProcessor->setValue('age#2', [['text' => '282', 'table_style' => ['gridSpan'=>2], 'type' => MDWORD_TEXT]]); 24 | 25 | 26 | $TemplateProcessor->setValue('vMerge', [['text' => 'vMerge', 'table_style' => ['vMerge'=>3], 'type' => MDWORD_TEXT]]); 27 | $TemplateProcessor->setValue('vMerge2', [['text' => 'vMerge2', 'table_style' => ['vMerge'=>2], 'type' => MDWORD_TEXT]]); 28 | $TemplateProcessor->setValue('gridSpan', [['text' => 'gridSpan', 'table_style' => ['gridSpan'=>2], 'type' => MDWORD_TEXT]]); 29 | 30 | //delete table 31 | $TemplateProcessor->deleteTbl('row1'); 32 | 33 | $TemplateProcessor->saveAs($rtemplate); 34 | -------------------------------------------------------------------------------- /tests/samples/merge table cells/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/merge table cells/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/merge table cells/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/merge table cells/temple.docx -------------------------------------------------------------------------------- /tests/samples/performance/1750pages.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/performance/1750pages.docx -------------------------------------------------------------------------------- /tests/samples/performance/index.php: -------------------------------------------------------------------------------- 1 | load($template); 25 | $b = microtime(true); 26 | $TemplateProcessor->clones("test",$n); 27 | $datas = []; 28 | for($i = 0;$i<$n;$i++) { 29 | $TemplateProcessor->setValue('test#'.$i, $text); 30 | } 31 | $TemplateProcessor->saveAs($rtemplate); 32 | $e = microtime(true); 33 | $markdownTableDatas[] = ["1页母版赋值".$n."次",number_format($e-$b,2)]; 34 | } 35 | 36 | $template = __DIR__.'/1750pages.docx'; 37 | $rtemplate = __DIR__.'/r-1750pages.docx'; 38 | 39 | foreach($ns as $n) { 40 | $b = microtime(true); 41 | $TemplateProcessor = new WordProcessor(); 42 | $TemplateProcessor->load($template); 43 | $TemplateProcessor->cloneP('test', $n); 44 | for($i=0;$i<$n;$i++) { 45 | $TemplateProcessor->setValue('test#'.$i, 'MDword'.$i); 46 | } 47 | $TemplateProcessor->saveAs($rtemplate); 48 | $e = microtime(true); 49 | $markdownTableDatas[] = ["1750页母版赋值".$n."次",number_format($e-$b,2)]; 50 | } 51 | 52 | 53 | echo markdownTable($markdownTableDatas); 54 | return ; 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/samples/performance/r-1750pages.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/performance/r-1750pages.docx -------------------------------------------------------------------------------- /tests/samples/performance/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/performance/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/performance/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/performance/temple.docx -------------------------------------------------------------------------------- /tests/samples/phpword/index.php: -------------------------------------------------------------------------------- 1 | addSection(); 13 | $section->addText( 14 | '"Learn from yesterday, live for today, hope for tomorrow. ' 15 | . 'The important thing is not to stop questioning." ' 16 | . '(Albert Einstein)' 17 | ); 18 | 19 | $section->addText( 20 | '"Great achievement is usually born of great sacrifice, ' 21 | . 'and is never the result of selfishness." ' 22 | . '(Napoleon Hill)', 23 | array('name' => 'Tahoma', 'size' => 10) 24 | ); 25 | 26 | // Adding Text element with font customized using named font style... 27 | $fontStyleName = 'oneUserDefinedStyle'; 28 | $phpWord->addFontStyle( 29 | $fontStyleName, 30 | array('name' => 'Tahoma', 'size' => 10, 'color' => 'red', 'bold' => true) 31 | ); 32 | $section->addText( 33 | '"The greatest accomplishment is not in never falling, ' 34 | . 'but in rising again after you fall." ' 35 | . '(Vince Lombardi)', 36 | $fontStyleName 37 | ); 38 | 39 | // Adding Text element with font customized using explicitly created font style object... 40 | $fontStyle = new \PhpOffice\PhpWord\Style\Font(); 41 | $fontStyle->setBold(true); 42 | $fontStyle->setName('Tahoma'); 43 | $fontStyle->setSize(13); 44 | $myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)'); 45 | $myTextElement->setFontStyle($fontStyle); 46 | 47 | 48 | $section->addImage(dirname(__FILE__).'/logo.jpg'); 49 | 50 | $TemplateProcessor = new WordProcessor(); 51 | $TemplateProcessor->load($template); 52 | $TemplateProcessor->setValue('name', $phpWord, MDWORD_PHPWORD); 53 | $TemplateProcessor->saveAs($rtemplate); 54 | 55 | -------------------------------------------------------------------------------- /tests/samples/phpword/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/phpword/logo.jpg -------------------------------------------------------------------------------- /tests/samples/phpword/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/phpword/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/phpword/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/phpword/temple.docx -------------------------------------------------------------------------------- /tests/samples/remain comments/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | //simple set value 14 | $TemplateProcessor->setValue('name', 'colin'); 15 | // $TemplateProcessor->setValue('header', 'HEADER'); 16 | // $TemplateProcessor->setValue('footer', 'FOOTER111'); 17 | 18 | $TemplateProcessor->saveAs($rtemplate,true); 19 | 20 | -------------------------------------------------------------------------------- /tests/samples/remain comments/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/remain comments/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/remain comments/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/remain comments/temple.docx -------------------------------------------------------------------------------- /tests/samples/simple for readme/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/img.png -------------------------------------------------------------------------------- /tests/samples/simple for readme/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | //simple set value 14 | $TemplateProcessor->setValue('value', 'r-value'); 15 | $TemplateProcessor->setValue('value', 'r-value2'); 16 | 17 | //image 18 | $TemplateProcessor->setImageValue('image', dirname(__FILE__) . '/logo.jpg'); 19 | 20 | //table 21 | $TemplateProcessor->clones('people', 3); 22 | 23 | $TemplateProcessor->setValue('name#0', 'colin0'); 24 | $TemplateProcessor->setValue('name#1', [['text' => 'colin1', 'style' => 'style', 'type' => MDWORD_TEXT]]); 25 | $TemplateProcessor->setValue('name#2', 'colin2'); 26 | 27 | $TemplateProcessor->setValue('sex#1', 'woman'); 28 | 29 | $TemplateProcessor->setValue('age#0', '280'); 30 | $TemplateProcessor->setValue('age#1', '281'); 31 | $TemplateProcessor->setValue('age#2', '282'); 32 | 33 | //list 34 | $TemplateProcessor->cloneP('item', 3); 35 | $TemplateProcessor->setValue('item#0', 'ITEM1'); 36 | $TemplateProcessor->setValue('item#1', 'ITEM2'); 37 | $TemplateProcessor->setValue('item#2', 'ITEM3'); 38 | 39 | //TOC and bind data 40 | $redWords = 'WORD'; 41 | $datas = [ 42 | [ 43 | 'title' => 'MDword Github', 'date' => date('Y-m-d'), 44 | 'link' => 'https://github.com/mkdreams/MDword', 45 | 'images' => dirname(__FILE__) . '/logo.jpg,' . dirname(__FILE__) . '/img.png', 46 | 'content' => 'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.' 47 | ], 48 | [ 49 | 'title' => 'Wake up India, you\'re harming yourself', 50 | 'date' => '2020-08-05', 51 | 'link' => 'http://epaper.chinadaily.com.cn/a/202008/06/WS5f2b56e4a3107831ec7540e6.html', 52 | 'images' => dirname(__FILE__) . '/logo.jpg', 53 | 'content' => 'On Tuesday, reports said that the Indian government had announced a ban on Baidu and Weibo, two popular smartphone apps developed in China.
Combined with the recent ban on short video sharing apps such as TikTok and Kwai, and social media app WeChat, India has now blocked its residents from using almost all popular Chinese apps.
That apart, in the past few months, India has provoked border clashes with China, set limitations on Chinese enterprises and imposed higher tariffs on some products imported from China.' 54 | ], 55 | ]; 56 | $bind = $TemplateProcessor->getBind($datas); 57 | $bind->bindValue('news', []) 58 | ->bindValue('title', ['title'], 'news') 59 | ->bindValue('date', ['date'], 'news') 60 | ->bindValue('link', ['link'], 'news', function ($value) { 61 | return [['type' => MDWORD_LINK, 'text' => $value, 'link' => $value]]; 62 | }) 63 | ->bindValue('images', ['images'], 'news', function ($value) { 64 | $images = explode(',', $value); 65 | $texts = []; 66 | foreach ($images as $key => $image) { 67 | if ($key === 0) { 68 | $texts[] = ['type' => MDWORD_IMG, 'text' => $image, 'style' => 'imgstyle']; 69 | } else { 70 | $texts[] = ['type' => MDWORD_IMG, 'text' => $image]; 71 | } 72 | } 73 | return $texts; 74 | }) 75 | ->bindValue('content', ['content'], 'news', function ($value) use ($redWords) { 76 | $valueArr = explode('
', $value); 77 | $texts = []; 78 | foreach ($valueArr as $text) { 79 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $text]; 80 | $tempTexts = explode($redWords, $text); 81 | foreach ($tempTexts as $idx => $tempText) { 82 | 83 | if ($idx != 0) { 84 | $texts[] = ['type' => MDWORD_TEXT, 'style' => 'red', 'text' => $redWords]; 85 | } 86 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $tempText]; 87 | } 88 | 89 | $texts[] = ['type' => MDWORD_BREAK, 'text' => 2]; 90 | } 91 | 92 | return $texts; 93 | }); 94 | 95 | $numDatas = [ 96 | [ 97 | 'title'=>'title-1', 98 | 'content'=>'content-1' 99 | ], 100 | [ 101 | 'title'=>'title-2', 102 | 'sub'=>[ 103 | [ 104 | 'title'=>'subTitle-2-1', 105 | 'content'=>'content-2-1', 106 | ], 107 | [ 108 | 'title'=>'subTitle-2-2', 109 | 'content'=>'content-2-2', 110 | ], 111 | ] 112 | ], 113 | [ 114 | 'title'=>'title-3', 115 | 'sub'=>[ 116 | [ 117 | 'title'=>'subTitle-3-1', 118 | 'content'=>'content-3-1', 119 | ], 120 | [ 121 | 'title'=>'subTitle-3-2', 122 | 'content'=>'content-3-2', 123 | ], 124 | ] 125 | ], 126 | ]; 127 | 128 | $TemplateProcessor->cloneP('num',count($numDatas)); 129 | foreach($numDatas as $idx => $numData) { 130 | $TemplateProcessor->cloneP('num'.'#'.$idx,3); 131 | 132 | $TemplateProcessor->setValue('num'.'#'.$idx.'#0',[['text' => $numData['title'], 'pstyle' => 'numstyle-level-1', 'style' => 'numstyle-level-1', 'type' => MDWORD_TEXT]]); 133 | 134 | if(isset($numData['content'])) { 135 | $TemplateProcessor->setValue('num'.'#'.$idx.'#1',[['text' => $numData['content'], 'pstyle' => 'numstyle-level-3', 'style' => 'numstyle-level-3', 'type' => MDWORD_TEXT]]); 136 | }else{ 137 | $TemplateProcessor->deleteP('num'.'#'.$idx.'#1'); 138 | } 139 | 140 | $subName = 'num'.'#'.$idx.'#2'; 141 | if(isset($numData['sub'])) { 142 | $TemplateProcessor->cloneP($subName,count($numData['sub'])); 143 | 144 | foreach($numData['sub'] as $subIdx => $subData) { 145 | $TemplateProcessor->cloneP($subName.'#'.$subIdx,2); 146 | 147 | $TemplateProcessor->setValue($subName.'#'.$subIdx.'#0',[['text' => $subData['title'], 'pstyle' => 'numstyle-level-2', 'style' => 'numstyle-level-2', 'type' => MDWORD_TEXT]]); 148 | $TemplateProcessor->setValue($subName.'#'.$subIdx.'#1',[['text' => $subData['content'], 'pstyle' => 'numstyle-level-3', 'style' => 'numstyle-level-3', 'type' => MDWORD_TEXT]]); 149 | } 150 | }else{ 151 | $TemplateProcessor->deleteP($subName); 152 | } 153 | } 154 | 155 | //links 156 | $TemplateProcessor->setValue('plink', [ 157 | ['type'=>MDWORD_LINK,'text' => 'colin1','link'=>'https://baidu.com?v=1'], 158 | ['type' =>MDWORD_LINK,'text' => 'colin2', 'style' => 'style','link'=>'https://baidu.com?v=2'], 159 | ['type'=>MDWORD_LINK,'text' => 'colin3','link'=>'https://baidu.com?v=3'], 160 | ]); 161 | 162 | $TemplateProcessor->deleteP('numstyle'); 163 | 164 | 165 | $TemplateProcessor->deleteP('style'); 166 | $TemplateProcessor->deleteP('red'); 167 | $TemplateProcessor->deleteP('imgstyle'); 168 | 169 | $TemplateProcessor->saveAs($rtemplate); 170 | -------------------------------------------------------------------------------- /tests/samples/simple for readme/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/logo.jpg -------------------------------------------------------------------------------- /tests/samples/simple for readme/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/simple for readme/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/temple.docx -------------------------------------------------------------------------------- /tests/samples/simple for readme/word config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/word config.png -------------------------------------------------------------------------------- /tests/samples/simple for readme/word result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/word result.png -------------------------------------------------------------------------------- /tests/samples/simple for readme/word.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/simple for readme/word.gif -------------------------------------------------------------------------------- /tests/samples/text/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | $datas = [ 14 | ['price'=>100,'change'=>5,'changepercent'=>0.05], 15 | ['price'=>200,'change'=>-10,'changepercent'=>-0.05], 16 | ['price'=>500,'change'=>100,'changepercent'=>0.20], 17 | ]; 18 | $bind = $TemplateProcessor->getBind($datas); 19 | $bind->bindValue('item',[]) 20 | ->bindValue('stockprice',['price'],'item') 21 | ->bindValue('change',['change'],'item',function($value) { 22 | if($value > 0) { 23 | $style = 'red'; 24 | }else{ 25 | $style = 'green'; 26 | } 27 | return [['type'=>MDWORD_TEXT,'text'=>$value,'style'=>$style]]; 28 | }) 29 | ->bindValue('changepercent',['changepercent'],'item',function($value) { 30 | if($value > 0) { 31 | $style = 'red'; 32 | }else{ 33 | $style = 'green'; 34 | } 35 | return [['type'=>MDWORD_TEXT,'text'=>($value*100).'%','style'=>$style]]; 36 | }) 37 | ; 38 | 39 | $TemplateProcessor->saveAs($rtemplate); 40 | 41 | -------------------------------------------------------------------------------- /tests/samples/text/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/text/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/text/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/text/temple.docx -------------------------------------------------------------------------------- /tests/samples/toc/index.php: -------------------------------------------------------------------------------- 1 | load($template); 12 | 13 | //TOC and bind data 14 | $redWords = 'WORD'; 15 | $datas = [ 16 | ['title'=>'MDword Github','date'=>date('Y-m-d'),'link'=>'https://github.com/mkdreams/MDword','content'=>'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.'], 17 | ['title'=>'Wake up India, you\'re harming yourself','date'=>'2020-08-05','link'=>'http://epaper.chinadaily.com.cn/a/202008/06/WS5f2b56e4a3107831ec7540e6.html','content'=>'On Tuesday, reports said that the Indian government had announced a ban on Baidu and Weibo, two popular smartphone apps developed in China.
Combined with the recent ban on short video sharing apps such as TikTok and Kwai, and social media app WeChat, India has now blocked its residents from using almost all popular Chinese apps.
That apart, in the past few months, India has provoked border clashes with China, set limitations on Chinese enterprises and imposed higher tariffs on some products imported from China.'], 18 | ['title'=>'MDword Github','date'=>date('Y-m-d'),'link'=>'https://github.com/mkdreams/MDword','content'=>'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.'], 19 | ]; 20 | $bind = $TemplateProcessor->getBind($datas); 21 | $bind->bindValue('news',[]) 22 | ->bindValue('title',['title'],'news') 23 | ->bindValue('date',['date'],'news') 24 | ->bindValue('link',['link'],'news',function($value) { 25 | return [['type'=>MDWORD_LINK,'text'=>$value,'link'=>$value]]; 26 | }) 27 | ->bindValue('content',['content'],'news',function($value) use($redWords) { 28 | $valueArr = explode('
', $value); 29 | $texts = []; 30 | foreach($valueArr as $text) { 31 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>$text]; 32 | $tempTexts = explode($redWords,$text); 33 | foreach($tempTexts as $idx => $tempText) { 34 | 35 | if($idx != 0) { 36 | $texts[] = ['type'=>MDWORD_TEXT,'style'=>'red','text'=>$redWords]; 37 | } 38 | $texts[] = ['type'=>MDWORD_TEXT,'text'=>$tempText]; 39 | } 40 | 41 | $texts[] = ['type'=>MDWORD_BREAK,'text'=>2]; 42 | } 43 | 44 | return $texts; 45 | }) 46 | ; 47 | 48 | 49 | //toc table 50 | $InnerVars = $TemplateProcessor->getInnerVars(); 51 | 52 | $TemplateProcessor->clones('table_item', 3); 53 | 54 | $TemplateProcessor->setValue('table_title#0',['text'=>$InnerVars['levels'][1]['text'],'name'=>$InnerVars['levels'][1]['name']],MDWORD_REF); 55 | $TemplateProcessor->setValue('table_title#1',['text'=>$InnerVars['levels'][2]['text'],'name'=>$InnerVars['levels'][2]['name']],MDWORD_REF); 56 | $TemplateProcessor->setValue('table_title#2',['text'=>$InnerVars['levels'][3]['text'],'name'=>$InnerVars['levels'][3]['name']],MDWORD_REF); 57 | 58 | $TemplateProcessor->setValue('table_page#0',['name'=>$InnerVars['levels'][1]['name']],MDWORD_PAGEREF); 59 | $TemplateProcessor->setValue('table_page#1',['name'=>$InnerVars['levels'][2]['name']],MDWORD_PAGEREF); 60 | $TemplateProcessor->setValue('table_page#2',['name'=>$InnerVars['levels'][3]['name']],MDWORD_PAGEREF); 61 | 62 | //page 63 | $TemplateProcessor->setValue('now_page',[],MDWORD_NOWPAGE); 64 | $TemplateProcessor->setValue('total_page',[],MDWORD_TOTALPAGE); 65 | 66 | $TemplateProcessor->setValue('toc_page',['name'=>'bookmarket_toc'],MDWORD_PAGEREF); 67 | 68 | 69 | $TemplateProcessor->saveAs($rtemplate); 70 | 71 | -------------------------------------------------------------------------------- /tests/samples/toc/r-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/toc/r-temple.docx -------------------------------------------------------------------------------- /tests/samples/toc/temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/toc/temple.docx -------------------------------------------------------------------------------- /tests/samples/wps/index.php: -------------------------------------------------------------------------------- 1 | 'MDword Github', 'date' => date('Y-m-d'), 12 | 'content' => 'OFFICE WORD 动态数据 绑定数据 生成报告
OFFICE WORD Dynamic data binding data generation report.' 13 | ], 14 | [ 15 | 'title' => 'Wake up India, you\'re harming yourself', 16 | 'content' => 'On Tuesday, reports said that the Indian government had announced a ban on Baidu and Weibo, two popular smartphone apps developed in China.
Combined with the recent ban on short video sharing apps such as TikTok and Kwai, and social media app WeChat, India has now blocked its residents from using almost all popular Chinese apps.
That apart, in the past few months, India has provoked border clashes with China, set limitations on Chinese enterprises and imposed higher tariffs on some products imported from China.' 17 | ], 18 | ]; 19 | 20 | $template = __DIR__ . '/wps-temple.docx'; 21 | $rtemplate = __DIR__ . '/r-wps-temple.docx'; 22 | $TemplateProcessor = new WordProcessor(); 23 | $TemplateProcessor->load($template); 24 | $bind = $TemplateProcessor->getBind($datas); 25 | $bind->bindValue('news', []) 26 | ->bindValue('title', ['title'], 'news') 27 | ->bindValue('content', ['content'], 'news', function ($value) use ($redWords) { 28 | $valueArr = explode('
', $value); 29 | $texts = []; 30 | foreach ($valueArr as $text) { 31 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $text]; 32 | $tempTexts = explode($redWords, $text); 33 | foreach ($tempTexts as $idx => $tempText) { 34 | 35 | if ($idx != 0) { 36 | $texts[] = ['type' => MDWORD_TEXT, 'style' => 'red', 'text' => $redWords]; 37 | } 38 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $tempText]; 39 | } 40 | 41 | $texts[] = ['type' => MDWORD_BREAK, 'text' => 2]; 42 | } 43 | 44 | $texts[] = ['type' => MDWORD_PAGE_BREAK]; 45 | return $texts; 46 | }); 47 | $TemplateProcessor->saveAs($rtemplate); 48 | 49 | $template = __DIR__ . '/word-temple.docx'; 50 | $rtemplate = __DIR__ . '/r-word-temple.docx'; 51 | $TemplateProcessor = new WordProcessor(); 52 | $TemplateProcessor->load($template); 53 | $bind = $TemplateProcessor->getBind($datas); 54 | $bind->bindValue('news', []) 55 | ->bindValue('title', ['title'], 'news') 56 | ->bindValue('content', ['content'], 'news', function ($value) use ($redWords) { 57 | $valueArr = explode('
', $value); 58 | $texts = []; 59 | foreach ($valueArr as $text) { 60 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $text]; 61 | $tempTexts = explode($redWords, $text); 62 | foreach ($tempTexts as $idx => $tempText) { 63 | 64 | if ($idx != 0) { 65 | $texts[] = ['type' => MDWORD_TEXT, 'style' => 'red', 'text' => $redWords]; 66 | } 67 | $texts[] = ['type' => MDWORD_TEXT, 'text' => $tempText]; 68 | } 69 | 70 | $texts[] = ['type' => MDWORD_BREAK, 'text' => 2]; 71 | } 72 | 73 | $texts[] = ['type' => MDWORD_PAGE_BREAK]; 74 | return $texts; 75 | }); 76 | $TemplateProcessor->saveAs($rtemplate); 77 | -------------------------------------------------------------------------------- /tests/samples/wps/r-word-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/wps/r-word-temple.docx -------------------------------------------------------------------------------- /tests/samples/wps/r-wps-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/wps/r-wps-temple.docx -------------------------------------------------------------------------------- /tests/samples/wps/word-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/wps/word-temple.docx -------------------------------------------------------------------------------- /tests/samples/wps/wps-temple.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkdreams/MDword/6e7b1f00bcecf7bbfe94e59568b1ca4288afa1ca/tests/samples/wps/wps-temple.docx --------------------------------------------------------------------------------