Completed
Pull Request — master (#383)
by Hura
03:16
created

Worksheet::getTableElementStartAsString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
crap 1
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 123
    public function __construct($externalSheet, $worksheetFilesFolder)
48
    {
49 123
        $this->externalSheet = $externalSheet;
50
        /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
51 123
        $this->stringsEscaper = \Box\Spout\Common\Escaper\ODS::getInstance();
52 123
        $this->worksheetFilePath = $worksheetFilesFolder . '/sheet' . $externalSheet->getIndex() . '.xml';
53
54 123
        $this->stringHelper = new StringHelper();
55
56 123
        $this->startSheet();
57 123
    }
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 123
    protected function startSheet()
68
    {
69 123
        $this->sheetFilePointer = fopen($this->worksheetFilePath, 'w');
70 123
        $this->throwIfSheetFilePointerIsNotAvailable();
71 123
    }
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 123
    protected function throwIfSheetFilePointerIsNotAvailable()
80
    {
81 123
        if (!$this->sheetFilePointer) {
82
            throw new IOException('Unable to open sheet for writing.');
83
        }
84 123
    }
85
86
    /**
87
     * @return string Path to the temporary sheet content XML file
88
     */
89 96
    public function getWorksheetFilePath()
90
    {
91 96
        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 96
    public function getTableElementStartAsString()
100
    {
101 96
        $escapedSheetName = $this->stringsEscaper->escape($this->externalSheet->getName());
102 96
        $tableStyleName = 'ta' . ($this->externalSheet->getIndex() + 1);
103
104 96
        $tableElement  = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
105 96
        $tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $this->maxNumColumns . '"/>';
106
107 96
        return $tableElement;
108
    }
109
110
    /**
111
     * @return \Box\Spout\Writer\Common\Sheet The "external" sheet
112
     */
113 30
    public function getExternalSheet()
114
    {
115 30
        return $this->externalSheet;
116
    }
117
118
    /**
119
     * @return int The index of the last written row
120
     */
121 90
    public function getLastWrittenRowIndex()
122
    {
123 90
        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 90
    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 90
        $dataRowWithNumericIndexes = array_values($dataRow);
141
142 90
        $styleIndex = ($style->getId() + 1); // 1-based
143 90
        $cellsCount = count($dataRow);
144 90
        $this->maxNumColumns = max($this->maxNumColumns, $cellsCount);
145
146 90
        $data = '<table:table-row table:style-name="ro1">';
147
148 90
        $currentCellIndex = 0;
149 90
        $nextCellIndex = 1;
150
151 90
        for ($i = 0; $i < $cellsCount; $i++) {
152 90
            $currentCellValue = $dataRowWithNumericIndexes[$currentCellIndex];
153
154
            // Using isset here because it is way faster than array_key_exists...
155 90
            if (!isset($dataRowWithNumericIndexes[$nextCellIndex]) ||
156 90
                $currentCellValue !== $dataRowWithNumericIndexes[$nextCellIndex]) {
157
158 90
                $numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
159 90
                $data .= $this->getCellXML($currentCellValue, $styleIndex, $numTimesValueRepeated);
160
161 87
                $currentCellIndex = $nextCellIndex;
162 87
            }
163
164 87
            $nextCellIndex++;
165 87
        }
166
167 87
        $data .= '</table:table-row>';
168
169 87
        $wasWriteSuccessful = fwrite($this->sheetFilePointer, $data);
170 87
        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 87
        $this->lastWrittenRowIndex++;
176 87
    }
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 90
    protected function getCellXML($cellValue, $styleIndex, $numTimesValueRepeated)
188
    {
189 90
        $data = '<table:table-cell table:style-name="ce' . $styleIndex . '"';
190
191 90
        if ($numTimesValueRepeated !== 1) {
192 12
            $data .= ' table:number-columns-repeated="' . $numTimesValueRepeated . '"';
193 12
        }
194
195 90
        if ($cellValue instanceof Cell) {
196 6
            $cell = $cellValue;
197 6
        } else {
198 84
            $cell = new Cell($cellValue);
199
        }
200
201 90
        if ($cell->isString()) {
202 78
            $data .= ' office:value-type="string" calcext:value-type="string">';
203
204 78
            $cellValueLines = explode("\n", $cell->getValue());
205 78
            foreach ($cellValueLines as $cellValueLine) {
206 78
                $data .= '<text:p>' . $this->stringsEscaper->escape($cellValueLine) . '</text:p>';
207 78
            }
208
209 78
            $data .= '</table:table-cell>';
210 90
        } else if ($cell->isBoolean()) {
211 9
            $data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
212 9
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
213 9
            $data .= '</table:table-cell>';
214 21
        } else if ($cell->isNumeric()) {
215 9
            $data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
216 9
            $data .= '<text:p>' . $cell->getValue() . '</text:p>';
217 9
            $data .= '</table:table-cell>';
218 18
        } else if ($cell->isBlank()) {
219 6
            $data .= '/>';
220 6
        } else {
221 6
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue()));
222
        }
223
224 87
        return $data;
225
    }
226
227
    /**
228
     * Closes the worksheet
229
     *
230
     * @return void
231
     */
232 96
    public function close()
233
    {
234 96
        if (!is_resource($this->sheetFilePointer)) {
235
            return;
236
        }
237
238 96
        fclose($this->sheetFilePointer);
239 96
    }
240
}
241