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

PHPOffice / PHPWord / 16541995925

26 Jul 2025 05:02PM UTC coverage: 96.757%. Remained the same
16541995925

Pull #2805

github

web-flow
Merge a52e12d77 into 0ab0b4940
Pull Request #2805: Add support for parsing w:sym tags in Word2007 reader

4 of 4 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

12501 of 12920 relevant lines covered (96.76%)

35.44 hits per line

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

97.34
/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;
53✔
106
        $this->xmlFile = $xmlFile;
53✔
107
    }
108

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

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

123
        return $this;
15✔
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;
15✔
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;
15✔
149

150
        return $this;
15✔
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;
33✔
199

200
        if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) {
33✔
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)) {
30✔
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');
33✔
270
        if ($xmlReader->elementExists('m:oMath', $domNode)) {
33✔
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)) {
32✔
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;
31✔
300
        if ($headingDepth !== null) {
31✔
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);
28✔
319
        if (0 === $textRunContainers) {
28✔
320
            $parent->addTextBreak(1, $paragraphStyle);
7✔
321
        } else {
322
            $nodes = $xmlReader->getElements('*', $domNode);
27✔
323
            $paragraph = $parent->addTextRun($paragraphStyle);
27✔
324
            foreach ($nodes as $node) {
27✔
325
                $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
27✔
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'])) {
18✔
441
            if ('Title' === $paragraphStyle['styleName']) {
9✔
442
                return 0;
4✔
443
            }
444

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

452
        return null;
15✔
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'])) {
31✔
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') {
31✔
472
            $fontStyle = $this->readFontStyle($xmlReader, $domNode);
31✔
473
            $nodes = $xmlReader->getElements('*', $domNode);
31✔
474
            foreach ($nodes as $node) {
31✔
475
                $this->readRunChild($xmlReader, $node, $parent, $docPart, $paragraphStyle, $fontStyle);
31✔
476
            }
477
        }
478

479
        if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
31✔
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;
31✔
501
        if ($node->nodeName == 'w:footnoteReference') {
31✔
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') {
31✔
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') {
31✔
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✔
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') {
31✔
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✔
533
                $name = $xmlReader->getAttribute('name', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:nvPicPr/pic:cNvPr');
×
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') {
30✔
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') {
30✔
551
            $parent->addTextBreak();
3✔
552
        } elseif ($node->nodeName == 'w:tab') {
30✔
553
            $parent->addText("\t");
1✔
554
        } elseif ($node->nodeName == 'mc:AlternateContent') {
30✔
555
            if ($node->hasChildNodes()) {
1✔
556
                // Get fallback instead of mc:Choice to make sure it is compatible
557
                $fallbackElements = $node->getElementsByTagName('Fallback');
1✔
558

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

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

571
            if ($runParent->nodeName == 'w:hyperlink') {
28✔
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);
28✔
582
                if (in_array($runParent->nodeName, ['w:ins', 'w:del'])) {
28✔
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);
28✔
588
                }
589
            }
590
        } elseif ($node->nodeName == 'w:softHyphen') {
20✔
UNCOV
591
            $element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle);
×
592
        } elseif ($node->nodeName == 'w:ruby') {
20✔
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;
11✔
643
        if ($xmlReader->elementExists('w:tblPr', $domNode)) {
11✔
644
            $tblStyle = $this->readTableStyle($xmlReader, $domNode);
7✔
645
        }
646

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

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

675
                        $cell = $row->addCell($cellWidth, $cellStyle);
6✔
676
                        $cellNodes = $xmlReader->getElements('*', $rowNode);
6✔
677
                        foreach ($cellNodes as $cellNode) {
6✔
678
                            if ('w:p' == $cellNode->nodeName) { // Paragraph
6✔
679
                                $this->readParagraph($xmlReader, $cellNode, $cell, $docPart);
3✔
680
                            } elseif ($cellNode->nodeName == 'w:tbl') { // Table
6✔
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)) {
26✔
698
            return null;
13✔
699
        }
700

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

735
        return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
21✔
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) {
38✔
UNCOV
746
            return null;
×
747
        }
748
        // Hyperlink has an extra w:r child
749
        if ('w:hyperlink' == $domNode->nodeName) {
38✔
UNCOV
750
            $domNode = $xmlReader->getElement('w:r', $domNode);
×
751
        }
752
        if (!$xmlReader->elementExists('w:rPr', $domNode)) {
38✔
753
            return null;
33✔
754
        }
755

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

779
        return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
27✔
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;
12✔
792
        $margins = ['top', 'left', 'bottom', 'right'];
12✔
793
        $borders = array_merge($margins, ['insideH', 'insideV']);
12✔
794

795
        if ($xmlReader->elementExists('w:tblPr', $domNode)) {
12✔
796
            if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) {
12✔
UNCOV
797
                $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle');
×
798
            } else {
799
                $styleNode = $xmlReader->getElement('w:tblPr', $domNode);
12✔
800
                $styleDefs = [];
12✔
801
                foreach ($margins as $side) {
12✔
802
                    $ucfSide = ucfirst($side);
12✔
803
                    $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'];
12✔
804
                }
805
                foreach ($borders as $side) {
12✔
806
                    $ucfSide = ucfirst($side);
12✔
807
                    $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'];
12✔
808
                    $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'];
12✔
809
                    $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'];
12✔
810
                }
811
                $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type'];
12✔
812
                $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual'];
12✔
813
                $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w'];
12✔
814
                $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
12✔
815

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

821
                $indentNode = $xmlReader->getElement('w:tblInd', $styleNode);
12✔
822
                if ($indentNode !== null) {
12✔
823
                    $style['indent'] = $this->readTableIndent($xmlReader, $indentNode);
7✔
824
                }
825
            }
826
        }
