Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
98.61% |
71 / 72 |
|
88.89% |
8 / 9 |
CRAP | |
0.00% |
0 / 1 |
| Contents | |
98.61% |
71 / 72 |
|
88.89% |
8 / 9 |
28 | |
0.00% |
0 / 1 |
| write | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
| writeChunks | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
| writeFontNames | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
| writeContainer | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
| writeTable | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| writeParagraph | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| writeInline | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
3.04 | |||
| getElementText | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
7 | |||
| sanitizeText | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| 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\Writer\WPS\Part; |
| 20 | |
| 21 | use PhpOffice\PhpWord\Element\AbstractContainer; |
| 22 | use PhpOffice\PhpWord\Element\AbstractElement; |
| 23 | use PhpOffice\PhpWord\Element\Link; |
| 24 | use PhpOffice\PhpWord\Element\ListItem; |
| 25 | use PhpOffice\PhpWord\Element\PageBreak; |
| 26 | use PhpOffice\PhpWord\Element\Table; |
| 27 | use PhpOffice\PhpWord\Element\Text; |
| 28 | use PhpOffice\PhpWord\Element\TextBreak; |
| 29 | use PhpOffice\PhpWord\Element\Title; |
| 30 | |
| 31 | /** |
| 32 | * WPS CONTENTS part writer. |
| 33 | * |
| 34 | * The stream is a list of chunks, each of them described by an entry of the |
| 35 | * index which follows the header of the stream. |
| 36 | * |
| 37 | * @since 1.5.0 |
| 38 | */ |
| 39 | class Contents extends AbstractPart |
| 40 | { |
| 41 | /** Magic of the Microsoft Works 7/8 word processor documents. */ |
| 42 | const MAGIC = "CHNKWKS\x00"; |
| 43 | |
| 44 | /** Size of the stream header, including the header of the index. */ |
| 45 | const HEADER_SIZE = 0x20; |
| 46 | |
| 47 | /** Size of an entry of the index. */ |
| 48 | const ENTRY_SIZE = 0x18; |
| 49 | |
| 50 | /** End of paragraph. */ |
| 51 | const PARAGRAPH_END = "\x0d"; |
| 52 | |
| 53 | /** Page break. */ |
| 54 | const PAGE_BREAK = "\x0c"; |
| 55 | |
| 56 | /** |
| 57 | * Write the CONTENTS stream. |
| 58 | */ |
| 59 | public function write(): string |
| 60 | { |
| 61 | $phpWord = $this->getParentWriter()->getPhpWord(); |
| 62 | |
| 63 | $text = ''; |
| 64 | foreach ($phpWord->getSections() as $section) { |
| 65 | $text .= $this->writeContainer($section); |
| 66 | } |
| 67 | |
| 68 | return $this->writeChunks([ |
| 69 | 'TEXT' => $this->encodeText($text), |
| 70 | 'FONT' => $this->writeFontNames([$phpWord->getDefaultFontName()]), |
| 71 | ]); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Write the header, the index and the data of the chunks. |
| 76 | * |
| 77 | * @param array<string, string> $chunks Chunk name and content pairs |
| 78 | */ |
| 79 | private function writeChunks(array $chunks): string |
| 80 | { |
| 81 | $offset = self::HEADER_SIZE + count($chunks) * self::ENTRY_SIZE; |
| 82 | |
| 83 | $index = ''; |
| 84 | $data = ''; |
| 85 | $id = 0; |
| 86 | foreach ($chunks as $name => $content) { |
| 87 | $index .= pack('v', self::ENTRY_SIZE); |
| 88 | $index .= $name; // name of the chunk |
| 89 | $index .= pack('vvv', $id++, 0, 0); |
| 90 | $index .= $name; // type of the chunk |
| 91 | $index .= pack('VV', $offset + strlen($data), strlen($content)); |
| 92 | $data .= $content; |
| 93 | } |
| 94 | |
| 95 | $header = self::MAGIC; |
| 96 | $header .= pack('vvv', 0, 0, count($chunks)); // unknown, unknown, number of chunks |
| 97 | $header .= str_repeat("\x00", 10); // unknown |
| 98 | $header .= pack('vv', 0, count($chunks)); // unknown, number of chunks of this index |
| 99 | $header .= pack('V', 0xFFFFFFFF); // offset of the next index, none |
| 100 | |
| 101 | return $header . $index . $data; |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Write the table of the font names. |
| 106 | * |
| 107 | * @param string[] $fonts |
| 108 | */ |
| 109 | private function writeFontNames(array $fonts): string |
| 110 | { |
| 111 | $names = ''; |
| 112 | foreach ($fonts as $font) { |
| 113 | $names .= pack('v', mb_strlen($font)); |
| 114 | $names .= $this->encodeText($font); |
| 115 | $names .= str_repeat("\x00", 4); // unknown |
| 116 | } |
| 117 | |
| 118 | $offsets = str_repeat(pack('V', 0), count($fonts)); |
| 119 | |
| 120 | return pack('V', strlen($offsets) + strlen($names)) // size of the chunk, without this header |
| 121 | . pack('V', count($fonts)) |
| 122 | . str_repeat("\x00", 12) // unknown |
| 123 | . $offsets |
| 124 | . $names; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Write the paragraphs of a container. |
| 129 | */ |
| 130 | private function writeContainer(AbstractContainer $container): string |
| 131 | { |
| 132 | $text = ''; |
| 133 | foreach ($container->getElements() as $element) { |
| 134 | if ($element instanceof PageBreak) { |
| 135 | $text .= self::PAGE_BREAK; |
| 136 | } elseif ($element instanceof TextBreak) { |
| 137 | $text .= self::PARAGRAPH_END; |
| 138 | } elseif ($element instanceof Table) { |
| 139 | $text .= $this->writeTable($element); |
| 140 | } elseif ($element instanceof AbstractContainer) { |
| 141 | $text .= $this->writeParagraph($this->writeInline($element)); |
| 142 | } else { |
| 143 | $text .= $this->writeParagraph($this->getElementText($element)); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | return $text; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Write the paragraphs of the cells of a table. |
| 152 | */ |
| 153 | private function writeTable(Table $table): string |
| 154 | { |
| 155 | $text = ''; |
| 156 | foreach ($table->getRows() as $row) { |
| 157 | foreach ($row->getCells() as $cell) { |
| 158 | $text .= $this->writeContainer($cell); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | return $text; |
| 163 | } |
| 164 | |
| 165 | /** |
| 166 | * Write a paragraph, ie its text followed by an end of paragraph. |
| 167 | */ |
| 168 | private function writeParagraph(string $text): string |
| 169 | { |
| 170 | if ($text === '') { |
| 171 | return ''; |
| 172 | } |
| 173 | |
| 174 | return $text . self::PARAGRAPH_END; |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Get the text of the elements of a container, without paragraph separator. |
| 179 | */ |
| 180 | private function writeInline(AbstractContainer $container): string |
| 181 | { |
| 182 | $text = ''; |
| 183 | foreach ($container->getElements() as $element) { |
| 184 | if ($element instanceof AbstractContainer) { |
| 185 | $text .= $this->writeInline($element); |
| 186 | } else { |
| 187 | $text .= $this->getElementText($element); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | return $text; |
| 192 | } |
| 193 | |
| 194 | /** |
| 195 | * Get the text of an element, empty for the unsupported ones. |
| 196 | */ |
| 197 | private function getElementText(AbstractElement $element): string |
| 198 | { |
| 199 | if ($element instanceof Text || $element instanceof ListItem) { |
| 200 | $text = $element->getText(); |
| 201 | } elseif ($element instanceof Link) { |
| 202 | $text = $element->getText() !== '' ? $element->getText() : $element->getSource(); |
| 203 | } elseif ($element instanceof Title) { |
| 204 | $title = $element->getText(); |
| 205 | $text = $title instanceof AbstractContainer ? $this->writeInline($title) : $title; |
| 206 | } else { |
| 207 | $text = ''; |
| 208 | } |
| 209 | |
| 210 | return $this->sanitizeText((string) $text); |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Replace the characters which delimit the paragraphs by spaces. |
| 215 | */ |
| 216 | private function sanitizeText(string $text): string |
| 217 | { |
| 218 | return str_replace(["\r\n", "\r", "\n", self::PAGE_BREAK], ' ', $text); |
| 219 | } |
| 220 | } |