PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.5.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.5.4
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / vendor / openspout / openspout / src / Reader / XLSX / Manager / SharedStringsManager.php

SharedStringsManager.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.5.4, at vendor/openspout/openspout/src/Reader/XLSX/Manager/SharedStringsManager.php

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