PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.5
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.5
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Spout / Reader / XLSX / Helper / SharedStringsHelper.php

SharedStringsHelper.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.5, at app/Services/Spout/Reader/XLSX/Helper/SharedStringsHelper.php

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