Completed
Branch master (ce8742)
by Bingo
02:49
created

DocxDocument::fixTables()   F

Complexity

Conditions 29
Paths 12376

Size

Total Lines 103
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 77.2552

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 29
eloc 69
c 1
b 0
f 1
nc 12376
nop 1
dl 0
loc 103
ccs 43
cts 70
cp 0.6143
crap 77.2552
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace PhpDocxTemplate;
4
5
use DOMDocument;
6
use DOMElement;
7
use Exception;
8
use ZipArchive;
9
use RecursiveIteratorIterator;
10
use RecursiveDirectoryIterator;
11
12
/**
13
 * Class DocxDocument
14
 *
15
 * @package PhpDocxTemplate
16
 */
17
class DocxDocument
18
{
19
    private $path;
20
    private $tmpDir = "./tmp/";
21
    private $document;
22
23
    /**
24
     * Construct an instance of Document
25
     *
26
     * @param string $path - path to the document
27
     *
28
     * @throws Exception
29
     */
30 6
    public function __construct(string $path)
31
    {
32 6
        if (file_exists($path)) {
33 6
            $this->path = $path;
34 6
            $this->tmpDir .= uniqid("", true) . date("His");
35 6
            $this->extract();
36
        } else {
37
            throw new Exception("The template " . $path . " was not found!");
38
        }
39 6
    }
40
41
    /**
42
     * Extract (unzip) document contents
43
     */
44 6
    private function extract(): void
45
    {
46 6
        if (file_exists($this->tmpDir) && is_dir($this->tmpDir)) {
47
            $this->rrmdir($this->tmpDir);
48
        }
49
        
50 6
        mkdir($this->tmpDir);
51
52 6
        $zip = new ZipArchive();
53 6
        $zip->open($this->path);
54 6
        $zip->extractTo($this->tmpDir);
55 6
        $zip->close();
56
57 6
        $this->document = file_get_contents($this->tmpDir . "/word/document.xml");
58 6
    }
59
60
    /**
61
     * Get document.xml contents as DOMDocument
62
     *
63
     * @return DOMDocument
64
     */
65 3
    public function getDOMDocument(): DOMDocument
66
    {
67 3
        $dom = new DOMDocument();
68 3
        $dom->loadXML($this->document);
69 3
        return $dom;
70
    }
71
72
    /**
73
     * Update document.xml contents
74
     *
75
     * @param DOMDocument $dom - new contents
76
     */
77 1
    public function updateDOMDocument(DOMDocument $dom): void
78
    {
79 1
        $this->document = $dom->saveXml();
80 1
        file_put_contents($this->tmpDir . "/word/document.xml", $this->document);
81 1
    }
82
83
    /**
84
     * Fix table corruption
85
     *
86
     * @param string $xml - xml to fix
87
     *
88
     * @return DOMDocument
89
     */
90 1
    public function fixTables(string $xml): DOMDocument
91
    {
92 1
        $dom = new DOMDocument();
93 1
        $dom->loadXML($xml);
94 1
        $tables = $dom->getElementsByTagName('tbl');
95 1
        foreach ($tables as $table) {
96 1
            $columns = [];
97 1
            $columnsLen = 0;
98 1
            $toAdd = 0;
99 1
            $tableGrid = null;
100 1
            foreach ($table->childNodes as $el) {
101 1
                if ($el->nodeName == 'w:tblGrid') {
102 1
                    $tableGrid = $el;
103 1
                    foreach ($el->childNodes as $col) {
104 1
                        if ($col->nodeName == 'w:gridCol') {
105 1
                            $columns[] = $col;
106 1
                            $columnsLen += 1;
107
                        }
108
                    }
109 1
                } elseif ($el->nodeName == 'w:tr') {
110 1
                    $cellsLen = 0;
111 1
                    foreach ($el->childNodes as $col) {
112 1
                        if ($col->nodeName == 'w:tc') {
113 1
                            $cellsLen += 1;
114
                        }
115
                    }
116 1
                    if (($columnsLen + $toAdd) < $cellsLen) {
117 1
                        $toAdd = $cellsLen - $columnsLen;
118
                    }
119
                }
120
            }
121
122
            // add columns, if necessary
123 1
            if (!is_null($tableGrid) && $toAdd > 0) {
124
                $width = 0;
125
                foreach ($columns as $col) {
126
                    if (!is_null($col->getAttribute('w:w'))) {
127
                        $width += $col->getAttribute('w:w');
128
                    }
129
                }
130
                if ($width > 0) {
131
                    $oldAverage = $width / $columnsLen;
132
                    $newAverage = round($width / ($columnsLen + $toAdd));
133
                    foreach ($columns as $col) {
134
                        $col->setAttribute('w:w', round($col->getAttribute('w:w') * $newAverage / $oldAverage));
135
                    }
136
                    while ($toAdd > 0) {
137
                        $newCol = $dom->createElement("w:gridCol");
138
                        $newCol->setAttribute('w:w', $newAverage);
139
                        $tableGrid->appendChild($newCol);
140
                        $toAdd -= 1;
141
                    }
142
                }
143
            }
144
145
            // remove columns, if necessary
146 1
            $columns = [];
147 1
            foreach ($tableGrid->childNodes as $col) {
148 1
                if ($col->nodeName == 'w:gridCol') {
149 1
                    $columns[] = $col;
150
                }
151
            }
152 1
            $columnsLen = count($columns);
153
154 1
            $cellsLen = 0;
155 1
            $cellsLenMax = 0;
156 1
            foreach ($table->childNodes as $el) {
157 1
                if ($el->nodeName == 'w:tr') {
158 1
                    $cells = [];
159 1
                    foreach ($el->childNodes as $col) {
160 1
                        if ($col->nodeName == 'w:tc') {
161 1
                            $cells[] = $col;
162
                        }
163
                    }
164 1
                    $cellsLen = $this->getCellLen($cells);
165 1
                    $cellsLenMax = max($cellsLenMax, $cellsLen);
166
                }
167
            }
168 1
            $toRemove = $cellsLen - $cellsLenMax;
169 1
            if ($toRemove > 0) {
170
                $removedWidth = 0.0;
171
                for ($i = $columnsLen - 1; ($i + 1) >= $toRemove; $i -= 1) {
172
                    $extraCol = $columns[$i];
173
                    $removedWidth += $extraCol->getAttribute('w:w');
174
                    $tableGrid->removeChild($extraCol);
175
                }
176
177
                $columnsLeft = [];
178
                foreach ($tableGrid->childNodes as $col) {
179
                    if ($col->nodeName == 'w:gridCol') {
180
                        $columnsLeft[] = $col;
181
                    }
182
                }
183
                $extraSpace = 0;
184
                if (count($columnsLeft) > 0) {
185
                    $extraSpace = $removedWidth / count($columnsLeft);
186
                }
187
                foreach ($columnsLeft as $col) {
188 1
                    $col->setAttribute('w:w', round($col->getAttribute('w:w') + $extraSpace));
189
                }
190
            }
191
        }
192 1
        return $dom;
193
    }
194
195
    /**
196
     * Get total cells length
197
     *
198
     * @param array $cells - cells
199
     *
200
     * @return int
201
     */
202 1
    private function getCellLen(array $cells): int
203
    {
204 1
        $total = 0;
205 1
        foreach ($cells as $cell) {
206 1
            foreach ($cell->childNodes as $tc) {
207 1
                if ($tc->nodeName == 'w:tcPr') {
208 1
                    foreach ($tc->childNodes as $span) {
209 1
                        if ($span->nodeName == 'w:gridSpan') {
210 1
                            $total += intval($span->getAttribute('w:val'));
211 1
                            break;
212
                        }
213
                    }
214 1
                    break;
215
                }
216
            }
217
        }
218 1
        return $total + 1;
219
    }
220
221
    /**
222
     * Save the document to the target path
223
     *
224
     * @param string $path - target path
225
     */
226 1
    public function save(string $path): void
227
    {
228 1
        $rootPath = realpath($this->tmpDir);
229
230 1
        $zip = new ZipArchive();
231 1
        $zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
232
233 1
        $files = new RecursiveIteratorIterator(
234 1
            new RecursiveDirectoryIterator($rootPath),
235 1
            RecursiveIteratorIterator::LEAVES_ONLY
236
        );
237
238 1
        foreach ($files as $name => $file) {
239 1
            if (!$file->isDir()) {
240 1
                $filePath = $file->getRealPath();
241 1
                $relativePath = substr($filePath, strlen($rootPath) + 1);
242 1
                $zip->addFile($filePath, $relativePath);
243
            }
244
        }
245
246 1
        $zip->close();
247
248 1
        $this->rrmdir($this->tmpDir);
249 1
    }
250
251
    /**
252
     * Remove recursively directory
253
     *
254
     * @param string $dir - target directory
255
     */
256 6
    private function rrmdir(string $dir): void
257
    {
258 6
        if (is_dir($dir)) {
259 6
            $objects = scandir($dir);
260 6
            foreach ($objects as $object) {
261 6
                if ($object != "." && $object != "..") {
262 6
                    if (filetype($dir . "/" . $object) == "dir") {
263 6
                        $this->rrmdir($dir . "/" . $object);
264
                    } else {
265 6
                        unlink($dir . "/" . $object);
266
                    }
267
                }
268
            }
269 6
            reset($objects);
0 ignored issues
show
Bug introduced by
It seems like $objects can also be of type false; however, parameter $array of reset() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

269
            reset(/** @scrutinizer ignore-type */ $objects);
Loading history...
270 6
            rmdir($dir);
271
        }
272 6
    }
273
274
    /**
275
     * Close document
276
     */
277 5
    public function close(): void
278
    {
279 5
        $this->rrmdir($this->tmpDir);
280 5
    }
281
}
282