• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PHPWord / 13461426829

21 Feb 2025 04:52PM UTC coverage: 96.905% (+0.1%) from 96.767%
13461426829

Pull #2567

github

web-flow
Merge e64a82db6 into 6ca8c9ff6
Pull Request #2567: WIP Do Not Install

300 of 307 new or added lines in 26 files covered. (97.72%)

161 existing lines in 21 files now uncovered.

12681 of 13086 relevant lines covered (96.91%)

37.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.58
/src/PhpWord/Reader/Word2007/AbstractPart.php
1
<?php
2

3
/**
4
 * This file is part of PHPWord - A pure PHP library for reading and writing
5
 * word processing documents.
6
 *
7
 * PHPWord is free software distributed under the terms of the GNU Lesser
8
 * General Public License version 3 as published by the Free Software Foundation.
9
 *
10
 * For the full copyright and license information, please read the LICENSE
11
 * file that was distributed with this source code. For the full list of
12
 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
13
 *
14
 * @see         https://github.com/PHPOffice/PHPWord
15
 *
16
 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
17
 */
18

19
namespace PhpOffice\PhpWord\Reader\Word2007;
20

21
use DateTime;
22
use DOMElement;
23
use InvalidArgumentException;
24
use PhpOffice\Math\Reader\OfficeMathML;
25
use PhpOffice\PhpWord\ComplexType\RubyProperties;
26
use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType;
27
use PhpOffice\PhpWord\Element\AbstractContainer;
28
use PhpOffice\PhpWord\Element\AbstractElement;
29
use PhpOffice\PhpWord\Element\FormField;
30
use PhpOffice\PhpWord\Element\Ruby;
31
use PhpOffice\PhpWord\Element\Text;
32
use PhpOffice\PhpWord\Element\TextRun;
33
use PhpOffice\PhpWord\Element\TrackChange;
34
use PhpOffice\PhpWord\PhpWord;
35
use PhpOffice\PhpWord\Shared\XMLReader;
36

37
/**
38
 * Abstract part reader.
39
 *
40
 * This class is inherited by ODText reader
41
 *
42
 * @since 0.10.0
43
 */
