Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.59% covered (success)
97.59%
81 / 83
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Word2007
97.59% covered (success)
97.59%
81 / 83
50.00% covered (danger)
50.00%
2 / 4
16
0.00% covered (danger)
0.00%
0 / 1
 load
97.67% covered (success)
97.67%
42 / 43
0.00% covered (danger)
0.00%
0 / 1
5
 readPart
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 readRelationships
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 getRels
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * This file is part of PHPWord - A pure PHP library for reading and writing
4 * word processing documents.
5 *
6 * PHPWord is free software distributed under the terms of the GNU Lesser
7 * General Public License version 3 as published by the Free Software Foundation.
8 *
9 * For the full copyright and license information, please read the LICENSE
10 * file that was distributed with this source code. For the full list of
11 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
12 *
13 * @see         https://github.com/PHPOffice/PHPWord
14 *
15 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
16 */
17
18namespace PhpOffice\PhpWord\Reader;
19
20use Exception;
21use PhpOffice\PhpWord\Element\AbstractElement;
22use PhpOffice\PhpWord\PhpWord;
23use PhpOffice\PhpWord\Reader\Word2007\AbstractPart;
24use PhpOffice\PhpWord\Shared\XMLReader;
25use PhpOffice\PhpWord\Shared\ZipArchive;
26
27/**
28 * Reader for Word2007.
29 *
30 * @since 0.8.0
31 *
32 * @todo watermark, checkbox, toc
33 * @todo Partly done: image, object
34 */
35class Word2007 extends AbstractReader implements ReaderInterface
36{
37    /**
38     * Loads PhpWord from file.
39     *
40     * @param string $docFile
41     *
42     * @return \PhpOffice\PhpWord\PhpWord
43     */
44    public function load($docFile)
45    {
46        $phpWord = new PhpWord();
47        $relationships = $this->readRelationships($docFile);
48        $commentRefs = [];
49
50        $steps = [
51            [
52                'stepPart' => 'document',
53                'stepItems' => [
54                    'styles' => 'Styles',
55                    'numbering' => 'Numbering',
56                ],
57            ],
58            [
59                'stepPart' => 'main',
60                'stepItems' => [
61                    'officeDocument' => 'Document',
62                    'core-properties' => 'DocPropsCore',
63                    'extended-properties' => 'DocPropsApp',
64                    'custom-properties' => 'DocPropsCustom',
65                ],
66            ],
67            [
68                'stepPart' => 'document',
69                'stepItems' => [
70                    'endnotes' => 'Endnotes',
71                    'footnotes' => 'Footnotes',
72                    'settings' => 'Settings',
73                    'comments' => 'Comments',
74                ],
75            ],
76        ];
77
78        foreach ($steps as $step) {
79            $stepPart = $step['stepPart'];
80            $stepItems = $step['stepItems'];
81            if (!isset($relationships[$stepPart])) {
82                continue;
83            }
84            foreach ($relationships[$stepPart] as $relItem) {
85                $relType = $relItem['type'];
86                if (isset($stepItems[$relType])) {
87                    $partName = $stepItems[$relType];
88                    $xmlFile = $relItem['target'];
89                    $part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile);
90                    $commentRefs = $part->getCommentReferences();
91                }
92            }
93        }
94
95        return $phpWord;
96    }
97
98    /**
99     * Read document part.
100     *
101     * @param array<string, array<string, null|AbstractElement>> $commentRefs
102     */
103    private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart
104    {
105        $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}";
106        if (!class_exists($partClass)) {
107            throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass));
108        }
109
110        /** @var AbstractPart $part Type hint */
111        $part = new $partClass($docFile, $xmlFile);
112        $part->setImageLoading($this->hasImageLoading());
113        $part->setRels($relationships);
114        $part->setCommentReferences($commentRefs);
115        $part->read($phpWord);
116
117        return $part;
118    }
119
120    /**
121     * Read all relationship files.
122     *
123     * @param string $docFile
124     *
125     * @return array
126     */
127    private function readRelationships($docFile)
128    {
129        $relationships = [];
130
131        // _rels/.rels
132        $relationships['main'] = $this->getRels($docFile, '_rels/.rels');
133
134        // word/_rels/*.xml.rels
135        $wordRelsPath = 'word/_rels/';
136        $zip = new ZipArchive();
137        if ($zip->open($docFile) === true) {
138            for ($i = 0; $i < $zip->numFiles; ++$i) {
139                $xmlFile = $zip->getNameIndex($i);
140                if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') {
141                    $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile));
142                    $relationships[$docPart] = $this->getRels($docFile, $xmlFile, 'word/');
143                }
144            }
145            $zip->close();
146        }
147
148        return $relationships;
149    }
150
151    /**
152     * Get relationship array.
153     *
154     * @param string $docFile
155     * @param string $xmlFile
156     * @param string $targetPrefix
157     *
158     * @return array
159     */
160    private function getRels($docFile, $xmlFile, $targetPrefix = '')
161    {
162        $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/';
163        $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
164
165        $rels = [];
166
167        $xmlReader = new XMLReader();
168        $xmlReader->getDomFromZip($docFile, $xmlFile);
169        $nodes = $xmlReader->getElements('*');
170        foreach ($nodes as $node) {
171            $rId = $xmlReader->getAttribute('Id', $node);
172            $type = $xmlReader->getAttribute('Type', $node);
173            $target = $xmlReader->getAttribute('Target', $node);
174            $mode = $xmlReader->getAttribute('TargetMode', $node);
175
176            // Remove URL prefixes from $type to make it easier to read
177            $type = str_replace($metaPrefix, '', $type);
178            $type = str_replace($officePrefix, '', $type);
179            $docPart = str_replace('.xml', '', $target);
180
181            // Do not add prefix to link source
182            if ($type != 'hyperlink' && $mode != 'External') {
183                $target = $targetPrefix . $target;
184            }
185
186            // Push to return array
187            $rels[$rId] = ['type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode];
188        }
189        ksort($rels);
190
191        return $rels;
192    }
193}