Completed
Push — master ( dd1269...21c3dd )
by Sam
02:11
created

Writer::getBuffer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
namespace SPSS\Sav;
4
5
use SPSS\Buffer;
6
use SPSS\Exception;
7
use SPSS\Utils;
8
9
class Writer
10
{
11
    /**
12
     * @var Record\Header
13
     */
14
    public $header;
15
16
    /**
17
     * @var Record\Variable[]
18
     */
19
    public $variables = [];
20
21
    /**
22
     * @var Record\ValueLabel[]
23
     */
24
    public $valueLabels = [];
25
26
    /**
27
     * @var Record\Document
28
     */
29
    public $document;
30
31
    /**
32
     * @var Record\Info[]
33
     */
34
    public $info = [];
35
36
    /**
37
     * @var Record\Data
38
     */
39
    public $data;
40
41
    /**
42
     * @var Buffer
43
     */
44
    protected $buffer;
45
46
    /**
47
     * Writer constructor.
48
     *
49
     * @param array $data
50
     * @throws \Exception
51
     */
52 5
    public function __construct($data = [])
53
    {
54 5
        $this->buffer = Buffer::factory();
55 5
        $this->buffer->context = $this;
56
57 5
        if (! empty($data)) {
58 5
            $this->write($data);
59
        }
60 5
    }
61
62
    /**
63
     * @param array $data
64
     * @throws \Exception
65
     */
66 5
    public function write($data)
67
    {
68 5
        $this->header = new Record\Header($data['header']);
69 5
        $this->header->nominalCaseSize = 0;
70 5
        $this->header->casesCount = 0;
71
72 5
        $this->info[Record\Info\MachineInteger::SUBTYPE] = $this->prepareInfoRecord(
73 5
            Record\Info\MachineInteger::class,
74
            $data
75
        );
76
77 5
        $this->info[Record\Info\MachineFloatingPoint::SUBTYPE] = $this->prepareInfoRecord(
78 5
            Record\Info\MachineFloatingPoint::class,
79
            $data
80
        );
81
82 5
        $this->info[Record\Info\VariableDisplayParam::SUBTYPE] = new Record\Info\VariableDisplayParam();
83 5
        $this->info[Record\Info\LongVariableNames::SUBTYPE] = new Record\Info\LongVariableNames();
84 5
        $this->info[Record\Info\VeryLongString::SUBTYPE] = new Record\Info\VeryLongString();
85 5
        $this->info[Record\Info\ExtendedNumberOfCases::SUBTYPE] = $this->prepareInfoRecord(
86 5
            Record\Info\ExtendedNumberOfCases::class,
87
            $data
88
        );
89 5
        $this->info[Record\Info\VariableAttributes::SUBTYPE] = new Record\Info\VariableAttributes();
90 5
        $this->info[Record\Info\LongStringValueLabels::SUBTYPE] = new Record\Info\LongStringValueLabels();
91 5
        $this->info[Record\Info\LongStringMissingValues::SUBTYPE] = new Record\Info\LongStringMissingValues();
92 5
        $this->info[Record\Info\CharacterEncoding::SUBTYPE] = new Record\Info\CharacterEncoding('UTF-8');
93
94 5
        $this->data = new Record\Data();
95
96 5
        $nominalIdx = 0;
97
98
        /**
99
         * @var bool[string] The variable names used in this SPSS file
100
         */
101 5
        $variableNames = [];
102
        /** @var Variable $var */
103 5
        foreach (array_values($data['variables']) as $idx => $var) {
104
105 5
            if (is_array($var)) {
106 5
                $var = new Variable($var);
107
            }
108
109
            #if (! preg_match('/^[A-Za-z0-9_]+$/', $var->name)) {
110
            # UTF-8 and '.' characters could pass here
111 5
            if (! preg_match('/^[A-Za-z0-9_\.\x{4e00}-\x{9fa5}]+$/u', $var->name)) {
112
                throw new \InvalidArgumentException(
113
                    sprintf('Variable name `%s` contains an illegal character.', $var->name)
114
                );
115
            }
116
117 5
            if (empty($var->width)) {
118
                throw new \InvalidArgumentException(
119
                    sprintf('Invalid field width. Should be an integer number greater than zero.')
120
                );
121
            }
122
123 5
            $variable = new Record\Variable();
124
125
            /**
126
             * @see \SPSS\Sav\Record\Variable::getSegmentName()
127
             *
128
             * Variable names in the SPSS file should be unique. If they are not, SPSS will rename them.
129
             * If SPSS renames them and it happens to be a long string then the segments will no longer share
130
             * the required prefix in the name.
131
             */
132 5
            $name = strtoupper(substr($var->name, 0, 8));
133
134 5
            $counter = 0;
135
            /**
136
             * Using base convert we can encode 36^3 = 46656 variables with a common 5 character prefix in an 8
137
             * character variable name. This should suffice since the current variable limit of SPSS is 32767
138
             * variables.
139
             */
140 5
            while (isset($variableNames[$name])) {
141
                $name = strtoupper(substr($var->name, 0, 5) . base_convert($counter, 10, 36));
142
                $counter++;
143
            }
144
145 5
            $variableNames[$name] = true;
146 5
            $variable->name = $name;
147
148 5
            if ($var->format == Variable::FORMAT_TYPE_A) {
149 4
                $variable->width = $var->width;
150
            } else {
151 4
                $variable->width = 0;
152
            }
153
154 5
            $variable->label = $var->label;
155 5
            $variable->print = [
156 5
                0,
157 5
                $var->format,
158 5
                $var->width ? min($var->width, 255) : 8,
159 5
                $var->decimals,
160
            ];
161 5
            $variable->write = [
162 5
                0,
163 5
                $var->format,
164 5
                $var->width ? min($var->width, 255) : 8,
165 5
                $var->decimals,
166
            ];
167
168
            // TODO: refactory
169 5
            $shortName = $variable->name;
170 5
            $longName = $var->name;
171
172 5
            if ($var->attributes) {
173 2
                $this->info[Record\Info\VariableAttributes::SUBTYPE][$longName] = $var->attributes;
174
            }
175
176 5
            if ($var->missing) {
177
                if ($var->width <= 8) {
178
                    if (count($var->missing) >= 3) {
179
                        $variable->missingValuesFormat = 3;
180
                    } elseif (count($var->missing) == 2) {
181
                        $variable->missingValuesFormat = -2;
182
                    } else {
183
                        $variable->missingValuesFormat = 1;
184
                    }
185
                    $variable->missingValues = $var->missing;
186
                } else {
187
                    $this->info[Record\Info\LongStringMissingValues::SUBTYPE][$shortName] = $var->missing;
188
                }
189
            }
190
191 5
            $this->variables[$idx] = $variable;
192
193 5
            if ($var->values) {
194 2
                if ($variable->width > 8) {
195
                    $this->info[Record\Info\LongStringValueLabels::SUBTYPE][$longName] = [
196
                        'width' => $var->width,
197
                        'values' => $var->values,
198
                    ];
199
                } else {
200 2
                    $valueLabel = new Record\ValueLabel([
201 2
                        'variables' => $this->variables,
202
                    ]);
203 2
                    foreach ($var->values as $key => $value) {
204 2
                        $valueLabel->labels[] = [
205 2
                            'value' => $key,
206 2
                            'label' => $value,
207
                        ];
208 2
                        $valueLabel->indexes = [$nominalIdx + 1];
209
                    }
210 2
                    $this->valueLabels[] = $valueLabel;
211
                }
212
            }
213
214 5
            $this->info[Record\Info\LongVariableNames::SUBTYPE][$shortName] = $var->name;
215
216 5
            if (Record\Variable::isVeryLong($var->width)) {
217 1
                $this->info[Record\Info\VeryLongString::SUBTYPE][$shortName] = $var->width;
218
            }
219
220 5
            $segmentCount = Utils::widthToSegments($var->width);
221 5
            for ($i = 0; $i < $segmentCount; $i++) {
222 5
                $this->info[Record\Info\VariableDisplayParam::SUBTYPE][] = [
223 5
                    $var->getMeasure(),
224 5
                    $var->getColumns(),
225 5
                    $var->getAlignment(),
226
                ];
227
            }
228
229
            // TODO: refactory
230 5
            $dataCount = count($var->data);
231 5
            if ($dataCount > $this->header->casesCount) {
232 4
                $this->header->casesCount = $dataCount;
233
            }
234
235 5
            foreach ($var->data as $case => $value) {
236 4
                $this->data->matrix[$case][$idx] = $value;
237
            }
238
239 5
            $nominalIdx += Utils::widthToOcts($var->width);
240
        }
241
242 5
        $this->header->nominalCaseSize = $nominalIdx;
243
244
        // write header
245 5
        $this->header->write($this->buffer);
246
247
        // write variables
248 5
        foreach ($this->variables as $variable) {
249 5
            $variable->write($this->buffer);
250
        }
251
252
        // write valueLabels
253 5
        foreach ($this->valueLabels as $valueLabel) {
254 2
            $valueLabel->write($this->buffer);
255
        }
256
257
        // write documents
258 5
        if (! empty($data['documents'])) {
259 1
            $this->document = new Record\Document([
260 1
                    'lines' => $data['documents'],
261
                ]
262
            );
263 1
            $this->document->write($this->buffer);
264
        }
265
266 5
        foreach ($this->info as $info) {
267 5
            $info->write($this->buffer);
268
        }
269
270 5
        $this->data->write($this->buffer);
271 5
    }
272
273
    /**
274
     * @param $file
275
     * @return false|int
276
     */
277
    public function save($file)
278
    {
279
        return $this->buffer->saveToFile($file);
280
    }
281
282
    /**
283
     * @return \SPSS\Buffer
284
     */
285 5
    public function getBuffer()
286
    {
287 5
        return $this->buffer;
288
    }
289
290
    /**
291
     * @param string $className
292
     * @param array $data
293
     * @param string $group
294
     * @return array
295
     * @throws Exception
296
     */
297 5
    private function prepareInfoRecord($className, $data, $group = 'info')
298
    {
299 5
        if (! class_exists($className)) {
300
            throw new Exception('Unknown class');
301
        }
302 5
        $key = lcfirst(substr($className, strrpos($className, '\\') + 1));
303
304 5
        return new $className(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new $className(Is...group][$key] : array()) returns the type object which is incompatible with the documented return type array.
Loading history...
305 5
            isset($data[$group]) && isset($data[$group][$key]) ?
306
                $data[$group][$key] :
307 5
                []
308
        );
309
    }
310
}
311