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

PHPOffice / PHPWord / 13025401639

29 Jan 2025 05:48AM UTC coverage: 96.825% (-0.4%) from 97.217%
13025401639

Pull #2562

github

web-flow
Merge ebe3e5dac into 2d2759585
Pull Request #2562: TemplateProcessor SetComplexBlock/Value and Sections

6 of 7 new or added lines in 1 file covered. (85.71%)

245 existing lines in 40 files now uncovered.

12227 of 12628 relevant lines covered (96.82%)

34.56 hits per line

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

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

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

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

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

150
        return $this;
9✔
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✔
UNCOV
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✔
UNCOV
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;
31✔
199

200
        if ($xmlReader->elementExists('w:r/w:fldChar/w:ffData', $domNode)) {
31✔
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)) {
28✔
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✔
UNCOV
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');
31✔
270
        if ($xmlReader->elementExists('m:oMath', $domNode)) {
31✔
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)) {
30✔
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;
29✔
300
        if ($headingDepth !== null) {
29✔
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);
26✔
319
        if (0 === $textRunContainers) {
26✔
320
            $parent->addTextBreak(1, $paragraphStyle);
7✔
321
        } else {
322
            $nodes = $xmlReader->getElements('*', $domNode);
25✔
323
            $paragraph = $parent->addTextRun($paragraphStyle);
25✔
324
            foreach ($nodes as $node) {
25✔
325
                $this->readRun($xmlReader, $node, $paragraph, $docPart, $paragraphStyle);
25✔
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✔
UNCOV
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✔
UNCOV
361
                                $formField->setDefault($xmlReader->getAttribute('w:val', $ddListNode));
×
362

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

479
        if ($xmlReader->elementExists('.//*["commentReference"=local-name()]', $domNode)) {
29✔
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;
29✔
501
        if ($node->nodeName == 'w:footnoteReference') {
29✔
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') {
29✔
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') {
29✔
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') {
29✔
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') {
28✔
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') {
28✔
551
            $parent->addTextBreak();
3✔
552
        } elseif ($node->nodeName == 'w:tab') {
28✔
553
            $parent->addText("\t");
1✔
554
        } elseif ($node->nodeName == 'mc:AlternateContent') {
28✔
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') {
27✔
568
            // TextRun
569
            $textContent = htmlspecialchars($xmlReader->getValue('.', $node), ENT_QUOTES, 'UTF-8');
26✔
570

571
            if ($runParent->nodeName == 'w:hyperlink') {
26✔
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);
26✔
582
                if (in_array($runParent->nodeName, ['w:ins', 'w:del'])) {
26✔
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);
26✔
588
                }
589
            }
590
        } elseif ($node->nodeName == 'w:softHyphen') {
19✔
UNCOV
591
            $element = $parent->addText("\u{200c}", $fontStyle, $paragraphStyle);
×
592
        } elseif ($node->nodeName == 'w:ruby') {
19✔
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)) {
19✔
698
            return null;
7✔
699
        }
700

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

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

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

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

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

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

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

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

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

827
        return $style;
12✔
828
    }
829

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

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

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

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

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

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

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

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

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

901
        return $style;
5✔
902
    }
903

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

924
        return null;
25✔
925
    }
926

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

944
            return null;
8✔
945
        }
946

947
        return $attributes;
33✔
948
    }
949

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

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

966
            $element = $this->findPossibleElement($xmlReader, $parentNode, $element);
34✔
967
            if ($element === null) {
34✔
968
                continue;
25✔
969
            }
970

971
            if ($xmlReader->elementExists($element, $parentNode)) {
34✔
972
                $node = $xmlReader->getElement($element, $parentNode);
33✔
973

974
                $attribute = $this->findPossibleAttribute($xmlReader, $node, $attribute);
33✔
975

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

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

987
        return $styles;
34✔
988
    }
989

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

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

1016
        return $style;
33✔
1017
    }
1018

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

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

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

1049
        return $target;
6✔
1050
    }
1051

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

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

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