44
abstract class AbstractPart
45
{
46
    /**
47
     * Conversion method.
48
     *
49
     * @const int
50
     */
51
    const READ_VALUE = 'attributeValue';            // Read attribute value
52
    const READ_EQUAL = 'attributeEquals';           // Read `true` when attribute value equals specified value
53
    const READ_TRUE = 'attributeTrue';              // Read `true` when element exists
54
    const READ_FALSE = 'attributeFalse';            // Read `false` when element exists
55
    const READ_SIZE = 'attributeMultiplyByTwo';     // Read special attribute value for Font::$size
56

57
    /**
58
     * Document file.
59
     *
60
     * @var string
61
     */
62
    protected $docFile;
63

64
    /**
65
     * XML file.
66
     *
67
     * @var string
68
     */
69
    protected $xmlFile;
70

71
    /**
72
     * Part relationships.
73
     *
74
     * @var array
75
     */
76
    protected $rels = [];
77

78
    /**
79
     * Comment references.
80
     *
81
     * @var array<string, array<string, AbstractElement>>
82
     */
83
    protected $commentRefs = [];
84

85
    /**
86
     * Image Loading.
87
     *
88
     * @var bool
89
     */
90
    protected $imageLoading = true;
91

92
    /**
93
     * Read part.
94
     */
95
    abstract public function read(PhpWord $phpWord);
96

97
    /**
98
     * Create new instance.
99
     *
100
     * @param string $docFile
101
     * @param string $xmlFile
102
     */
103
    public function __construct($docFile, $xmlFile)
104
    {
105
        $this->docFile = $docFile;
54✔
106
        $this->xmlFile = $xmlFile;
54✔
107
    }
108

109
    /**
110
     * Set relationships.
111
     *
112
     * @param array $value
113
     */
114
    public function setRels($value): void
115
    {
116
        $this->rels = $value;
21✔
117
    }
118

119
    public function setImageLoading(bool $value): self
120
    {
121
        $this->imageLoading = $value;
16✔
122

123
        return $this;
16✔
124
    }
125

126
    public function hasImageLoading(): bool
127
    {
128
        return $this->imageLoading;
6✔
129
    }
130

131
    /**
132
     * Get comment references.
133
     *
134
     * @return array<string, array<string, null|AbstractElement>>
135
     */
136
    public function getCommentReferences(): array
137
    {
138
        return $this->commentRefs;
16✔
139
    }
140

141
    /**
142
     * Set comment references.
143
     *
144
     * @param array<string, array<string, null|AbstractElement>> $commentRefs
145
     */
146
    public function setCommentReferences(array $commentRefs): self
147
    {
148
        $this->commentRefs = $commentRefs;
16✔
149

150
        return $this;
16✔
151
    }
152

153
    /**
154
     * Set comment reference.
155
     */
156
    private function setCommentReference(string $type, string $id, AbstractElement $element): self
157
    {
158
        if (!in_array($type, ['start', 'end'])) {
1✔
159
            throw new InvalidArgumentException('Type must be "start" or "end"');
×
160
        }
161

162
        if (!array_key_exists($id, $this->commentRefs)) {
1✔
163
            $this->commentRefs[$id] = [
1✔
164
                'start' => null,
1✔
165
                'end' => null,
1✔
166
            ];
1✔
167
        }
168
        $this->commentRefs[$id][$type] = $element;
1✔
169

170
        return $this;
1✔
171
    }
172

173
    /**
174
     * Get comment reference.
175
     *
176
     * @return array<string, null|AbstractElement>
177
     */
178
    protected function getCommentReference(string $id): array
179
    {
180
        if (!array_key_exists($id, $this->commentRefs)) {
1✔
181
            throw new InvalidArgumentException(sprintf('Comment with id %s isn\'t referenced in document', $id));
×
182
        }
183

184
        return $this->commentRefs[$id];
1✔
185
    }
186

187
    /**
188
     * Read w:p.
189
     *
190
     * @param AbstractContainer $parent
191
     * @param string $docPart
192
     *
193
     * @todo Get font style for preserve text
194
     */
195
    protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void
196
    {
197
        // Paragraph style
198
        $paragraphStyle = $xmlReader->elementExists('w:pPr', $domNode) ? $this->readParagraphStyle($xmlReader, $domNode) : null;
34✔
199

200
        if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) {
34✔
201
            // FormField
202
            $partOfFormField = false;
3✔
203
            $formNodes = [];
3✔
204
            $formType = null;
3✔
205
            $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag', $domNode);
3✔
206
            if ($textRunContainers > 0) {
3✔
207
                $nodes = $xmlReader->getElements('*', $domNode);
3✔
208
                $paragraph = $parent->addTextRun($paragraphStyle);
3✔
209
                foreach ($nodes as $node) {
3✔
210
                    if ($xmlReader->elementExists('w:fldChar/w:ffData', $node)) {
3✔
211
                        $partOfFormField = true;
3✔
212
                        $formNodes[] = $node;
3✔
213
                        if ($xmlReader->elementExists('w:fldChar/w:ffData/w:ddList', $node)) {
3✔
214
                            $formType = 'dropdown';
1✔
215
                        } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:textInput', $node)) {
2✔
216
                            $formType = 'textinput';
1✔
217
                        } elseif ($xmlReader->elementExists('w:fldChar/w:ffData/w:checkBox', $node)) {
1✔
218
                            $formType = 'checkbox';
3✔
219
                        }
220
                    } elseif ($partOfFormField &&
3✔
221
                        $xmlReader->elementExists('w:fldChar', $node) &&
3✔
222
                        'end' == $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar')
3✔
223
                    ) {
224
                        $formNodes[] = $node;
3✔
225
                        $partOfFormField = false;
3✔
226
                        // Process the form fields
227
                        $this->readFormField($xmlReader, $formNodes, $paragraph, $paragraphStyle, $formType);
3✔
228
                    } elseif ($partOfFormField) {
3✔
229
                        $formNodes[] = $node;
3✔
230
                    } else {
231
                        // normal runs
232
                        $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
3✔
233
                    }
234
                }
235
            }
236
        } elseif ($xmlReader->elementExists('w:r/w:instrText', $domNode)) {
31✔
237
            // PreserveText
238
            $ignoreText = false;
2✔
239
            $textContent = '';
2✔
240
            $fontStyle = $this->readFontStyle($xmlReader, $domNode);
2✔
241
            $nodes = $xmlReader->getElements('w:r', $domNode);
2✔
242
            foreach ($nodes as $node) {
2✔
243
                if ($xmlReader->elementExists('w:lastRenderedPageBreak', $node)) {
2✔
244
                    $parent->addPageBreak();
×
245
                }
246
                $instrText = $xmlReader->getValue('w:instrText', $node);
2✔
247
                if (null !== $instrText) {
2✔
248
                    $textContent .= '{' . $instrText . '}';
2✔
249
                } else {
250
                    if ($xmlReader->elementExists('w:fldChar', $node)) {
2✔
251
                        $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar');
2✔
252
                        if ('begin' == $fldCharType) {
2✔
253
                            $ignoreText = true;
2✔
254
                        } elseif ('end' == $fldCharType) {
2✔
255
                            $ignoreText = false;
2✔
256
                        }
257
                    }
258
                    if (false === $ignoreText) {
2✔
259
                        $textContent .= $xmlReader->getValue('w:t', $node);
2✔
260
                    }
261
                }
262
            }
263
            $parent->addPreserveText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'), $fontStyle, $paragraphStyle);
2✔
264

265
            return;
2✔
266
        }
267

268
        // Formula
269
        $xmlReader->registerNamespace('m', 'http://schemas.openxmlformats.org/officeDocument/2006/math');
34✔
270
        if ($xmlReader->elementExists('m:oMath', $domNode)) {
34✔
271
            $mathElement = $xmlReader->getElement('m:oMath', $domNode);
1✔
272
            $mathXML = $mathElement->ownerDocument->saveXML($mathElement);
1✔
273
            if (is_string($mathXML)) {
1✔
274
                $reader = new OfficeMathML();
1✔
275
                $math = $reader->read($mathXML);
1✔
276

277
                $parent->addFormula($math);
1✔
278
            }
279

280
            return;
1✔
281
        }
282

283
        // List item
284
        if ($xmlReader->elementExists('w:pPr/w:numPr', $domNode)) {
33✔
285
            $numId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:numId');
3✔
286
            $levelId = $xmlReader->getAttribute('w:val', $domNode, 'w:pPr/w:numPr/w:ilvl');
3✔
287
            $nodes = $xmlReader->getElements('*', $domNode);
3✔
288

289
            $listItemRun = $parent->addListItemRun($levelId, "PHPWordList{$numId}", $paragraphStyle);
3✔
290

291
            foreach ($nodes as $node) {
3✔
292
                $this->readRun($xmlReader, $node, $listItemRun, $docPart, $paragraphStyle);
3✔
293
            }
294

295
            return;
3✔
296
        }
297

298
        // Heading or Title
299
        $headingDepth = $xmlReader->elementExists('w:pPr', $domNode) ? $this->getHeadingDepth($paragraphStyle) : null;