827

828
        return $style;
12✔
829
    }
830

831
    /**
832
     * Read w:tblpPr.
833
     *
834
     * @return array
835
     */
836
    private function readTablePosition(XMLReader $xmlReader, DOMElement $domNode)
837
    {
838
        $styleDefs = [
1✔
839
            'leftFromText' => [self::READ_VALUE, '.', 'w:leftFromText'],
1✔
840
            'rightFromText' => [self::READ_VALUE, '.', 'w:rightFromText'],
1✔
841
            'topFromText' => [self::READ_VALUE, '.', 'w:topFromText'],
1✔
842
            'bottomFromText' => [self::READ_VALUE, '.', 'w:bottomFromText'],
1✔
843
            'vertAnchor' => [self::READ_VALUE, '.', 'w:vertAnchor'],
1✔
844
            'horzAnchor' => [self::READ_VALUE, '.', 'w:horzAnchor'],
1✔
845
            'tblpXSpec' => [self::READ_VALUE, '.', 'w:tblpXSpec'],
1✔
846
            'tblpX' => [self::READ_VALUE, '.', 'w:tblpX'],
1✔
847
            'tblpYSpec' => [self::READ_VALUE, '.', 'w:tblpYSpec'],
1✔
848
            'tblpY' => [self::READ_VALUE, '.', 'w:tblpY'],
1✔
849
        ];
1✔
850

851
        return $this->readStyleDefs($xmlReader, $domNode, $styleDefs);
1✔
852
    }
853

854
    /**
855
     * Read w:tblInd.
856
     *
857
     * @return TblWidthComplexType
858
     */
859
    private function readTableIndent(XMLReader $xmlReader, DOMElement $domNode)
860
    {
861
        $styleDefs = [
7✔
862
            'value' => [self::READ_VALUE, '.', 'w:w'],
7✔
863
            'type' => [self::READ_VALUE, '.', 'w:type'],
7✔
864
        ];
7✔
865
        $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs);
7✔
866

867
        return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']);
7✔
868
    }
869

870
    /**
871
     * Read w:tcPr.
872
     *
873
     * @return null|array
874
     */
875
    private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode)
876
    {
877
        $styleDefs = [
5✔
878
            'valign' => [self::READ_VALUE, 'w:vAlign'],
5✔
879
            'textDirection' => [self::READ_VALUE, 'w:textDirection'],
5✔
880
            'gridSpan' => [self::READ_VALUE, 'w:gridSpan'],
5✔
881
            'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'],
5✔
882
            'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'],
5✔
883
            'noWrap' => [self::READ_VALUE, 'w:noWrap', null, null, true],
5✔
884
        ];
5✔
885
        $style = null;
5✔
886

887
        if ($xmlReader->elementExists('w:tcPr', $domNode)) {
5✔
888
            $styleNode = $xmlReader->getElement('w:tcPr', $domNode);
5✔
889

890
            $borders = ['top', 'left', 'bottom', 'right'];
5✔
891
            foreach ($borders as $side) {
5✔
892
                $ucfSide = ucfirst($side);
5✔
893

894
                $styleDefs['border' . $ucfSide . 'Size'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:sz'];
5✔
895
                $styleDefs['border' . $ucfSide . 'Color'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:color'];
5✔
896
                $styleDefs['border' . $ucfSide . 'Style'] = [self::READ_VALUE, 'w:tcBorders/w:' . $side, 'w:val'];
5✔
897
            }
898

899
            $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs);
5✔
900
        }
901

902
        return $style;
5✔
903
    }
904

905
    /**
906
     * Returns the first child element found.
907
     *
908
     * @param null|array|string $elements
909
     *
910
     * @return null|string
911
     */
912
    private function findPossibleElement(XMLReader $xmlReader, ?DOMElement $parentNode = null, $elements = null)
