Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace OpenSpout\Reader\ODS;
6
 
7
use DOMElement;
8
use OpenSpout\Common\Exception\IOException;
9
use OpenSpout\Common\Helper\Escaper\ODS;
10
use OpenSpout\Reader\Common\XMLProcessor;
11
use OpenSpout\Reader\Exception\XMLProcessingException;
12
use OpenSpout\Reader\ODS\Helper\CellValueFormatter;
13
use OpenSpout\Reader\ODS\Helper\SettingsHelper;
14
use OpenSpout\Reader\SheetIteratorInterface;
15
use OpenSpout\Reader\Wrapper\XMLReader;
16
 
17
/**
18
 * @implements SheetIteratorInterface<Sheet>
19
 */
20
final class SheetIterator implements SheetIteratorInterface
21
{
22
    public const CONTENT_XML_FILE_PATH = 'content.xml';
23
 
24
    public const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';
25
 
26
    /**
27
     * Definition of XML nodes name and attribute used to parse sheet data.
28
     */
29
    public const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
30
    public const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
31
    public const XML_NODE_TABLE = 'table:table';
32
    public const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
33
    public const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
34
    public const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
35
    public const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
36
 
37
    /** @var string Path of the file to be read */
38
    private readonly string $filePath;
39
 
40
    private readonly Options $options;
41
 
42
    /** @var XMLReader The XMLReader object that will help read sheet's XML data */
43
    private readonly XMLReader $xmlReader;
44
 
45
    /** @var ODS Used to unescape XML data */
46
    private readonly ODS $escaper;
47
 
48
    /** @var bool Whether there are still at least a sheet to be read */
49
    private bool $hasFoundSheet;
50
 
51
    /** @var int The index of the sheet being read (zero-based) */
52
    private int $currentSheetIndex;
53
 
54
    /** @var string The name of the sheet that was defined as active */
55
    private readonly ?string $activeSheetName;
56
 
57
    /** @var array<string, bool> Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] */
58
    private array $sheetsVisibility;
59
 
60
    public function __construct(
61
        string $filePath,
62
        Options $options,
63
        ODS $escaper,
64
        SettingsHelper $settingsHelper
65
    ) {
66
        $this->filePath = $filePath;
67
        $this->options = $options;
68
        $this->xmlReader = new XMLReader();
69
        $this->escaper = $escaper;
70
        $this->activeSheetName = $settingsHelper->getActiveSheetName($filePath);
71
    }
72
 
73
    /**
74
     * Rewind the Iterator to the first element.
75
     *
76
     * @see http://php.net/manual/en/iterator.rewind.php
77
     *
78
     * @throws IOException If unable to open the XML file containing sheets' data
79
     */
80
    public function rewind(): void
81
    {
82
        $this->xmlReader->close();
83
 
84
        if (false === $this->xmlReader->openFileInZip($this->filePath, self::CONTENT_XML_FILE_PATH)) {
85
            $contentXmlFilePath = $this->filePath.'#'.self::CONTENT_XML_FILE_PATH;
86
 
87
            throw new IOException("Could not open \"{$contentXmlFilePath}\".");
88
        }
89
 
90
        try {
91
            $this->sheetsVisibility = $this->readSheetsVisibility();
92
            $this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
93
        } catch (XMLProcessingException $exception) {
94
            throw new IOException("The content.xml file is invalid and cannot be read. [{$exception->getMessage()}]");
95
        }
96
 
97
        $this->currentSheetIndex = 0;
98
    }
99
 
100
    /**
101
     * Checks if current position is valid.
102
     *
103
     * @see http://php.net/manual/en/iterator.valid.php
104
     */
105
    public function valid(): bool
106
    {
107
        $valid = $this->hasFoundSheet;
108
        if (!$valid) {
109
            $this->xmlReader->close();
110
        }
111
 
112
        return $valid;
113
    }
114
 
115
    /**
116
     * Move forward to next element.
117
     *
118
     * @see http://php.net/manual/en/iterator.next.php
119
     */
120
    public function next(): void
121
    {
122
        $this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);
123
 
124
        if ($this->hasFoundSheet) {
125
            ++$this->currentSheetIndex;
126
        }
127
    }
128
 
129
    /**
130
     * Return the current element.
131
     *
132
     * @see http://php.net/manual/en/iterator.current.php
133
     */
134
    public function current(): Sheet
135
    {
136
        $escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME);
137
        \assert(null !== $escapedSheetName);
138
        $sheetName = $this->escaper->unescape($escapedSheetName);
139
 
140
        $isSheetActive = $this->isSheetActive($sheetName, $this->currentSheetIndex, $this->activeSheetName);
141
 
142
        $sheetStyleName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_STYLE_NAME);
143
        \assert(null !== $sheetStyleName);
144
        $isSheetVisible = $this->isSheetVisible($sheetStyleName);
145
 
146
        return new Sheet(
147
            new RowIterator(
148
                $this->options,
149
                new CellValueFormatter($this->options->SHOULD_FORMAT_DATES, new ODS()),
150
                new XMLProcessor($this->xmlReader)
151
            ),
152
            $this->currentSheetIndex,
153
            $sheetName,
154
            $isSheetActive,
155
            $isSheetVisible
156
        );
157
    }
158
 
159
    /**
160
     * Return the key of the current element.
161
     *
162
     * @see http://php.net/manual/en/iterator.key.php
163
     */
164
    public function key(): int
165
    {
166
        return $this->currentSheetIndex + 1;
167
    }
168
 
169
    /**
170
     * Extracts the visibility of the sheets.
171
     *
172
     * @return array<string, bool> Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE]
173
     */
174
    private function readSheetsVisibility(): array
175
    {
176
        $sheetsVisibility = [];
177
 
178
        $this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);
179
 
180
        $automaticStylesNode = $this->xmlReader->expand();
181
        \assert($automaticStylesNode instanceof DOMElement);
182
 
183
        $tableStyleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES);
184
 
185
        foreach ($tableStyleNodes as $tableStyleNode) {
186
            $isSheetVisible = ('false' !== $tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY));
187
 
188
            $parentStyleNode = $tableStyleNode->parentNode;
189
            \assert($parentStyleNode instanceof DOMElement);
190
            $styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);
191
 
192
            $sheetsVisibility[$styleName] = $isSheetVisible;
193
        }
194
 
195
        return $sheetsVisibility;
196
    }
197
 
198
    /**
199
     * Returns whether the current sheet was defined as the active one.
200
     *
201
     * @param string      $sheetName       Name of the current sheet
202
     * @param int         $sheetIndex      Index of the current sheet
203
     * @param null|string $activeSheetName Name of the sheet that was defined as active or NULL if none defined
204
     *
205
     * @return bool Whether the current sheet was defined as the active one
206
     */
207
    private function isSheetActive(string $sheetName, int $sheetIndex, ?string $activeSheetName): bool
208
    {
209
        // The given sheet is active if its name matches the defined active sheet's name
210
        // or if no information about the active sheet was found, it defaults to the first sheet.
211
        return
212
            (null === $activeSheetName && 0 === $sheetIndex)
213
            || ($activeSheetName === $sheetName);
214
    }
215
 
216
    /**
217
     * Returns whether the current sheet is visible.
218
     *
219
     * @param string $sheetStyleName Name of the sheet style
220
     *
221
     * @return bool Whether the current sheet is visible
222
     */
223
    private function isSheetVisible(string $sheetStyleName): bool
224
    {
225
        return $this->sheetsVisibility[$sheetStyleName] ??
226
            true;
227
    }
228
}