32✔
300
        if ($headingDepth !== null) {
32✔
301
            $textContent = null;
7✔
302
            $nodes = $xmlReader->getElements('w:r|w:hyperlink', $domNode);
7✔
303
            $hasRubyElement = $xmlReader->elementExists('w:r/w:ruby', $domNode);
7✔
304
            if ($nodes->length === 1 && !$hasRubyElement) {
7✔
305
                $textContent = htmlspecialchars($xmlReader->getValue('w:t', $nodes->item(0)), ENT_QUOTES, 'UTF-8');
2✔
306
            } else {
307
                $textContent = new TextRun($paragraphStyle);
7✔
308
                foreach ($nodes as $node) {
7✔
309
                    $this->readRun($xmlReader, $node, $textContent, $docPart, $paragraphStyle);
7✔
310
                }
311
            }
312
            $parent->addTitle($textContent, $headingDepth);
7✔
313

314
            return;
7✔
315
        }
316

317
        // Text and TextRun
318
        $textRunContainers = $xmlReader->countElements('w:r|w:ins|w:del|w:hyperlink|w:smartTag|w:commentReference|w:commentRangeStart|w:commentRangeEnd', $domNode);
29✔
319
        if (0 === $textRunContainers) {
29✔
320
            $parent->addTextBreak(1, $paragraphStyle);
8✔
321
        } else {
322
            $nodes = $xmlReader->getElements('*', $domNode);
28✔
323
            $paragraph = $parent->addTextRun($paragraphStyle);
28✔
324
            foreach ($nodes as $node) {
28✔
325
                $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
28✔
326
            }
327
        }
328
    }
329

330
    /**
331
     * @param DOMElement[] $domNodes
332
     * @param AbstractContainer $parent
333
     * @param mixed $paragraphStyle
334
     * @param string $formType
335
     */
336
    private function readFormField(XMLReader $xmlReader, array $domNodes, $parent, $paragraphStyle, $formType): void
337
    {
338
        if (!in_array($formType, ['textinput', 'checkbox', 'dropdown'])) {
3✔
339
            return;
×
340
        }
341

342
        $formField = $parent->addFormField($formType, null, $paragraphStyle);
3✔
343
        $ffData = $xmlReader->getElement('w:fldChar/w:ffData', $domNodes[0]);
3✔
344

345
        foreach ($xmlReader->getElements('*', $ffData) as $node) {
3✔
346
            /** @var DOMElement $node */
347
            switch ($node->localName) {
3✔
348
                case 'name':
3✔
349
                    $formField->setName($node->getAttribute('w:val'));
3✔
350

351
                    break;
3✔
352
                case 'ddList':
3✔
353
                    $listEntries = [];
1✔
354
                    foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
1✔
355
                        switch ($ddListNode->localName) {
1✔
356
                            case 'result':
1✔
357
                                $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode));
1✔
358

359
                                break;
1✔
360
                            case 'default':
1✔
361
                                $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
×
362

363
                                break;
×
364
                            case 'listEntry':
1✔
365
                                $listEntries[] = $xmlReader->getAttribute('w:val', $ddListNode);
1✔
366

367
                                break;
1✔
368
                        }
369
                    }
370
                    $formField->setEntries($listEntries);
1✔
371
                    if (null !== $formField->getValue()) {
1✔
372
                        $formField->setText($listEntries[$formField->getValue()]);
1✔
373
                    }
374

375
                    break;
1✔
376
                case 'textInput':
3✔
377
                    foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
1✔
378
                        switch ($ddListNode->localName) {
1✔
379
                            case 'default':
1✔
380
                                $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
1✔
381

382
                                break;
1✔
383
                            case 'format':
1✔
384
                            case 'maxLength':
1✔
385
                                break;
1✔
386
                        }
387
                    }
388

389
                    break;
1✔
390
                case 'checkBox':
3✔
391
                    foreach ($xmlReader->getElements('*', $node) as $ddListNode) {
1✔
392
                        switch ($ddListNode->localName) {
1✔
393
                            case 'default':
1✔
394
                                $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
1✔
395

396
                                break;
1✔
397
                            case 'checked':
1✔
398
                                $formField->setValue($xmlReader->getAttribute('w:val', $ddListNode));
1✔
399

400
                                break;
1✔
401
                            case 'size':
1✔
402
                            case 'sizeAuto':
1✔
403
                                break;
1✔
404
                        }
405
                    }
406

407
                    break;
1✔
408
            }
409
        }
410

411
        if ('textinput' == $formType) {
3✔
412
            $ignoreText = true;
1✔
413
            $textContent = '';
1✔
414
            foreach ($domNodes as $node) {
1✔
415
                if ($xmlReader->elementExists('w:fldChar', $node)) {
1✔
416
                    $fldCharType = $xmlReader->getAttribute('w:fldCharType', $node, 'w:fldChar');
1✔
417
                    if ('separate' == $fldCharType) {
1✔
418
                        $ignoreText = false;
1✔
419
                    } elseif ('end' == $fldCharType) {
1✔
420
                        $ignoreText = true;
1✔
421
                    }
422
                }
423

424
                if (false === $ignoreText) {
1✔
425
                    $textContent .= $xmlReader->getValue('w:t', $node);
1✔
426
                }
427
            }
428
            $formField->setValue(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'));
1✔
429
            $formField->setText(htmlspecialchars($textContent, ENT_QUOTES, 'UTF-8'));
1✔
430
        }
431
    }
432

433
    /**
434
     * Returns the depth of the Heading, returns 0 for a Title.
435
     *
436
     * @return null|number
437
     */
438
    private function getHeadingDepth(?array $paragraphStyle = null)
439
    {
440
        if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) {
19✔
441
            if ('Title' === $paragraphStyle['styleName']) {
10✔
442
                return 0;
4✔
443
            }
444

445
            $headingMatches = [];
6✔
446
            preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches);
6✔
447
            if (!empty($headingMatches)) {
6✔
448
                return $headingMatches[1];
3✔
449
            }
450
        }
451

452
        return null;
16✔
453
    }
454

455
    /**
456
     * Read w:r.
457
     *
458
     * @param AbstractContainer $parent
459
     * @param string $docPart
460
     * @param mixed $paragraphStyle
461
     *
462
     * @todo Footnote paragraph style
463
     */