913
    {
914
        if (is_array($elements)) {
41✔
915
            //if element is an array, we take the first element that exists in the XML
916
            foreach ($elements as $possibleElement) {
32✔
917
                if ($xmlReader->elementExists($possibleElement, $parentNode)) {
32✔
918
                    return $possibleElement;
20✔
919
                }
920
            }
921
        } else {
922
            return $elements;
41✔
923
        }
924

925
        return null;
32✔
926
    }
927

928
    /**
929
     * Returns the first attribute found.
930
     *
931
     * @param array|string $attributes
932
     *
933
     * @return null|string
934
     */
935
    private function findPossibleAttribute(XMLReader $xmlReader, DOMElement $node, $attributes)
936
    {
937
        //if attribute is an array, we take the first attribute that exists in the XML
938
        if (is_array($attributes)) {
40✔
939
            foreach ($attributes as $possibleAttribute) {
19✔
940
                if ($xmlReader->getAttribute($possibleAttribute, $node)) {
19✔
941
                    return $possibleAttribute;
15✔
942
                }
943
            }
944

945
            return null;
8✔
946
        }
947

948
        return $attributes;
40✔
949
    }
950

951
    /**
952
     * Read style definition.
953
     *
954
     * @param array $styleDefs
955
     *
956
     * @ignoreScrutinizerPatch
957
     *
958
     * @return array
959
     */
960
    protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = null, $styleDefs = [])
961
    {
962
        $styles = [];
41✔
963

964
        foreach ($styleDefs as $styleProp => $styleVal) {
41✔
965
            [$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null);
41✔
966

967
            $element = $this->findPossibleElement($xmlReader, $parentNode, $element);
41✔
968
            if ($element === null) {
41✔
969
                continue;
32✔
970
            }
971

972
            if ($xmlReader->elementExists($element, $parentNode)) {
41✔
973
                $node = $xmlReader->getElement($element, $parentNode);
40✔
974

975
                $attribute = $this->findPossibleAttribute($xmlReader, $node, $attribute);
40✔
976

977
                // Use w:val as default if no attribute assigned
978
                $attribute = ($attribute === null) ? 'w:val' : $attribute;
40✔
979
                $attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default;
40✔
980

981
                $styleValue = $this->readStyleDef($method, $attributeValue, $expected);
40✔
982
                if ($styleValue !== null) {
40✔
983
                    $styles[$styleProp] = $styleValue;
40✔
984
                }
985
            }
986
        }
987

988
        return $styles;
41✔
989
    }
990

991
    /**
992
     * Return style definition based on conversion method.
993
     *
994
     * @param string $method
995
     *
996
     * @ignoreScrutinizerPatch
997
     *
998
     * @param null|string $attributeValue
999
     * @param mixed $expected
1000
     *
1001
     * @return mixed
1002
     */
1003
    private function readStyleDef($method, $attributeValue, $expected)
1004
    {
1005
        $style = $attributeValue;
40✔
1006

1007
        if (self::READ_SIZE == $method) {
40✔
1008
            $style = $attributeValue / 2;
20✔
1009
        } elseif (self::READ_TRUE == $method) {
40✔
1010
            $style = $this->isOn($attributeValue);
15✔
1011
        } elseif (self::READ_FALSE == $method) {
37✔
1012
            $style = !$this->isOn($attributeValue);
4✔
1013
        } elseif (self::READ_EQUAL == $method) {
37✔
1014
            $style = $attributeValue == $expected;
8✔
1015
        }
1016

1017
        return $style;
40✔
1018
    }
1019

1020
    /**
1021
     * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present.
1022
     *
1023
     * @see http://www.datypic.com/sc/ooxml/t-w_ST_OnOff.html
1024
     *
1025
     * @param string $value
1026
     *
1027
     * @return bool
1028
     */
1029
    private function isOn($value = null)
1030
    {
1031
        return $value === null || $value === '1' || $value === 'true' || $value === 'on';
15✔
1032
    }
1033

1034
    /**
1035
     * Returns the target of image, object, or link as stored in ::readMainRels.
1036
     *
1037
     * @param string $docPart
1038
     * @param string $rId
1039
     *
1040
     * @return null|string
1041
     */
1042
    private function getMediaTarget($docPart, $rId)
1043
    {
1044
        $target = null;
6✔
1045

1046
        if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) {
6✔
1047
            $target = $this->rels[$docPart][$rId]['target'];
5✔
1048
        }
1049

1050
        return $target;
6✔
1051
    }
1052

1053
    /**
1054
     * Returns the target mode.
1055
     *
1056
     * @param string $docPart
1057
     * @param string $rId
1058
     *
1059
     * @return null|string
1060
     */
1061
    private function getTargetMode($docPart, $rId)
1062
    {
1063
        $mode = null;
1✔
1064

1065
        if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) {
1✔
1066
            $mode = $this->rels[$docPart][$rId]['targetMode'];
1✔
1067
        }
1068

1069
        return $mode;
1✔
1070
    }
1071
}
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