Failed Conditions
Push — develop_3.0 ( 80553c...f9d8ad )
by Adrien
31:09
created

Worksheet::addRow()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 5.0023

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 21
cts 22
cp 0.9545
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 6
nop 2
crap 5.0023
1
<?php
2
3
namespace Box\Spout\Writer\ODS\Internal;
4
5
use Box\Spout\Common\Exception\InvalidArgumentException;
6
use Box\Spout\Common\Exception\IOException;
7
use Box\Spout\Common\Helper\StringHelper;
8
use Box\Spout\Writer\Common\Cell;
9
use Box\Spout\Writer\Common\Helper\CellHelper;
10
use Box\Spout\Writer\Common\Internal\WorksheetInterface;
11
12
/**
13
 * Class Worksheet
14
 * Represents a worksheet within a ODS file. The difference with the Sheet object is
15
 * that this class provides an interface to write data
16
 *
17
 * @package Box\Spout\Writer\ODS\Internal
18
 */
19
class Worksheet implements WorksheetInterface
20
{
21
    /** @var \Box\Spout\Writer\Common\Sheet The "external" sheet */
22
    protected $externalSheet;
23
24
    /** @var string Path to the XML file that will contain the sheet data */
25
    protected $worksheetFilePath;
26
27
    /** @var \Box\Spout\Common\Escaper\ODS Strings escaper */
28
    protected $stringsEscaper;
29
30
    /** @var \Box\Spout\Common\Helper\StringHelper To help with string manipulation */
31
    protected $stringHelper;
32
33
    /** @var Resource Pointer to the temporary sheet data file (e.g. worksheets-temp/sheet1.xml) */
34
    protected $sheetFilePointer;
35
36
    /** @var int Maximum number of columns among all the written rows */
37
    protected $maxNumColumns = 1;
38
39
    /** @var int Index of the last written row */
40
    protected $lastWrittenRowIndex = 0;
41
42
    /**
43
     * @param \Box\Spout\Writer\Common\Sheet $externalSheet The associated "external" sheet
44
     * @param string $worksheetFilesFolder Temporary folder where the files to create the ODS will be stored
45
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
46
     */
47 43
    public function __construct($externalSheet, $worksheetFilesFolder)
48
    {
49 43
        $this->externalSheet = $externalSheet;
50
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
51 43
        $this->stringsEscaper = \Box\Spout\Common\Escaper\ODS::getInstance();
52 43
        $this->worksheetFilePath = $worksheetFilesFolder . '/sheet' . $externalSheet->getIndex() . '.xml';
53
54 43
        $this->stringHelper = new StringHelper();
55
56 43
        $this->startSheet();
57 43
    }
58
59
    /**
60
     * Prepares the worksheet to accept data
61
     * The XML file does not contain the "<table:table>" node as it contains the sheet's name
62
     * which may change during the execution of the program. It will be added at the end.
63
     *
64
     * @return void
65
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
66
     */
67 43
    protected function startSheet()
68
    {
69 43
        $this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
70 43
        $this->throwIfSheetFilePointerIsNotAvailable();
71 43
    }
72
73
    /**
74
     * Checks if the book has been created. Throws an exception if not created yet.
75
     *
76
     * @return void
77
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
78
     */
79 43
    protected function throwIfSheetFilePointerIsNotAvailable()
80
    {
81 43
        if (!$this->sheetFilePointer) {
82
            throw new IOException('Unable to open sheet for writing.');
83
        }
84 43
    }
85
86
    /**
87
     * @return string Path to the temporary sheet content XML file
88
     */
89 34
    public function getWorksheetFilePath()
90
    {
91 34
        return $this->worksheetFilePath;
92
    }
93
94
    /**
95
     * Returns the table XML root node as string.
96
     *
97
     * @return string <table> node as string
98
     */
99 34
    public function getTableElementStartAsString()
100
    {
101 34
        $escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
102 34
        $tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
103
104 34
        $tableElement  = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
105 34
        $tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $this->maxNumColumns . '"/>';
106
107 34
        return $tableElement;
108
    }
109
110
    /**
111
     * @return \Box\Spout\Writer\Common\Sheet The "external" sheet
112
     */
113 10
    public function getExternalSheet()
114
    {
115 10
        return $this->externalSheet;
116
    }
117
118
    /**
119
     * @return int The index of the last written row
120
     */
121 31
    public function getLastWrittenRowIndex()
122
    {
123 31
        return $this->lastWrittenRowIndex;
124
    }
125
126
    /**
127
     * Adds data to the worksheet.
128
     *
129
     * @param array $dataRow Array containing data to be written. Cannot be empty.
130
     *          Example $dataRow = ['data1', 1234, null, '', 'data5'];
131
     * @param \Box\Spout\Writer\Style\Style $style Style to be applied to the row. NULL means use default style.
132
     * @return void
133
     * @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
134
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
135
     */
136 31
    public function addRow($dataRow, $style)
137
    {
138
        // $dataRow can be an associative array. We need to transform
139
        // it into a regular array, as we'll use the numeric indexes.
140 31
        $dataRowWithNumericIndexes = array_values($dataRow);
141
142 31
        $styleIndex = ($style->getId() + 1); // 1-based
143 31
        $cellsCount = count($dataRow);
144 31
        $this->maxNumColumns = max($this->maxNumColumns, $cellsCount);
145
146 31
        $data = '<table:table-row table:style-name="ro1">';
147
148 31
        $currentCellIndex = 0;
149 31
        $nextCellIndex = 1;
150
151 31
        for ($i = 0; $i < $cellsCount; $i++) {
152 31
            $currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex];
153
154
            // Using isset here because it is way faster than array_key_exists...
155 31
            if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) ||
156 21
                $currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) {
157
158 31
                $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
159 31
                $data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated);