464
    protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void
465
    {
466
        if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink', 'w:commentReference'])) {
32✔
467
            $nodes = $xmlReader->getElements('*', $domNode);
5✔
468
            foreach ($nodes as $node) {
5✔
469
                $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle);
5✔
470
            }
471
        } elseif ($domNode->nodeName == 'w:r') {
32✔
472
            $fontStyle = $this->readFontStyle($xmlReader, $domNode);
32✔
473
            $nodes = $xmlReader->getElements('*', $domNode);
32✔
474
            foreach ($nodes as $node) {
32✔
475
                $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle);
32✔
476
            }
477
        }
478

479
        if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
32✔
480
            $node = iterator_to_array($xmlReader->getElements('.//*["commentReference"=local-name()]', $domNode))[0];
1✔
481
            $attributeIdentifier = $node->attributes->getNamedItem('id');
1✔
482
            if ($attributeIdentifier) {
1✔
483
                $id = $attributeIdentifier->nodeValue;
1✔
484

485
                $this->setCommentReference('start', $id, $parent->getElement($parent->countElements() - 1));
1✔
486
                $this->setCommentReference('end', $id, $parent->getElement($parent->countElements() - 1));
1✔
487
            }
488
        }
489
    }
490

491
    /**
492
     * Parses nodes under w:r.
493
     *
494
     * @param string $docPart
495
     * @param mixed $paragraphStyle
496
     * @param mixed $fontStyle
497
     */
498
    protected function readRunChild(XMLReader $xmlReader, DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null): void
