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/**
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;
20
21use Exception;
22use PhpOffice\PhpWord\Element\AbstractElement;
23use PhpOffice\PhpWord\PhpWord;
24use PhpOffice\PhpWord\Reader\Word2007\AbstractPart;
25use PhpOffice\PhpWord\Shared\XMLReader;
26use PhpOffice\PhpWord\Shared\ZipArchive;
27
28/**
29 * Reader for Word2007.
30 *
31 * @since 0.8.0
32 *
33 * @todo watermark, checkbox, toc
34 * @todo Partly done: image, object
35 */
36class Word2007 extends AbstractReader implements ReaderInterface
37{
38    /**
39     * Loads PhpWord from file.
40     *
41     * @param string $docFile
42     *
43     * @return PhpWord
44     */
45    public function load($docFile)
46    {
47        $phpWord = new PhpWord();
48        $relationships = $this->readRelationships($docFile);
49        $commentRefs = [];
50
51        $steps = [
52            [
53                'stepPart' => 'document',
54                'stepItems' => [
55                    'styles' => 'Styles',
56                    'numbering' => 'Numbering',
57                ],
58            ],
59            [
60                'stepPart' => 'main',
61                'stepItems' => [
62                    'officeDocument' => 'Document',
63                    'core-properties' => 'DocPropsCore',
64                    'extended-properties' => 'DocPropsApp',
65                    'custom-properties' => 'DocPropsCustom',
66                ],
67            ],
68            [
69                'stepPart' => 'document',
70                'stepItems' => [
71                    'endnotes' => 'Endnotes',
72                    'footnotes' => 'Footnotes',
73                    'settings' => 'Settings',
74                    'comments' => 'Comments',
75                ],
76            ],
77        ];
78
79        foreach ($steps as $step) {
80            $stepPart = $step['stepPart'];
81            $stepItems = $step['stepItems'];
82            if (!isset($relationships[$stepPart])) {
83                continue;
84            }
85            foreach ($relationships[$stepPart] as $relItem) {
86                $relType = $relItem['type'];
87                if (isset($stepItems[$relType])) {
88                    $partName = $stepItems[$relType];
89                    $xmlFile = $relItem['target'];
90                    $part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile);
91                    $commentRefs = $part->getCommentReferences();
92                }
93            }
94        }
95
96        return $phpWord;
97    }
98
99    /**
100     * Read document part.
101     *
102     * @param array<string, array<string, null|AbstractElement>> $commentRefs
103     */
104    private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart
105    {
106        $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}";
107        if (!class_exists($partClass)) {
108            throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass));
109        }
110
111        /** @var AbstractPart $part Type hint */
112        $part = new $partClass($docFile, $xmlFile);
113        $part->setImageLoading($this->hasImageLoading());
114        $part->setRels($relationships);
115        $part->setCommentReferences($commentRefs);
116        $part->read($phpWord);
117
118        return $part;
119    }
120
121    /**
122     * Read all relationship files.
123     *
124     * @param string $docFile
125     *
126     * @return array
127     */
128    private function readRelationships($docFile)
129    {
130        $relationships = [];
131
132        // _rels/.rels
133        $relationships['main'] = $this->getRels($docFile, '_rels/.rels');
134
135        // word/_rels/*.xml.rels
136        $wordRelsPath = 'word/_rels/';
137        $zip = new ZipArchive();
138        if ($zip->open($docFile) === true) {
139            for ($i = 0; $i < $zip->numFiles; ++$i) {
140                $xmlFile = $zip->getNameIndex($i);
141                if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') {
142                    $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile));
143                    $relationships[$docPart] = $this->getRels($docFile, $xmlFile, 'word/');
144                }
145            }
146            $zip->close();
147        }
148
149        return $relationships;
150    }
151
152    /**
153     * Get relationship array.
154     *
155     * @param string $docFile
156     * @param string $xmlFile
157     * @param string $targetPrefix
158     *
159     * @return array
160     */
161    private function getRels($docFile, $xmlFile, $targetPrefix = '')
162    {
163        $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/';
164        $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';
165
166        $rels = [];
167
168        $xmlReader = new XMLReader();
169        $xmlReader->getDomFromZip($docFile, $xmlFile);
170        $nodes = $xmlReader->getElements('*');
171        foreach ($nodes as $node) {
172            $rId = $xmlReader->getAttribute('Id', $node);
173            $type = $xmlReader->getAttribute('Type', $node);
174            $target = $xmlReader->getAttribute('Target', $node);
175            $mode = $xmlReader->getAttribute('TargetMode', $node);
176
177            // Remove URL prefixes from $type to make it easier to read
178            $type = str_replace($metaPrefix, '', $type);
179            $type = str_replace($officePrefix, '', $type);
180            $docPart = str_replace('.xml', '', $target);
181
182            // Do not add prefix to link source
183            if ($type != 'hyperlink' && $mode != 'External') {
184                $target = $targetPrefix . $target;
185            }
186
187            // Push to return array
188            $rels[$rId] = ['type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode];
189        }
190        ksort($rels);
191
192        return $rels;
193    }
194}