Differences Between: [Versions 402 and 403]
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 string $filePath; 39 40 private Options $options; 41 42 /** @var XMLReader The XMLReader object that will help read sheet's XML data */ 43 private XMLReader $xmlReader; 44 45 /** @var ODS Used to unescape XML data */ 46 private 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 ?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 \OpenSpout\Common\Exception\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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body