499
    {
500
        $runParent = $node->parentNode->parentNode;
32✔
501
        if ($node->nodeName == 'w:footnoteReference') {
32✔
502
            // Footnote
503
            $wId = $xmlReader->getAttribute('w:id', $node);
3✔
504
            $footnote = $parent->addFootnote();
3✔
505
            $footnote->setRelationId($wId);
3✔
506
        } elseif ($node->nodeName == 'w:endnoteReference') {
32✔
507
            // Endnote
508
            $wId = $xmlReader->getAttribute('w:id', $node);
3✔
509
            $endnote = $parent->addEndnote();
3✔
510
            $endnote->setRelationId($wId);
3✔
511
        } elseif ($node->nodeName == 'w:pict') {
32✔
512
            // Image
513
            $rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata');
1✔
514
            $target = $this->getMediaTarget($docPart, $rId);
1✔
515
            if ($this->hasImageLoading() && null !== $target) {
1✔
516
                if ('External' == $this->getTargetMode($docPart, $rId)) {
1✔
UNCOV
517
                    $imageSource = $target;
×
518
                } else {
519
                    $imageSource = "zip://{$this->docFile}#{$target}";
1✔
520
                }
521
                $parent->addImage($imageSource);
1✔
522
            }
523
        } elseif ($node->nodeName == 'w:drawing') {
32✔
524
            // Office 2011 Image
525
            $xmlReader->registerNamespace('wp', 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing');
5✔
526
            $xmlReader->registerNamespace('r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
5✔
527
            $xmlReader->registerNamespace('pic', 'http://schemas.openxmlformats.org/drawingml/2006/picture');
5✔
528
            $xmlReader->registerNamespace('a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
5✔
529

530
            $name = $xmlReader->getAttribute('name', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr');
5✔
531
            $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:inline/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip');
5✔
532
            if ($name === null && $embedId === null) { // some Converters puts images on a different path
5✔
UNCOV
533
                $name = $xmlReader->getAttribute('name', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr');
×
UNCOV
534
                $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip');
×
535
            }
536
            $target = $this->getMediaTarget($docPart, $embedId);
5✔
537
            if ($this->hasImageLoading() && null !== $target) {
5✔
538
                $imageSource = "zip://{$this->docFile}#{$target}";
3✔
539
                $parent->addImage($imageSource, null, false, $name);
5✔
540
            }
541
        } elseif ($node->nodeName == 'w:object') {
31✔
542
            // Object
543
            $rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject');
2✔
544
            // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata');
545
            $target = $this->getMediaTarget($docPart, $rId);
2✔
546
            if (null !== $target) {
2✔
547
                $textContent = "&lt;Object: {$target}>";
2✔
548
                $parent->addText($textContent, $fontStyle, $paragraphStyle);
2✔
549
            }
550
        } elseif ($node->nodeName == 'w:br') {
31✔
551
            $parent->addTextBreak();
3✔
552
        } elseif ($node->nodeName == 'w:tab') {
31✔
553
            $parent->addText("\t");
1✔
554
        } elseif ($node->nodeName == 'mc:AlternateContent') {
31✔
555
            if ($node->hasChildNodes()) {
2✔
556
                // Get fallback instead of mc:Choice to make sure it is compatible
557
                $fallbackElements = $node->getElementsByTagName('Fallback');
2✔
558

559
                if ($fallbackElements->length) {
2✔
560
                    $fallback = $fallbackElements->item(0);
2✔
561
                    // TextRun
562
                    $textContent = htmlspecialchars($fallback->nodeValue, ENT_QUOTES, 'UTF-8');
2✔
563

564
                    $parent->addText($textContent, $fontStyle, $paragraphStyle);
2✔
565
                }
566
            }
567
        } elseif ($node->nodeName == 'w:t' || $node->nodeName == 'w:delText') {
30✔
568
            // TextRun
569
            $textContent = htmlspecialchars($xmlReader->getValue('.', $node), ENT_QUOTES, 'UTF-8');
29✔
570

571
            if ($runParent->nodeName == 'w:hyperlink') {
29✔
572
                $rId = $xmlReader->getAttribute('r:id', $runParent);
2✔
573
                $target = $this->getMediaTarget($docPart, $rId);
2✔
574
                if (null !== $target) {
2✔
575
                    $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle);
2✔
576
                } else {
577
                    $parent->addText($textContent, $fontStyle, $paragraphStyle);
2✔
578
                }
579
            } else {
580
                /** @var AbstractElement $element */
581
                $element = $parent->addText($textContent, $fontStyle, $paragraphStyle);
29✔
582
                if (in_array($runParent->nodeName, ['w:ins', 'w:del'])) {
29✔
583
                    $type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED;
3✔
584
                    $author = $runParent->getAttribute('w:author');
3✔
585
                    $date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date'));
3✔
586
                    $date = $date instanceof DateTime ? $date : null;
3✔
587
                    $element->setChangeInfo($type, $author, $date);
29✔
588
                }
589
            }
590
        } elseif ($node->nodeName == 'w:softHyphen') {
21✔
591
            $element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle);
×
592
        } elseif ($node->nodeName == 'w:ruby') {
21✔
593
            $rubyPropertiesNode = $xmlReader->getElement('w:rubyPr', $node);
2✔
594
            $properties = $this->readRubyProperties($xmlReader, $rubyPropertiesNode);
2✔
595
            // read base text node
596
            $baseText = new TextRun($paragraphStyle);
2✔
597
            $baseTextNode = $xmlReader->getElement('w:rubyBase/w:r', $node);
2✔
598
            $this->readRun($xmlReader, $baseTextNode, $baseText, $docPart, $paragraphStyle);
2✔
599
            // read the actual ruby text (e.g. furigana in Japanese)
600
            $rubyText = new TextRun($paragraphStyle);
2✔
601
            $rubyTextNode = $xmlReader->getElement('w:rt/w:r', $node);
2✔
602
            $this->readRun($xmlReader, $rubyTextNode, $rubyText, $docPart, $paragraphStyle);
2✔
603
            // add element to parent
604
            $parent->addRuby($baseText, $rubyText, $properties);
2✔
605
        }
606
    }
607

608
    /**
609
     * Read w:rubyPr element.
610
     *
611
     * @param XMLReader $xmlReader reader for XML
612
     * @param DOMElement $domNode w:RubyPr element
613
     *
614
     * @return RubyProperties ruby properties from element
615
     */
616
    protected function readRubyProperties(XMLReader $xmlReader, DOMElement $domNode): RubyProperties
617
    {
618
        $rubyAlignment = $xmlReader->getElement('w:rubyAlign', $domNode)->getAttribute('w:val');
2✔
619
        $rubyHps = $xmlReader->getElement('w:hps', $domNode)->getAttribute('w:val'); // font face
2✔
620
        $rubyHpsRaise = $xmlReader->getElement('w:hpsRaise', $domNode)->getAttribute('w:val'); // pts above base text
2✔
621
        $rubyHpsBaseText = $xmlReader->getElement('w:hpsBaseText', $domNode)->getAttribute('w:val'); // base text size
2✔
622
        $rubyLid = $xmlReader->getElement('w:lid', $domNode)->getAttribute('w:val'); // type of ruby
2✔
623
        $properties = new RubyProperties();
2✔
624
        $properties->setAlignment($rubyAlignment);
2✔
625
        $properties->setFontFaceSize((float) $rubyHps);
2✔
626
        $properties->setFontPointsAboveBaseText((float) $rubyHpsRaise);
2✔
627
        $properties->setFontSizeForBaseText((float) $rubyHpsBaseText);
2✔
628
        $properties->setLanguageId($rubyLid);
2✔
629

630
        return $properties;
2✔
631
    }
632

633
    /**
634
     * Read w:tbl.
635
     *
636
     * @param mixed $parent
637
     * @param string $docPart
638
     */
639
    protected function readTable(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void
640
    {
641
        // Table style
642
        $tblStyle = null;
12✔
643
        if ($xmlReader->elementExists('w:tblPr', $domNode)) {
12✔
644
            $tblStyle = $this->readTableStyle($xmlReader, $domNode);
8✔
645
        }
646

647
        /** @var \PhpOffice\PhpWord\Element\Table $table Type hint */
648
        $table = $parent->addTable($tblStyle);
12✔
649
        $tblNodes = $xmlReader->getElements('*', $domNode);
12✔
650
        foreach ($tblNodes as $tblNode) {
12✔
651
            if ('w:tblGrid' == $tblNode->nodeName) { // Column
12✔
652
                // @todo Do something with table columns
653
            } elseif ('w:tr' == $tblNode->nodeName) { // Row
12✔
654
                $rowHeight = $xmlReader->getAttribute('w:val', $tblNode, 'w:trPr/w:trHeight');
7✔
655
                $rowHRule = $xmlReader->getAttribute('w:hRule', $tblNode, 'w:trPr/w:trHeight');
7✔
656
                $rowHRule = $rowHRule == 'exact';
7✔
657
                $rowStyle = [
7✔
658
                    'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode),
7✔
659
                    'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode),
7✔
660
                    'exactHeight' => $rowHRule,
7✔
661
                ];
7✔
662

663
                $row = $table->addRow($rowHeight, $rowStyle);
7✔
664
                $rowNodes = $xmlReader->getElements('*', $tblNode);
7✔
665
                foreach ($rowNodes as $rowNode) {
7✔
666
                    if ('w:trPr' == $rowNode->nodeName) { // Row style
7✔
667
                        // @todo Do something with row style
668
                    } elseif ('w:tc' == $rowNode->nodeName) { // Cell
7✔
669
                        $cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW');
7✔
670
                        $cellStyle = null;
7✔
671
                        if ($xmlReader->elementExists('w:tcPr', $rowNode)) {
7✔
672
                            $cellStyle = $this->readCellStyle($xmlReader, $rowNode);
6✔
673
                        }
674

675
                        $cell = $row->addCell($cellWidth, $cellStyle);
7✔
676
                        $cellNodes = $xmlReader->getElements('*', $rowNode);
7✔
677
                        foreach ($cellNodes as $cellNode) {
7✔
678
                            if ('w:p' == $cellNode->nodeName) { // Paragraph
7✔
679
                                $this->readParagraph($xmlReader, $cellNode, $cell, $docPart);
4✔
680
                            } elseif ($cellNode->nodeName == 'w:tbl') { // Table
7✔
681
                                $this->readTable($xmlReader, $cellNode, $cell, $docPart);
1✔
682
                            }
683
                        }
684
                    }
685
                }
686
            }
687
        }
688
    }
689

690
    /**
691
     * Read w:pPr.
692
     *
693
     * @return null|array
694
     */
695
    protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode)
696
    {
697
        if (!$xmlReader->elementExists('w:pPr', $domNode)) {
27✔
698
            return null;
14✔
699
        }
700

701
        $styleNode = $xmlReader->getElement('w:pPr', $domNode);
22✔
702
        $styleDefs = [
22✔
703
            'styleName' => [self::READ_VALUE, ['w:pStyle', 'w:name']],
22✔
704
            'alignment' => [self::READ_VALUE, 'w:jc'],
22✔
705
            'basedOn' => [self::READ_VALUE, 'w:basedOn'],
22✔
706
            'next' => [self::READ_VALUE, 'w:next'],
22✔
707
            'indentLeft' => [self::READ_VALUE, 'w:ind', 'w:left'],
22✔
708
            'indentRight' => [self::READ_VALUE, 'w:ind', 'w:right'],
22✔
709
            'indentHanging' => [self::READ_VALUE, 'w:ind', 'w:hanging'],
22✔
710
            'indentFirstLine' => [self::READ_VALUE, 'w:ind', 'w:firstLine'],
22✔
711
            'indentFirstLineChars' => [self::READ_VALUE, 'w:ind', 'w:firstLineChars'],
22✔
712
            'spaceAfter' => [self::READ_VALUE, 'w:spacing', 'w:after'],
22✔
713
            'spaceBefore' => [self::READ_VALUE, 'w:spacing', 'w:before'],
22✔
714
            'widowControl' => [self::READ_FALSE, 'w:widowControl'],
22✔
715
            'keepNext' => [self::READ_TRUE,  'w:keepNext'],
22✔
716
            'keepLines' => [self::READ_TRUE,  'w:keepLines'],
22✔
717
            'pageBreakBefore' => [self::READ_TRUE,  'w:pageBreakBefore'],
22✔
718
            'contextualSpacing' => [self::READ_TRUE,  'w:contextualSpacing'],
22✔
719
            'bidi' => [self::READ_TRUE,  'w:bidi'],
22✔
720
            'suppressAutoHyphens' => [self::READ_TRUE,  'w:suppressAutoHyphens'],
22✔
721
            'borderTopStyle' => [self::READ_VALUE, 'w:pBdr/w:top'],
22✔
722
            'borderTopColor' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:color'],
22✔
723
            'borderTopSize' => [self::READ_VALUE, 'w:pBdr/w:top', 'w:sz'],
22✔
724
            'borderRightStyle' => [self::READ_VALUE, 'w:pBdr/w:right'],
22✔
725
            'borderRightColor' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:color'],
22✔
726
            'borderRightSize' => [self::READ_VALUE, 'w:pBdr/w:right', 'w:sz'],
22✔
727
            'borderBottomStyle' => [self::READ_VALUE, 'w:pBdr/w:bottom'],
22✔
728
            'borderBottomColor' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:color'],
22✔
729
            'borderBottomSize' => [self::READ_VALUE, 'w:pBdr/w:bottom', 'w:sz'],
22✔
730
            'borderLeftStyle' => [self::READ_VALUE, 'w:pBdr/w:left'],
22✔
731
            'borderLeftColor' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:color'],
22✔
732
            'borderLeftSize' => [self::READ_VALUE, 'w:pBdr/w:left', 'w:sz'],
22✔
733
        ];
22✔
734

735
        return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
22✔
736
    }
737

738
    /**
739
     * Read w:rPr.
740
     *
741
     * @return null|array
742
     */
743
    protected function readFontStyle(XMLReader $xmlReader, DOMElement $domNode)
744
    {
745
        if (null === $domNode) {
39✔
746
            return null;
×
747
        }
748
        // Hyperlink has an extra w:r child
749
        if ('w:hyperlink' == $domNode->nodeName) {
39✔
750
            $domNode = $xmlReader->getElement('w:r', $domNode);
×
751
        }
752
        if (!$xmlReader->elementExists('w:rPr', $domNode)) {
39✔
753
            return null;
34✔
754
        }
755

756
        $styleNode = $xmlReader->getElement('w:rPr', $domNode);
28✔
757
        $styleDefs = [
28✔
758
            'styleName' => [self::READ_VALUE, 'w:rStyle'],
28✔
759
            'name' => [self::READ_VALUE, 'w:rFonts', ['w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs']],
28✔
760
            'hint' => [self::READ_VALUE, 'w:rFonts', 'w:hint'],
28✔
761
            'size' => [self::READ_SIZE,  ['w:sz', 'w:szCs']],
28✔
762
            'color' => [self::READ_VALUE, 'w:color'],
28✔
763
            'underline' => [self::READ_VALUE, 'w:u'],
28✔
764
            'bold' => [self::READ_TRUE,  'w:b'],
28✔
765
            'italic' => [self::READ_TRUE,  'w:i'],
28✔
766
            'strikethrough' => [self::READ_TRUE,  'w:strike'],
28✔
767
            'doubleStrikethrough' => [self::READ_TRUE,  'w:dstrike'],
28✔
768
            'smallCaps' => [self::READ_TRUE,  'w:smallCaps'],
28✔
769
            'allCaps' => [self::READ_TRUE,  'w:caps'],
28✔
770
            'superScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'],
28✔
771
            'subScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'],
28✔
772
            'fgColor' => [self::READ_VALUE, 'w:highlight'],
28✔
773
            'rtl' => [self::READ_TRUE,  'w:rtl'],
28✔
774
            'lang' => [self::READ_VALUE, 'w:lang'],
28✔
775
            'position' => [self::READ_VALUE, 'w:position'],
28✔
776
            'hidden' => [self::READ_TRUE,  'w:vanish'],
28✔
777
        ];
28✔
778

779
        return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
28✔
780
    }
781

782
    /**
783
     * Read w:tblPr.
784
     *
785
     * @return null|array|string
786
     *
787
     * @todo Capture w:tblStylePr w:type="firstRow"
788
     */
789
    protected function readTableStyle(XMLReader $xmlReader, DOMElement $domNode)
790
    {
791
        $style = null;
13✔
792
        $margins = ['top', 'left', 'bottom', 'right'];
13✔
793
        $borders = array_merge($margins, ['insideH', 'insideV']);
13✔
794

795
        if ($xmlReader->elementExists('w:tblPr', $domNode)) {
13✔
796
            $tblStyleName = '';
13✔
797
            if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) {
13✔
798
                $tblStyleName = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle');
1✔
799
            }
800
            $styleNode = $xmlReader->getElement('w:tblPr', $domNode);
13✔
801
            $styleDefs = [];
13✔
802

803
            foreach ($margins as $side) {
13✔
804
                $ucfSide = ucfirst($side);
13✔
805
                $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'];
13✔
806
            }
807
            foreach ($borders as $side) {
13✔
808
                $ucfSide = ucfirst($side);
13✔
809
                $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'];
13✔
810
                $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'];
13✔
811
                $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'];
13✔
812
            }
813
            $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type'];
13✔
814
            $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual'];
13✔
815
            $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w'];
13✔
816
            $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
13✔
817

818
            $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode);
13✔
819
            if ($tablePositionNode !== null) {
13✔
820
                $style['position'] = $this->readTablePosition($xmlReader, $tablePositionNode);
1✔
821
            }
822

823
            $indentNode = $xmlReader->getElement('w:tblInd', $styleNode);
13✔
824
            if ($indentNode !== null) {
13✔
825
                $style['indent'] = $this->readTableIndent($xmlReader, $indentNode);
8✔
826
            }
827
            if ($xmlReader->elementExists('w:basedOn', $domNode)) {
13✔
828
                $style['basedOn'] = $xmlReader->getAttribute('w:val', $domNode, 'w:basedOn');
3✔
829
            }
830
            if ($tblStyleName !== '') {
13✔
831
                $style['tblStyle'] = $tblStyleName;
1✔
832
            }
833
            // this may be unneeded
834
            if ($xmlReader->elementExists('w:name', $domNode)) {
13✔
835
                $style['styleName'] = $xmlReader->getAttribute('w:val', $domNode, 'w:name');
8✔
836
            }
837
        }
