Completed
Pull Request — develop_3.0 (#433)
by Adrien
06:44
created

throwIfSheetFilePointerIsNotAvailable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 4
cp 0.75
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
crap 2.0625
1
<?php
2
3
namespace Box\Spout\Writer\ODS\Manager;
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\Entity\Cell;
9
use Box\Spout\Writer\Common\Entity\Worksheet;
10
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
11
use Box\Spout\Writer\Common\Entity\Style\Style;
12
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
13
14
/**
15
 * Class WorksheetManager
16
 * ODS worksheet manager, providing the interfaces to work with ODS worksheets.
17
 *
18
 * @package Box\Spout\Writer\ODS\Manager
19
 */
20
class WorksheetManager implements WorksheetManagerInterface
21
{
22
    /** @var \Box\Spout\Common\Escaper\ODS Strings escaper */
23
    private $stringsEscaper;
24
25
    /** @var StringHelper String helper */
26
    private $stringHelper;
27
28
    /**
29
     * WorksheetManager constructor.
30
     *
31
     * @param \Box\Spout\Common\Escaper\ODS $stringsEscaper
32
     * @param StringHelper $stringHelper
33
     */
34 43
    public function __construct(
35
        \Box\Spout\Common\Escaper\ODS $stringsEscaper,
36
        StringHelper $stringHelper)
37
    {
38 43
        $this->stringsEscaper = $stringsEscaper;
39 43
        $this->stringHelper = $stringHelper;
40 43
    }
41
42
    /**
43
     * Prepares the worksheet to accept data
44
     *
45
     * @param Worksheet $worksheet The worksheet to start
46
     * @return void
47
     * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
48
     */
49 43
    public function startSheet(Worksheet $worksheet)
50
    {
51 43
        $sheetFilePointer = fopen($worksheet->getFilePath(), 'w');
52 43
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
53
54 43
        $worksheet->setFilePointer($sheetFilePointer);
55 43
    }
56
57
    /**
58
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
59
     *
60
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
61
     * @return void
62
     * @throws IOException If the sheet data file cannot be opened for writing
63
     */
64 43
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
65
    {
66 43
        if (!$sheetFilePointer) {
67
            throw new IOException('Unable to open sheet for writing.');
68
        }
69 43
    }
70
71
    /**
72
     * Returns the table XML root node as string.
73
     *
74
     * @param Worksheet $worksheet
75
     * @return string <table> node as string
76
     */
77 34
    public function getTableElementStartAsString(Worksheet $worksheet)
78
    {
79 34
        $externalSheet = $worksheet->getExternalSheet();
80 34
        $escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
81 34
        $tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
82
83 34
        $tableElement  = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
84 34
        $tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $worksheet->getMaxNumColumns() . '"/>';
85
86 34
        return $tableElement;
87
    }
88
89
    /**
90
     * Adds data to the given worksheet.
91
     *
92
     * @param Worksheet $worksheet The worksheet to add the row to
93
     * @param array $dataRow Array containing data to be written. Cannot be empty.
94
     *          Example $dataRow = ['data1', 1234, null, '', 'data5'];
95
     * @param Style $rowStyle Style to be applied to the row. NULL means use default style.
96
     * @return void
97
     * @throws IOException If the data cannot be written
98
     * @throws InvalidArgumentException If a cell value's type is not supported
99
     */
100 31
    public function addRow(Worksheet $worksheet, $dataRow, $rowStyle)
101
    {
102
        // $dataRow can be an associative array. We need to transform
103
        // it into a regular array, as we'll use the numeric indexes.
104 31
        $dataRowWithNumericIndexes = array_values($dataRow);
105
106 31
        $styleIndex = ($rowStyle->getId() + 1); // 1-based
107 31
        $cellsCount = count($dataRow);
108
109 31
        $data = '<table:table-row table:style-name="ro1">';
110
111 31
        $currentCellIndex = 0;
112 31
        $nextCellIndex = 1;
113
114 31
        for ($i = 0; $i < $cellsCount; $i++) {
115 31
            $currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex];
116
117
            // Using isset here because it is way faster than array_key_exists...
118 31
            if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) ||
119 21
                $currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) {
120
121 31
                $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
122 31
                $data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated);
123
124 30
                $currentCellIndex = $nextCellIndex;
125
            }
126
127 30
            $nextCellIndex++;
128
        }
129
130 30
        $data .= '</table:table-row>';
131
132 30
        $wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $data);
133 30
        if ($wasWriteSuccessful === false) {
134
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
135
        }
136
137
        // only update the count if the write worked
138 30
        $lastWrittenRowIndex = $worksheet->getLastWrittenRowIndex();
139 30
        $worksheet->setLastWrittenRowIndex($lastWrittenRowIndex + 1);
140 30
    }
141
142
    /**
143
     * Returns the cell XML content, given its value.
144
     *
145
     * @param mixed $cellValue The value to be written
146
     * @param int $styleIndex Index of the used style
147
     * @param int $numTimesValueRepeated Number of times the value is consecutively repeated
148
     * @return string The cell XML content
149
     * @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
150
     */
151 31
    private function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
152
    {
153 31
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
154
155 31
        if ($numTimesValueRepeated !== 1) {
156 4
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
157
        }
158
159
        /** @TODO Remove code duplication with XLSX writer: https://github.com/box/spout/pull/383#discussion_r113292746 */
160 31
        if ($cellValue instanceof Cell) {
161 2
            $cell = $cellValue;
162
        } else {
163 29
            $cell = new Cell($cellValue);
164
        }
165
166 31
        if ($cell->isString()) {
167 27
            $data .= ' office:value-type="string" calcext:value-type="string">';
168
169 27
            $cellValueLines = explode("\n", $cell->getValue());
170 27
            foreach ($cellValueLines as $cellValueLine) {
171 27
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
172
            }
173
174 27
            $data .= '</table:table-cell>';
175 7
        } else if ($cell->isBoolean()) {
176 3
            $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
177 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
178 3
            $data .= '</table:table-cell>';
179 6
        } else if ($cell->isNumeric()) {
180 3
            $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
181 3
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
182 3
            $data .= '</table:table-cell>';
183 4
        } else if ($cell->isEmpty()) {
184 2
            $data .= '/>';
185
        } else {
186 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue()));
187
        }
188
189 30
        return $data;
190
    }
191
192
    /**
193
     * Closes the worksheet
194
     *
195
     * @param Worksheet $worksheet
196
     * @return void
197
     */
198 34
    public function close(Worksheet $worksheet)
199
    {
200 34
        $worksheetFilePointer = $worksheet->getFilePointer();
201
202 34
        if (!is_resource($worksheetFilePointer)) {
203
            return;
204
        }
205
206 34
        fclose($worksheetFilePointer);
207
    }
208
}