SharedStringsTable::getBlocksSizesOrDataToWrite()   D
last analyzed

Complexity

Conditions 17
Paths 24

Size

Total Lines 112
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 112
rs 4.8361
cc 17
eloc 54
nc 24
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Xls;
3
4
class SharedStringsTable
5
{
6
    /**
7
     * Total number of strings
8
     * @var int
9
     */
10
    protected $totalCount = 0;
11
12
    /**
13
     * Number of unique strings
14
     * @var int
15
     */
16
    protected $uniqueCount = 0;
17
18
    /**
19
     * Array containing all the unique strings
20
     * @var array
21
     */
22
    protected $data = array();
23
24
    /**
25
     * @return int
26
     */
27
    public function getTotalCount()
28
    {
29
        return $this->totalCount;
30
    }
31
32
    /**
33
     * @return int
34
     */
35
    public function getUniqueCount()
36
    {
37
        return $this->uniqueCount;
38
    }
39
40
    /**
41
     * @return array
42
     */
43
    public function getStrings()
44
    {
45
        return array_keys($this->data);
46
    }
47
48
    /**
49
     * Add string to table. Returns string index
50
     * @param $str
51
     * @return integer
52
     */
53
    public function add($str)
54
    {
55
        $str = StringUtils::toBiff8UnicodeLong($str);
56
57
        if (!isset($this->data[$str])) {
58
            $this->data[$str] = $this->uniqueCount++;
59
        }
60
61
        $this->totalCount++;
62
63
        return $this->data[$str];
64
    }
65
66
    /**
67
     * @param $str
68
     *
69
     * @return array
70
     */
71
    public function getStringInfo($str)
72
    {
73
        $info = unpack("vlength/Cunicode", $str);
74
75
        return array(
76
            'is_unicode' => $info["unicode"],
77
            'header_length' => ($info["unicode"] == 1) ? 4 : 3,
78
            'length' => strlen($str)
79
        );
80
    }
81
82
    /**
83
     * Handling of the SST continue blocks is complicated by the need to include an
84
     * additional continuation byte depending on whether the string is split between
85
     * blocks or whether it starts at the beginning of the block. (There are also
86
     * additional complications that will arise later when/if Rich Strings are
87
     * supported).
88
     *
89
     * @param null|array $tmpBlockSizes
90
     * @param bool $returnDataToWrite
91
     *
92
     * @return array
93
     */
94
    public function getBlocksSizesOrDataToWrite($tmpBlockSizes = null, $returnDataToWrite = false)
95
    {
96
        $continueLimit = Biff8::CONTINUE_LIMIT;
97
        $blockLength = 0;
98
        $written = 0;
99
        $blockSizes = array();
100
        $data = array();
101
        $continue = 0;
102
103
        if ($returnDataToWrite) {
104
            $originalBlocksSizes = $tmpBlockSizes;
105
            array_shift($tmpBlockSizes);
106
        }
107
108
        foreach ($this->getStrings() as $string) {
109
            $info = $this->getStringInfo($string);
110
            $splitString = 0;
111
112
            // Block length is the total length of the strings that will be
113
            // written out in a single SST or CONTINUE block.
114
            $blockLength += $info['length'];
115
116
            // We can write the string if it doesn't cross a CONTINUE boundary
117
            if ($blockLength < $continueLimit) {
118
                $data[] = $string;
119
                $written += $info['length'];
120
                continue;
121
            }
122
123
            // Deal with the cases where the next string to be written will exceed
124
            // the CONTINUE boundary. If the string is very long it may need to be
125
            // written in more than one CONTINUE record.
126
            while ($blockLength >= $continueLimit) {
127
                $spaceRemaining = $continueLimit - $written - $continue;
128
129
                // Unicode data should only be split on char (2 byte) boundaries.
130
                // Therefore, in some cases we need to reduce the amount of available
131
                // space by 1 byte to ensure the correct alignment.
132
                $align = 0;
133
134
                if ($spaceRemaining > $info['header_length']) {
135
                    // Only applies to Unicode strings
136
                    if ($info['is_unicode']) {
137
                        if (!$splitString && $spaceRemaining % 2 != 1) {
138
                            // String contains 3 byte header => split on odd boundary
139
                            $spaceRemaining--;
140
                            $align = 1;
141
                        } elseif ($splitString && $spaceRemaining % 2 == 1) {
142
                            // Split section without header => split on even boundary
143
                            $spaceRemaining--;
144
                            $align = 1;
145
                        }
146
147
                        $splitString = 1;
148
                    }
149
150
                    // Write as much as possible of the string in the current block
151
                    $data[] = substr($string, 0, $spaceRemaining);
152
                    $written += $spaceRemaining;
153
154
                    // The remainder will be written in the next block(s)
155
                    $string = substr($string, $spaceRemaining);
156
157
                    // Reduce the current block length by the amount written
158
                    $blockLength -= $continueLimit - $continue - $align;
159
160
                    // Store the max size for this block
161
                    $blockSizes[] = $continueLimit - $align;
162
163
                    // If the current string was split then the next CONTINUE block
164
                    // should have the string continue flag (grbit) set unless the
165
                    // split string fits exactly into the remaining space.
166
                    $continue = ($blockLength > 0) ? 1 : 0;
167
                } else {
168
                    // Store the max size for this block
169
                    $blockSizes[] = $written + $continue;
170
171
                    // Not enough space to start the string in the current block
172
                    $blockLength -= $continueLimit - $spaceRemaining - $continue;
173
                    $continue = 0;
174
                }
175
176
                // Write the CONTINUE block header
177
                if (!empty($originalBlocksSizes)) {
178
                    $length = array_shift($tmpBlockSizes);
179
                    $header = Record\ContinueRecord::getHeader($length);
180
                    if ($continue) {
181
                        $header .= pack('C', intval($info['is_unicode']));
182
                    }
183
184
                    $data[] = $header;
185
                }
186
187
                // If the string (or substr) is small enough we can write it in the
188
                // new CONTINUE block. Else, go through the loop again to write it in
189
                // one or more CONTINUE blocks
190
                if ($blockLength < $continueLimit) {
191
                    $data[] = $string;
192
                    $written = $blockLength;
193
                } else {
194
                    $written = 0;
195
                }
196
            }
197
        }
198
199
        // Store the max size for the last block unless it is empty
200
        if ($written + $continue) {
201
            $blockSizes[] = $written + $continue;
202
        }
203
204
        return ($returnDataToWrite) ? $data : $blockSizes;
205
    }
206
207
    /**
208
     * @return array
209
     */
210
    public function getDataForWrite()
211
    {
212
        return $this->getBlocksSizesOrDataToWrite(
213
            $this->getBlocksSizes(),
214
            true
215
        );
216
    }
217
218
    /**
219
     * @return array
220
     */
221
    public function getBlocksSizes()
222
    {
223
        return $this->getBlocksSizesOrDataToWrite();
224
    }
225
}
226