838

839
        return $style;
13✔
840
    }
841

842
    /**
843
     * Read w:tblpPr.
844
     *
845
     * @return array
846
     */
847
    private function readTablePosition(XMLReader $xmlReader, DOMElement $domNode)
848
    {
849
        $styleDefs = [
1✔
850
            'leftFromText' => [self::READ_VALUE, '.', 'w:leftFromText'],
1✔
851
            'rightFromText' => [self::READ_VALUE, '.', 'w:rightFromText'],
1✔
852
            'topFromText' => [self::READ_VALUE, '.', 'w:topFromText'],
1✔
853
            'bottomFromText' => [self::READ_VALUE, '.', 'w:bottomFromText'],
1✔
854
            'vertAnchor' => [self::READ_VALUE, '.', 'w:vertAnchor'],
1✔
855
            'horzAnchor' => [self::READ_VALUE, '.', 'w:horzAnchor'],
1✔
856
            'tblpXSpec' => [self::READ_VALUE, '.', 'w:tblpXSpec'],
1✔
857
            'tblpX' => [self::READ_VALUE, '.', 'w:tblpX'],
1✔
858
            'tblpYSpec' => [self::READ_VALUE, '.', 'w:tblpYSpec'],
1✔
859
            'tblpY' => [self::READ_VALUE, '.', 'w:tblpY'],
1✔
860
        ];
1✔
861

862
        return $this->readStyleDefs($xmlReader, $domNode, $styleDefs);
1✔
863
    }
