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

PHPOffice / PHPWord / 25414441731

06 May 2026 03:12AM UTC coverage: 94.696% (-2.1%) from 96.757%
25414441731

Pull #2874

github

web-flow
Merge 3fa85d6e3 into 0ab0b4940
Pull Request #2874: Addresses issue #12

135 of 420 new or added lines in 30 files covered. (32.14%)

1 existing line in 1 file now uncovered.

12588 of 13293 relevant lines covered (94.7%)

34.84 hits per line

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

71.68
/src/PhpWord/Reader/ODText/Content.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\ODText;
20

21
use DateTime;
22
use DOMElement;
23
use DOMNodeList;
24
use PhpOffice\Math\Reader\MathML;
25
use PhpOffice\PhpWord\Element\Section;
26
use PhpOffice\PhpWord\Element\TrackChange;
27
use PhpOffice\PhpWord\PhpWord;
28
use PhpOffice\PhpWord\Shared\XMLReader;
29

30
/**
31
 * Content reader.
32
 *
33
 * @since 0.10.0
34
 */
35
class Content extends AbstractPart
36
{
37
    /** @var ?Section */
38
    private $section;
39

40
    /**
41
     * Read content.xml.
42
     */
43
    public function read(PhpWord $phpWord): void
44
    {
45
        $xmlReader = new XMLReader();
5✔
46
        $xmlReader->getDomFromZip($this->docFile, $this->xmlFile);
5✔
47

48
        $nodes = $xmlReader->getElements('office:body/office:text/*');
5✔
49
        $this->section = null;
5✔
50
        $this->processNodes($nodes, $xmlReader, $phpWord);
5✔
51
        $this->section = null;
5✔
52
    }
53

54
    /** @param DOMNodeList<DOMElement> $nodes */
55
    public function processNodes(DOMNodeList $nodes, XMLReader $xmlReader, PhpWord $phpWord): void
56
    {
57
        if ($nodes->length > 0) {
5✔
58
            foreach ($nodes as $node) {
5✔
59
                // $styleName = $xmlReader->getAttribute('text:style-name', $node);
60
                switch ($node->nodeName) {
5✔
61
                    case 'text:h': // Heading
5✔
62
                        $depth = $xmlReader->getAttribute('text:outline-level', $node);
1✔
63
                        $this->getSection($phpWord)->addTitle($node->nodeValue, $depth);
1✔
64

65
                        break;
1✔
66
                    case 'text:p': // Paragraph
5✔
67
                        $styleName = $xmlReader->getAttribute('text:style-name', $node);                                                
5✔
68
                        if (substr($styleName, 0, 2) === 'SB') {
5✔
69
                            break;                                                                                                      
2✔
70
                        }           
71

72
                        // Track page number via soft page break hints (LibreOffice equivalent of lastRenderedPageBreak)                
73
                        foreach ($node->childNodes as $pageBreakChild) {
5✔
74
                            if ($pageBreakChild->nodeName === 'text:soft-page-break') {                                                 
5✔
NEW
75
                                ++$this->currentPage;
×
NEW
76
                                break;                                                                                                  
×
77
                            }       
78
                        }
79

80
                        // Caption: paragraph contains a text:sequence element (ODT auto-numbered figure/table labels)                  
81
                        $sequenceNode = $xmlReader->getElement('text:sequence', $node);
5✔
82
                        if ($sequenceNode !== null) {
5✔
NEW
83
                            $label = $sequenceNode->getAttribute('text:name') ?: 'Figure';
×
NEW
84
                            $text = trim($node->nodeValue);
×
NEW
85
                            if ($text !== '') {
×
NEW
86
                                $this->getSection($phpWord)->addCaption($label, $text, null, null, $this->currentPage);
×
87
                            }
UNCOV
88
                            break;
×
89
                        }
90
                                    
91
                        $element = $xmlReader->getElement('draw:frame/draw:object', $node);  
5✔
92
                        if ($element) {
5✔
93
                            $mathFile = str_replace('./', '', $element->getAttribute('xlink:href')) . '/content.xml';
1✔
94

95
                            $xmlReaderObject = new XMLReader();
1✔
96
                            $mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile);
1✔
97
                            if ($mathElement) {
1✔
98
                                $mathXML = $mathElement->saveXML($mathElement);
1✔
99

100
                                if (is_string($mathXML)) {
1✔
101
                                    $reader = new MathML();
1✔
102
                                    $math = $reader->read($mathXML);
1✔
103

104
                                    $this->getSection($phpWord)->addFormula($math);
1✔
105
                                }
106
                            }
107
                        } else {
108
                            $children = $node->childNodes;
4✔
109
                            $spans = false;
4✔
110
                            /** @var DOMElement $child */
111
                            foreach ($children as $child) {
4✔
112
                                switch ($child->nodeName) {
4✔
113
                                    case 'text:change-start':
4✔
114
                                        $changeId = $child->getAttribute('text:change-id');
×
115
                                        if (isset($trackedChanges[$changeId])) {
×
116
                                            $changed = $trackedChanges[$changeId];
×
117
                                        }
118

119
                                        break;
×
120
                                    case 'text:change-end':
4✔
121
                                        unset($changed);
×
122

123
                                        break;
×
124
                                    case 'text:change':
4✔
125
                                        $changeId = $child->getAttribute('text:change-id');
×
126
                                        if (isset($trackedChanges[$changeId])) {
×
127
                                            $changed = $trackedChanges[$changeId];
×
128
                                        }
129

130
                                        break;
×
131
                                    case 'text:span':
4✔
132
                                        $spans = true;
3✔
133

134
                                        break;
3✔
135
                                }
136
                            }
137

138
                            if ($spans) {
4✔
139
                                $element = $this->getSection($phpWord)->addTextRun();
3✔
140
                                foreach ($children as $child) {
3✔
141
                                    switch ($child->nodeName) {
3✔
142
                                        case 'text:span':
3✔
143
                                            /** @var DOMElement $child2 */
144
                                            foreach ($child->childNodes as $child2) {
3✔
145
                                                switch ($child2->nodeName) {
3✔
146
                                                    case '#text':
3✔
147
                                                        $element->addText($child2->nodeValue);
3✔
148

149
                                                        break;
3✔
150
                                                    case 'text:tab':
1✔
151
                                                        $element->addText("\t");
1✔
152

153
                                                        break;
1✔
154
                                                    case 'text:s':
1✔
155
                                                        $spaces = (int) $child2->getAttribute('text:c') ?: 1;
1✔
156
                                                        $element->addText(str_repeat(' ', $spaces));
1✔
157

158
                                                        break;
1✔
159
                                                }
160
                                            }
161

162
                                            break;
3✔
163
                                    }
164
                                }
165
                            } else {
166
                                $element = $this->getSection($phpWord)->addText($node->nodeValue);
2✔
167
                            }
168
                            if (isset($changed) && is_array($changed)) {
4✔
169
                                $element->setTrackChange($changed['changed']);
×
170
                                if (isset($changed['textNodes'])) {
×
171
                                    foreach ($changed['textNodes'] as $changedNode) {
×
172
                                        $element = $this->getSection($phpWord)->addText($changedNode->nodeValue);
×
173
                                        $element->setTrackChange($changed['changed']);
×
174
                                    }
175
                                }
176
                            }
177
                        }
178

179
                        break;
5✔
180
                    case 'text:list': // List
4✔
181
                        $listItems = $xmlReader->getElements('text:list-item/text:p', $node);
1✔
182
                        foreach ($listItems as $listItem) {
1✔
183
                            // $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
184
                            $this->getSection($phpWord)->addListItem($listItem->nodeValue, 0);
1✔
185
                        }
186

187
                        break;
1✔
188
                    case 'text:tracked-changes':
4✔
189
                        $changedRegions = $xmlReader->getElements('text:changed-region', $node);
2✔
190
                        foreach ($changedRegions as $changedRegion) {
2✔
191
                            $type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED;
×
192
                            $creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild);
×
193
                            $author = $creatorNode[0]->nodeValue;
×
194
                            $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild);
×
195
                            $date = $dateNode[0]->nodeValue;
×
196
                            $date = preg_replace('/\.\d+$/', '', $date);
×
197
                            $date = DateTime::createFromFormat('Y-m-d\TH:i:s', $date);
×
198
                            $changed = new TrackChange($type, $author, $date);
×
199
                            $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion);
×
200
                            $trackedChanges[$changedRegion->getAttribute('text:id')] = ['changed' => $changed, 'textNodes' => $textNodes];
×
201
                        }
202

203
                        break;
2✔
204
                    case 'text:section': // Section
4✔
205
                        // $sectionStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
206
                        $this->section = $phpWord->addSection();
2✔
207
                        /** @var DOMNodeList<DOMElement> $children */
208
                        $children = $node->childNodes;
2✔
209
                        $this->processNodes($children, $xmlReader, $phpWord);
2✔
210

211
                        break;
2✔
212
                }
213
            }
214
        }
215
    }
216

217
    private function getSection(PhpWord $phpWord): Section
218
    {
219
        $section = $this->section;
5✔
220
        if ($section === null) {
5✔
221
            $section = $this->section = $phpWord->addSection();
3✔
222
        }
223

224
        return $section;
5✔
225
    }
226
}
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