Completed
Push — master ( dc31d6...a43c13 )
by Adrien
03:21
created

XLSX::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
ccs 3
cts 3
cp 1
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Box\Spout\Common\Escaper;
4
5
use Box\Spout\Common\Singleton;
6
7
/**
8
 * Class XLSX
9
 * Provides functions to escape and unescape data for XLSX files
10
 *
11
 * @package Box\Spout\Common\Escaper
12
 */
13
class XLSX implements EscaperInterface
14
{
15
    use Singleton;
16
17
    /** @var string[] Control characters to be escaped */
18
    protected $controlCharactersEscapingMap;
19
20
    /**
21
     * Initializes the singleton instance
22
     */
23 3
    protected function init()
24
    {
25 3
        $this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
26 3
    }
27
28
    /**
1 ignored issue
show
introduced by
Instead of declaring the constructor as final, maybe you should declare the entire class as final.
Loading history...
29
     * Escapes the given string to make it compatible with XLSX
30
     *
31
     * @param string $string The string to escape
32
     * @return string The escaped string
33
     */
34 87
    public function escape($string)
35
    {
36 87
        $escapedString = $this->escapeControlCharacters($string);
37 87
        $escapedString = htmlspecialchars($escapedString, ENT_QUOTES);
38
39 87
        return $escapedString;
40
    }
41
42
    /**
43
     * Unescapes the given string to make it compatible with XLSX
44
     *
45
     * @param string $string The string to unescape
46
     * @return string The unescaped string
47
     */
48 129
    public function unescape($string)
49
    {
50 129
        $unescapedString = htmlspecialchars_decode($string, ENT_QUOTES);
51 129
        $unescapedString = $this->unescapeControlCharacters($unescapedString);
52
53 129
        return $unescapedString;
54
    }
55
56
    /**
57
     * Builds the map containing control characters to be escaped
58
     * mapped to their escaped values.
59
     * "\t", "\r" and "\n" don't need to be escaped.
60
     *
61
     * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
62
     * @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
63
     *
64
     * @return string[]
65
     */
66 3
    protected function getControlCharactersEscapingMap()
67
    {
68 3
        $controlCharactersEscapingMap = [];
69 3
        $whitelistedControlCharacters = ["\t", "\r", "\n"];
70
71
        // control characters values are from 0 to 1F (hex values) in the ASCII table
72 3
        for ($charValue = 0x0; $charValue <= 0x1F; $charValue++) {
73 3
            if (!in_array(chr($charValue), $whitelistedControlCharacters)) {
74 3
                $charHexValue = dechex($charValue);
75 3
                $escapedChar = '_x' . sprintf('%04s' , strtoupper($charHexValue)) . '_';
76 3
                $controlCharactersEscapingMap[$escapedChar] = chr($charValue);
77 3
            }
78 3
        }
79
80 3
        return $controlCharactersEscapingMap;
81
    }
82
83
    /**
84
     * Converts PHP control characters from the given string to OpenXML escaped control characters
85
     *
86
     * Excel escapes control characters with _xHHHH_ and also escapes any
87
     * literal strings of that type by encoding the leading underscore.
88
     * So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
89
     *
90
     * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
91
     * @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
92
     *
93
     * @param string $string String to escape
94
     * @return string
95
     */
96 87
    protected function escapeControlCharacters($string)
97
    {
98 87
        $escapedString = $this->escapeEscapeCharacter($string);
99 87
        return str_replace(array_values($this->controlCharactersEscapingMap), array_keys($this->controlCharactersEscapingMap), $escapedString);
100
    }
101
102
    /**
103
     * Escapes the escape character: "_x0000_" -> "_x005F_x0000_"
104
     *
105
     * @param string $string String to escape
106
     * @return string The escaped string
107
     */
108 87
    protected function escapeEscapeCharacter($string)
109
    {
110 87
        return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
111
    }
112
113
    /**
114
     * Converts OpenXML escaped control characters from the given string to PHP control characters
115
     *
116
     * Excel escapes control characters with _xHHHH_ and also escapes any
117
     * literal strings of that type by encoding the leading underscore.
118
     * So "_x0000_" -> "\0" and "_x005F_x0000_" -> "_x0000_"
119
     *
120
     * NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
121
     * @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
122
     *
123
     * @param string $string String to unescape
124
     * @return string
125
     */
126 129
    protected function unescapeControlCharacters($string)
127
    {
128 129
        $unescapedString = $string;
129 129
        foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
130
            // only unescape characters that don't contain the escaped escape character for now
131 129
            $unescapedString = preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
132 129
        }
133
134 129
        return $this->unescapeEscapeCharacter($unescapedString);
135
    }
136
137
    /**
138
     * Unecapes the escape character: "_x005F_x0000_" => "_x0000_"
139
     *
140
     * @param string $string String to unescape
141
     * @return string The unescaped string
142
     */
143 129
    protected function unescapeEscapeCharacter($string)
144
    {
145 129
        return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
146
    }
147
}
148