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 / Common / Helper / Escaper / XLSX.php

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

195 lines 6.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\Common\Helper\Escaper;
4
5 /**
6 * Provides functions to escape and unescape data for XLSX files.
7 */
8 class XLSX implements EscaperInterface
9 {
10 /** @var bool Whether the escaper has already been initialized */
11 private $isAlreadyInitialized = false;
12
13 /** @var string Regex pattern to detect control characters that need to be escaped */
14 private $escapableControlCharactersPattern;
15
16 /** @var string[] Map containing control characters to be escaped (key) and their escaped value (value) */
17 private $controlCharactersEscapingMap;
18
19 /** @var string[] Map containing control characters to be escaped (value) and their escaped value (key) */
20 private $controlCharactersEscapingReverseMap;
21
22 /**
23 * Escapes the given string to make it compatible with XLSX.
24 *
25 * @param string $string The string to escape
26 *
27 * @return string The escaped string
28 */
29 public function escape($string)
30 {
31 $this->initIfNeeded();
32
33 $escapedString = $this->escapeControlCharacters($string);
34 // @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
35 // single/double quotes (for XML attributes) need to be encoded.
36 return htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
37 }
38
39 /**
40 * Unescapes the given string to make it compatible with XLSX.
41 *
42 * @param string $string The string to unescape
43 *
44 * @return string The unescaped string
45 */
46 public function unescape($string)
47 {
48 $this->initIfNeeded();
49
50 // ==============
51 // = WARNING =
52 // ==============
53 // It is assumed that the given string has already had its XML entities decoded.
54 // This is true if the string is coming from a DOMNode (as DOMNode already decode XML entities on creation).
55 // Therefore there is no need to call "htmlspecialchars_decode()".
56 return $this->unescapeControlCharacters($string);
57 }
58
59 /**
60 * Initializes the control characters if not already done.
61 */
62 protected function initIfNeeded()
63 {
64 if (!$this->isAlreadyInitialized) {
65 $this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
66 $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
67 $this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap);
68
69 $this->isAlreadyInitialized = true;
70 }
71 }
72
73 /**
74 * @return string Regex pattern containing all escapable control characters
75 */
76 protected function getEscapableControlCharactersPattern()
77 {
78 // control characters values are from 0 to 1F (hex values) in the ASCII table
79 // some characters should not be escaped though: "\t", "\r" and "\n".
80 return '[\x00-\x08'.
81 // skipping "\t" (0x9) and "\n" (0xA)
82 '\x0B-\x0C'.
83 // skipping "\r" (0xD)
84 '\x0E-\x1F]';
85 }
86
87 /**
88 * Builds the map containing control characters to be escaped
89 * mapped to their escaped values.
90 * "\t", "\r" and "\n" don't need to be escaped.
91 *
92 * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
93 *
94 * @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
95 *
96 * @return string[]
97 */
98 protected function getControlCharactersEscapingMap()
99 {
100 $controlCharactersEscapingMap = [];
101
102 // control characters values are from 0 to 1F (hex values) in the ASCII table
103 for ($charValue = 0x00; $charValue <= 0x1F; ++$charValue) {
104 $character = \chr($charValue);
105 if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
106 $charHexValue = dechex($charValue);
107 $escapedChar = '_x'.sprintf('%04s', strtoupper($charHexValue)).'_';
108 $controlCharactersEscapingMap[$escapedChar] = $character;
109 }
110 }
111
112 return $controlCharactersEscapingMap;
113 }
114
115 /**
116 * Converts PHP control characters from the given string to OpenXML escaped control characters.
117 *
118 * Excel escapes control characters with _xHHHH_ and also escapes any
119 * literal strings of that type by encoding the leading underscore.
120 * So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
121 *
122 * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
123 *
124 * @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
125 *
126 * @param string $string String to escape
127 *
128 * @return string
129 */
130 protected function escapeControlCharacters($string)
131 {
132 $escapedString = $this->escapeEscapeCharacter($string);
133
134 // if no control characters
135 if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
136 return $escapedString;
137 }
138
139 return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
140 return $this->controlCharactersEscapingReverseMap[$matches[0]];
141 }, $escapedString);
142 }
143
144 /**
145 * Escapes the escape character: "_x0000_" -> "_x005F_x0000_".
146 *
147 * @param string $string String to escape
148 *
149 * @return string The escaped string
150 */
151 protected function escapeEscapeCharacter($string)
152 {
153 return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
154 }
155
156 /**
157 * Converts OpenXML escaped control characters from the given string to PHP control characters.
158 *
159 * Excel escapes control characters with _xHHHH_ and also escapes any
160 * literal strings of that type by encoding the leading underscore.
161 * So "_x0000_" -> "\0" and "_x005F_x0000_" -> "_x0000_"
162 *
163 * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
164 *
165 * @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
166 *
167 * @param string $string String to unescape
168 *
169 * @return string
170 */
171 protected function unescapeControlCharacters($string)
172 {
173 $unescapedString = $string;
174
175 foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
176 // only unescape characters that don't contain the escaped escape character for now
177 $unescapedString = preg_replace("/(?<!_x005F)({$escapedCharValue})/", $charValue, $unescapedString);
178 }
179
180 return $this->unescapeEscapeCharacter($unescapedString);
181 }
182
183 /**
184 * Unecapes the escape character: "_x005F_x0000_" => "_x0000_".
185 *
186 * @param string $string String to unescape
187 *
188 * @return string The unescaped string
189 */
190 protected function unescapeEscapeCharacter($string)
191 {
192 return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
193 }
194 }
195