Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.73% covered (warning)
75.73%
78 / 103
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
Content
75.73% covered (warning)
75.73%
78 / 103
66.67% covered (warning)
66.67%
2 / 3
56.58
0.00% covered (danger)
0.00%
0 / 1
 read
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 processNodes
72.83% covered (warning)
72.83%
67 / 92
0.00% covered (danger)
0.00%
0 / 1
57.20
 getSection
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
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
19namespace PhpOffice\PhpWord\Reader\ODText;
20
21use DateTime;
22use DOMElement;
23use DOMNodeList;
24use PhpOffice\Math\Reader\MathML;
25use PhpOffice\PhpWord\Element\Section;
26use PhpOffice\PhpWord\Element\TrackChange;
27use PhpOffice\PhpWord\PhpWord;
28use PhpOffice\PhpWord\Shared\XMLReader;
29
30/**
31 * Content reader.
32 *
33 * @since 0.10.0
34 */
35class 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();
46        $xmlReader->getDomFromZip($this->docFile, $this->xmlFile);
47
48        $trackedChanges = [];
49
50        $nodes = $xmlReader->getElements('office:body/office:text/*');
51        $this->section = null;
52        $this->processNodes($nodes, $xmlReader, $phpWord);
53        $this->section = null;
54    }
55
56    /** @param DOMNodeList<DOMElement> $nodes */
57    public function processNodes(DOMNodeList $nodes, XMLReader $xmlReader, PhpWord $phpWord): void
58    {
59        if ($nodes->length > 0) {
60            foreach ($nodes as $node) {
61                // $styleName = $xmlReader->getAttribute('text:style-name', $node);
62                switch ($node->nodeName) {
63                    case 'text:h': // Heading
64                        $depth = $xmlReader->getAttribute('text:outline-level', $node);
65                        $this->getSection($phpWord)->addTitle($node->nodeValue, $depth);
66
67                        break;
68                    case 'text:p': // Paragraph
69                        $styleName = $xmlReader->getAttribute('text:style-name', $node);
70                        if (substr($styleName, 0, 2) === 'SB') {
71                            break;
72                        }
73                        $element = $xmlReader->getElement('draw:frame/draw:object', $node);
74                        if ($element) {
75                            $mathFile = str_replace('./', '', $element->getAttribute('xlink:href')) . '/content.xml';
76
77                            $xmlReaderObject = new XMLReader();
78                            $mathElement = $xmlReaderObject->getDomFromZip($this->docFile, $mathFile);
79                            if ($mathElement) {
80                                $mathXML = $mathElement->saveXML($mathElement);
81
82                                if (is_string($mathXML)) {
83                                    $reader = new MathML();
84                                    $math = $reader->read($mathXML);
85
86                                    $this->getSection($phpWord)->addFormula($math);
87                                }
88                            }
89                        } else {
90                            $children = $node->childNodes;
91                            $spans = false;
92                            /** @var DOMElement $child */
93                            foreach ($children as $child) {
94                                switch ($child->nodeName) {
95                                    case 'text:change-start':
96                                        $changeId = $child->getAttribute('text:change-id');
97                                        if (isset($trackedChanges[$changeId])) {
98                                            $changed = $trackedChanges[$changeId];
99                                        }
100
101                                        break;
102                                    case 'text:change-end':
103                                        unset($changed);
104
105                                        break;
106                                    case 'text:change':
107                                        $changeId = $child->getAttribute('text:change-id');
108                                        if (isset($trackedChanges[$changeId])) {
109                                            $changed = $trackedChanges[$changeId];
110                                        }
111
112                                        break;
113                                    case 'text:span':
114                                        $spans = true;
115
116                                        break;
117                                }
118                            }
119
120                            if ($spans) {
121                                $element = $this->getSection($phpWord)->addTextRun();
122                                foreach ($children as $child) {
123                                    switch ($child->nodeName) {
124                                        case 'text:span':
125                                            /** @var DOMElement $child2 */
126                                            foreach ($child->childNodes as $child2) {
127                                                switch ($child2->nodeName) {
128                                                    case '#text':
129                                                        $element->addText($child2->nodeValue);
130
131                                                        break;
132                                                    case 'text:tab':
133                                                        $element->addText("\t");
134
135                                                        break;
136                                                    case 'text:s':
137                                                        $spaces = (int) $child2->getAttribute('text:c') ?: 1;
138                                                        $element->addText(str_repeat(' ', $spaces));
139
140                                                        break;
141                                                }
142                                            }
143
144                                            break;
145                                    }
146                                }
147                            } else {
148                                $element = $this->getSection($phpWord)->addText($node->nodeValue);
149                            }
150                            if (isset($changed) && is_array($changed)) {
151                                $element->setTrackChange($changed['changed']);
152                                if (isset($changed['textNodes'])) {
153                                    foreach ($changed['textNodes'] as $changedNode) {
154                                        $element = $this->getSection($phpWord)->addText($changedNode->nodeValue);
155                                        $element->setTrackChange($changed['changed']);
156                                    }
157                                }
158                            }
159                        }
160
161                        break;
162                    case 'text:list': // List
163                        $listItems = $xmlReader->getElements('text:list-item/text:p', $node);
164                        foreach ($listItems as $listItem) {
165                            // $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
166                            $this->getSection($phpWord)->addListItem($listItem->nodeValue, 0);
167                        }
168
169                        break;
170                    case 'text:tracked-changes':
171                        $changedRegions = $xmlReader->getElements('text:changed-region', $node);
172                        foreach ($changedRegions as $changedRegion) {
173                            $type = ($changedRegion->firstChild->nodeName == 'text:insertion') ? TrackChange::INSERTED : TrackChange::DELETED;
174                            $creatorNode = $xmlReader->getElements('office:change-info/dc:creator', $changedRegion->firstChild);
175                            $author = $creatorNode[0]->nodeValue;
176                            $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild);
177                            $date = $dateNode[0]->nodeValue;
178                            $date = preg_replace('/\.\d+$/', '', $date);
179                            $date = DateTime::createFromFormat('Y-m-d\TH:i:s', $date);
180                            $changed = new TrackChange($type, $author, $date);
181                            $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion);
182                            $trackedChanges[$changedRegion->getAttribute('text:id')] = ['changed' => $changed, 'textNodes' => $textNodes];
183                        }
184
185                        break;
186                    case 'text:section': // Section
187                        // $sectionStyleName = $xmlReader->getAttribute('text:style-name', $listItem);
188                        $this->section = $phpWord->addSection();
189                        $children = $node->childNodes;
190                        $this->processNodes($children, $xmlReader, $phpWord);
191
192                        break;
193                }
194            }
195        }
196    }
197
198    private function getSection(PhpWord $phpWord): Section
199    {
200        $section = $this->section;
201        if ($section === null) {
202            $section = $this->section = $phpWord->addSection();
203        }
204
205        return $section;
206    }
207}