160
161 30
                $currentCellIndex = $nextCellIndex;
162
            }
163
164 30
            $nextCellIndex++;
165
        }
166
167 30
        $data .= '</table:table-row>';
168
169 30
        $wasWriteSuccessful = fwrite($this->sheetFilePointer, $data);
170 30
        if ($wasWriteSuccessful === false) {
171
            throw new IOException("Unable to write data in {$this->worksheetFilePath}");
172
        }
173
174
        // only update the count if the write worked
175 30
        $this->lastWrittenRowIndex++;
176 30
    }
177
178
    /**
179
     * Returns the cell XML content, given its value.
180
     *
181
     * @param mixed $cellValue The value to be written
182
     * @param int $styleIndex Index of the used style
183
     * @param int $numTimesValueRepeated Number of times the value is consecutively repeated
184
     * @return string The cell XML content
185
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
186
     */
187 31
    protected function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
188
    {
189 31
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
190
191 31
        if ($numTimesValueRepeated !== 1) {
192 4
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
193
        }
194
195
        /** @TODO Remove code duplication with XLSX writer: https://github.com/box/spout/pull/383#discussion_r113292746 */
196 31
        if ($cellValue instanceof Cell) {
197 2
            $cell = $cellValue;
198
        } else {
199 29
            $cell = new Cell($cellValue);
200
        }
201
202 31
        if ($cell->isString()) {
203 27
            $data .= ' office:value-type="string" calcext:value-type="string">';
204
205 27
            $cellValueLines = explode("\n", $cell->getValue());
206 27
            foreach ($cellValueLines as $cellValueLine) {
207 27
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
208
            }
209
210 27
            $data .= '</table:table-cell>';
211 7
        } else if ($cell->isBoolean()) {
212 3
            $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
213 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
214 3
            $data .= '</table:table-cell>';
215 6
        } else if ($cell->isNumeric()) {
216 3
            $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
217 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
218 3
            $data .= '</table:table-cell>';
219 4
        } else if ($cell->isEmpty()) {
220 2
            $data .= '/>';
221
        } else {
222 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue()));
223
        }
224
225 30
        return $data;
226
    }
227
228
    /**
229
     * Closes the worksheet
230
     *
231
     * @return void
232
     */
233 34
    public function close()
234
    {
235 34
        if (!is_resource($this->sheetFilePointer)) {
236
            return;
237
        }
238
239 34
        fclose($this->sheetFilePointer);
240 34
    }
241
}
242