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