PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.9
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.9
4.0.9 4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 All 150 releases
visualizer / vendor / openspout / openspout / src / Reader / XLSX / Manager / SharedStringsManager.php

SharedStringsManager.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 4.0.9, at vendor/openspout/openspout/src/Reader/XLSX/Manager/SharedStringsManager.php

253 lines 9.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace OpenSpout\Reader\XLSX\Manager;
4
5 use OpenSpout\Common\Exception\IOException;
6 use OpenSpout\Reader\Exception\XMLProcessingException;
7 use OpenSpout\Reader\Wrapper\XMLReader;
8 use OpenSpout\Reader\XLSX\Creator\HelperFactory;
9 use OpenSpout\Reader\XLSX\Creator\InternalEntityFactory;
10 use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactory;
11 use OpenSpout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyInterface;
12
13 /**
14 * This class manages the shared strings defined in the associated XML file.
15 */
16 class SharedStringsManager
17 {
18 /** Definition of XML nodes names used to parse data */
19 public const XML_NODE_SST = 'sst';
20 public const XML_NODE_SI = 'si';
21 public const XML_NODE_R = 'r';
22 public const XML_NODE_T = 't';
23
24 /** Definition of XML attributes used to parse data */
25 public const XML_ATTRIBUTE_COUNT = 'count';
26 public const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
27 public const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
28 public const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
29
30 /** @var string Path of the XLSX file being read */
31 protected $filePath;
32
33 /** @var string Temporary folder where the temporary files to store shared strings will be stored */
34 protected $tempFolder;
35
36 /** @var WorkbookRelationshipsManager Helps retrieving workbook relationships */
37 protected $workbookRelationshipsManager;
38
39 /** @var InternalEntityFactory Factory to create entities */
40 protected $entityFactory;
41
42 /** @var HelperFactory Factory to create helpers */
43 protected $helperFactory;
44
45 /** @var CachingStrategyFactory Factory to create shared strings caching strategies */
46 protected $cachingStrategyFactory;
47
48 /** @var CachingStrategyInterface The best caching strategy for storing shared strings */
49 protected $cachingStrategy;
50
51 /**
52 * @param string $filePath Path of the XLSX file being read
53 * @param string $tempFolder Temporary folder where the temporary files to store shared strings will be stored
54 * @param WorkbookRelationshipsManager $workbookRelationshipsManager Helps retrieving workbook relationships
55 * @param InternalEntityFactory $entityFactory Factory to create entities
56 * @param HelperFactory $helperFactory Factory to create helpers
57 * @param CachingStrategyFactory $cachingStrategyFactory Factory to create shared strings caching strategies
58 */
59 public function __construct(
60 $filePath,
61 $tempFolder,
62 $workbookRelationshipsManager,
63 $entityFactory,
64 $helperFactory,
65 $cachingStrategyFactory
66 ) {
67 $this->filePath = $filePath;
68 $this->tempFolder = $tempFolder;
69 $this->workbookRelationshipsManager = $workbookRelationshipsManager;
70 $this->entityFactory = $entityFactory;
71 $this->helperFactory = $helperFactory;
72 $this->cachingStrategyFactory = $cachingStrategyFactory;
73 }
74
75 /**
76 * Returns whether the XLSX file contains a shared strings XML file.
77 *
78 * @return bool
79 */
80 public function hasSharedStrings()
81 {
82 return $this->workbookRelationshipsManager->hasSharedStringsXMLFile();
83 }
84
85 /**
86 * Builds an in-memory array containing all the shared strings of the sheet.
87 * All the strings are stored in a XML file, located at 'xl/sharedStrings.xml'.
88 * It is then accessed by the sheet data, via the string index in the built table.
89 *
90 * More documentation available here: http://msdn.microsoft.com/en-us/library/office/gg278314.aspx
91 *
92 * The XML file can be really big with sheets containing a lot of data. That is why
93 * we need to use a XML reader that provides streaming like the XMLReader library.
94 *
95 * @throws \OpenSpout\Common\Exception\IOException If shared strings XML file can't be read
96 */
97 public function extractSharedStrings()
98 {
99 $sharedStringsXMLFilePath = $this->workbookRelationshipsManager->getSharedStringsXMLFilePath();
100 $xmlReader = $this->entityFactory->createXMLReader();
101 $sharedStringIndex = 0;
102
103 if (false === $xmlReader->openFileInZip($this->filePath, $sharedStringsXMLFilePath)) {
104 throw new IOException('Could not open "'.$sharedStringsXMLFilePath.'".');
105 }
106
107 try {
108 $sharedStringsUniqueCount = $this->getSharedStringsUniqueCount($xmlReader);
109 $this->cachingStrategy = $this->getBestSharedStringsCachingStrategy($sharedStringsUniqueCount);
110
111 $xmlReader->readUntilNodeFound(self::XML_NODE_SI);
112
113 while (self::XML_NODE_SI === $xmlReader->getCurrentNodeName()) {
114 $this->processSharedStringsItem($xmlReader, $sharedStringIndex);
115 ++$sharedStringIndex;
116
117 // jump to the next '<si>' tag
118 $xmlReader->next(self::XML_NODE_SI);
119 }
120
121 $this->cachingStrategy->closeCache();
122 } catch (XMLProcessingException $exception) {
123 throw new IOException("The sharedStrings.xml file is invalid and cannot be read. [{$exception->getMessage()}]");
124 }
125
126 $xmlReader->close();
127 }
128
129 /**
130 * Returns the shared string at the given index, using the previously chosen caching strategy.
131 *
132 * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
133 *
134 * @throws \OpenSpout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
135 *
136 * @return string The shared string at the given index
137 */
138 public function getStringAtIndex($sharedStringIndex)
139 {
140 return $this->cachingStrategy->getStringAtIndex($sharedStringIndex);
141 }
142
143 /**
144 * Destroys the cache, freeing memory and removing any created artifacts.
145 */
146 public function cleanup()
147 {
148 if (null !== $this->cachingStrategy) {
149 $this->cachingStrategy->clearCache();
150 }
151 }
152
153 /**
154 * Returns the shared strings unique count, as specified in <sst> tag.
155 *
156 * @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XMLReader instance
157 *
158 * @throws \OpenSpout\Common\Exception\IOException If sharedStrings.xml is invalid and can't be read
159 *
160 * @return null|int Number of unique shared strings in the sharedStrings.xml file
161 */
162 protected function getSharedStringsUniqueCount($xmlReader)
163 {
164 $xmlReader->next(self::XML_NODE_SST);
165
166 // Iterate over the "sst" elements to get the actual "sst ELEMENT" (skips any DOCTYPE)
167 while (self::XML_NODE_SST === $xmlReader->getCurrentNodeName() && XMLReader::ELEMENT !== $xmlReader->nodeType) {
168 $xmlReader->read();
169 }
170
171 $uniqueCount = $xmlReader->getAttribute(self::XML_ATTRIBUTE_UNIQUE_COUNT);
172
173 // some software do not add the "uniqueCount" attribute but only use the "count" one
174 // @see https://github.com/box/spout/issues/254
175 if (null === $uniqueCount) {
176 $uniqueCount = $xmlReader->getAttribute(self::XML_ATTRIBUTE_COUNT);
177 }
178
179 return (null !== $uniqueCount) ? (int) $uniqueCount : null;
180 }
181
182 /**
183 * Returns the best shared strings caching strategy.
184 *
185 * @param null|int $sharedStringsUniqueCount Number of unique shared strings (NULL if unknown)
186 *
187 * @return CachingStrategyInterface
188 */
189 protected function getBestSharedStringsCachingStrategy($sharedStringsUniqueCount)
190 {
191 return $this->cachingStrategyFactory
192 ->createBestCachingStrategy($sharedStringsUniqueCount, $this->tempFolder, $this->helperFactory)
193 ;
194 }
195
196 /**
197 * Processes the shared strings item XML node which the given XML reader is positioned on.
198 *
199 * @param \OpenSpout\Reader\Wrapper\XMLReader $xmlReader XML Reader positioned on a "<si>" node
200 * @param int $sharedStringIndex Index of the processed shared strings item
201 */
202 protected function processSharedStringsItem($xmlReader, $sharedStringIndex)
203 {
204 $sharedStringValue = '';
205
206 // NOTE: expand() will automatically decode all XML entities of the child nodes
207 /** @var \DOMElement $siNode */
208 $siNode = $xmlReader->expand();
209 $textNodes = $siNode->getElementsByTagName(self::XML_NODE_T);
210
211 foreach ($textNodes as $textNode) {
212 if ($this->shouldExtractTextNodeValue($textNode)) {
213 $textNodeValue = $textNode->nodeValue;
214 $shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
215
216 $sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue);
217 }
218 }
219
220 $this->cachingStrategy->addStringForIndex($sharedStringValue, $sharedStringIndex);
221 }
222
223 /**
224 * Not all text nodes' values must be extracted.
225 * Some text nodes are part of a node describing the pronunciation for instance.
226 * We'll only consider the nodes whose parents are "<si>" or "<r>".
227 *
228 * @param \DOMElement $textNode Text node to check
229 *
230 * @return bool Whether the given text node's value must be extracted
231 */
232 protected function shouldExtractTextNodeValue($textNode)
233 {
234 $parentTagName = $textNode->parentNode->localName;
235
236 return self::XML_NODE_SI === $parentTagName || self::XML_NODE_R === $parentTagName;
237 }
238
239 /**
240 * If the text node has the attribute 'xml:space="preserve"', then preserve whitespace.
241 *
242 * @param \DOMElement $textNode The text node element (<t>) whose whitespace may be preserved
243 *
244 * @return bool Whether whitespace should be preserved
245 */
246 protected function shouldPreserveWhitespace($textNode)
247 {
248 $spaceValue = $textNode->getAttribute(self::XML_ATTRIBUTE_XML_SPACE);
249
250 return self::XML_ATTRIBUTE_VALUE_PRESERVE === $spaceValue;
251 }
252 }
253