864

865
    /**
866
     * Read w:tblInd.
867
     *
868
     * @return TblWidthComplexType
869
     */
870
    private function readTableIndent(XMLReader $xmlReader, DOMElement $domNode)
871
    {
872
        $styleDefs = [
8✔
873
            'value' => [self::READ_VALUE, '.', 'w:w'],
8✔
874
            'type' => [self::READ_VALUE, '.', 'w:type'],
8✔
875
        ];
8✔
876
        $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs);
8✔
877

878
        return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']);
8✔
879
    }
880

881
    /**
882
     * Read w:tcPr.
883
     *
884
     * @return null|array
885
     */
886
    private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode)
887
    {
888
        $styleDefs = [
6✔
889
            'valign' => [self::READ_VALUE, 'w:vAlign'],
6✔
890
            'textDirection' => [self::READ_VALUE, 'w:textDirection'],
6✔
891
            'gridSpan' => [self::READ_VALUE, 'w:gridSpan'],
6✔
892
            'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'],
6✔
893
            'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'],
6✔
894
            'noWrap' => [self::READ_VALUE, 'w:noWrap', null, null, true],
6✔
895
        ];
6✔
896
        $style = null;
6✔
897

898
        if ($xmlReader->elementExists('w:tcPr', $domNode)) {
6✔
899
            $styleNode = $xmlReader->getElement('w:tcPr', $domNode);
6✔
900

901
            $borders = ['top', 'left', 'bottom', 'right'];
6✔
902
            foreach ($borders as $side) {
6✔
903
                $ucfSide = ucfirst($side);
6✔
904

905
                $styleDefs['border' . $ucfSide . 'Size'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:sz'];
6✔
906
                $styleDefs['border' . $ucfSide . 'Color'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:color'];
6✔
907
                $styleDefs['border' . $ucfSide . 'Style'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:val'];
6✔
908
            }
909

910
            $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
6✔
911
        }
912

913
        return $style;
6✔
914
    }
915

916
    /**
917
     * Returns the first child element found.
918
     *
919
     * @param null|array|string $elements
920
     *
921
     * @return null|string
922
     */
923
    private function findPossibleElement(XMLReader $xmlReader, ?DOMElement $parentNode = null, $elements = null)
924
    {
925
        if (is_array($elements)) {
42✔
926
            //if element is an array, we take the first element that exists in the XML
927
            foreach ($elements as $possibleElement) {
33✔
928
                if ($xmlReader->elementExists($possibleElement, $parentNode)) {
33✔
929
                    return $possibleElement;
21✔
930
                }
931
            }
932
        } else {
933
            return $elements;
42✔
934
        }
935

936
        return null;
33✔
937
    }
938

939
    /**
940
     * Returns the first attribute found.
941
     *
942
     * @param array|string $attributes
943
     *
944
     * @return null|string
945
     */
946
    private function findPossibleAttribute(XMLReader $xmlReader, DOMElement $node, $attributes)
947
    {
948
        //if attribute is an array, we take the first attribute that exists in the XML
949
        if (is_array($attributes)) {
41✔
950
            foreach ($attributes as $possibleAttribute) {
20✔
951
                if ($xmlReader->getAttribute($possibleAttribute, $node)) {
20✔
952
                    return $possibleAttribute;
16✔
953
                }
954
            }
955

956
            return null;
8✔
957
        }
958

959
        return $attributes;
41✔
960
    }
961

962
    /**
963
     * Read style definition.
964
     *
965
     * @param array $styleDefs
966
     *
967
     * @ignoreScrutinizerPatch
968
     *
969
     * @return array
970
     */
971
    protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = null, $styleDefs = [])
972
    {
973
        $styles = [];
42✔
974

975
        foreach ($styleDefs as $styleProp => $styleVal) {
42✔
976
            [$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null);
42✔
977

978
            $element = $this->findPossibleElement($xmlReader, $parentNode, $element);
42✔
979
            if ($element === null) {
42✔
980
                continue;
33✔
981
            }
982

983
            if ($xmlReader->elementExists($element, $parentNode)) {
42✔
984
                $node = $xmlReader->getElement($element, $parentNode);
41✔
985

986
                $attribute = $this->findPossibleAttribute($xmlReader, $node, $attribute);
41✔
987

988
                // Use w:val as default if no attribute assigned
989
                $attribute = ($attribute === null) ? 'w:val' : $attribute;
41✔
990
                $attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default;
41✔
991

992
                $styleValue = $this->readStyleDef($method, $attributeValue, $expected);
41✔
993
                if ($styleValue !== null) {
41✔
994
                    $styles[$styleProp] = $styleValue;
41✔
995
                }
996
            }
997
        }
998

999
        return $styles;
42✔
1000
    }
1001

1002
    /**
1003
     * Return style definition based on conversion method.
1004
     *
1005
     * @param string $method
1006
     *
1007
     * @ignoreScrutinizerPatch
1008
     *
1009
     * @param null|string $attributeValue
1010
     * @param mixed $expected
1011
     *
1012
     * @return mixed
1013
     */
1014
    private function readStyleDef($method, $attributeValue, $expected)
1015
    {
1016
        $style = $attributeValue;
41✔
1017

1018
        if (self::READ_SIZE == $method) {
41✔
1019
            $style = $attributeValue / 2;
21✔
1020
        } elseif (self::READ_TRUE == $method) {
41✔
1021
            $style = $this->isOn($attributeValue);
16✔
1022
        } elseif (self::READ_FALSE == $method) {
38✔
1023
            $style = !$this->isOn($attributeValue);
5✔
1024
        } elseif (self::READ_EQUAL == $method) {
38✔
1025
            $style = $attributeValue == $expected;
8✔
1026
        }
1027

1028
        return $style;
41✔
1029
    }
1030

1031
    /**
1032
     * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present.
1033
     *
1034
     * @see http://www.datypic.com/sc/ooxml/t-w_ST_OnOff.html
1035
     *
1036
     * @param string $value
1037
     *
1038
     * @return bool
1039
     */
1040
    private function isOn($value = null)
1041
    {
1042
        return $value === null || $value === '1' || $value === 'true' || $value === 'on';
16✔
1043
    }
1044

1045
    /**
1046
     * Returns the target of image, object, or link as stored in ::readMainRels.
1047
     *
1048
     * @param string $docPart
1049
     * @param string $rId
1050
     *
1051
     * @return null|string
1052
     */
1053
    private function getMediaTarget($docPart, $rId)
1054
    {
1055
        $target = null;
6✔
1056

1057
        if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) {
6✔
1058
            $target = $this->rels[$docPart][$rId]['target'];
5✔
1059
        }
1060

1061
        return $target;
6✔
1062
    }
1063

1064
    /**
1065
     * Returns the target mode.
1066
     *
1067
     * @param string $docPart
1068
     * @param string $rId
1069
     *
1070
     * @return null|string
1071
     */
1072
    private function getTargetMode($docPart, $rId)
1073
    {
1074
        $mode = null;
1✔
1075

1076
        if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) {
1✔
1077
            $mode = $this->rels[$docPart][$rId]['targetMode'];
1✔
1078
        }
1079

1080
        return $mode;
1✔
1081
    }
1082
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc