AutorÃa | Ultima modificación | Ver Log |
<?phpdeclare(strict_types=1);namespace OpenSpout\Reader\ODS;use DOMElement;use OpenSpout\Common\Exception\IOException;use OpenSpout\Common\Helper\Escaper\ODS;use OpenSpout\Reader\Common\XMLProcessor;use OpenSpout\Reader\Exception\XMLProcessingException;use OpenSpout\Reader\ODS\Helper\CellValueFormatter;use OpenSpout\Reader\ODS\Helper\SettingsHelper;use OpenSpout\Reader\SheetIteratorInterface;use OpenSpout\Reader\Wrapper\XMLReader;/*** @implements SheetIteratorInterface<Sheet>*/final class SheetIterator implements SheetIteratorInterface{public const CONTENT_XML_FILE_PATH = 'content.xml';public const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';/*** Definition of XML nodes name and attribute used to parse sheet data.*/public const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';public const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';public const XML_NODE_TABLE = 'table:table';public const XML_ATTRIBUTE_STYLE_NAME = 'style:name';public const XML_ATTRIBUTE_TABLE_NAME = 'table:name';public const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';public const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';/** @var string Path of the file to be read */private readonly string $filePath;private readonly Options $options;/** @var XMLReader The XMLReader object that will help read sheet's XML data */private readonly XMLReader $xmlReader;/** @var ODS Used to unescape XML data */private readonly ODS $escaper;/** @var bool Whether there are still at least a sheet to be read */private bool $hasFoundSheet;/** @var int The index of the sheet being read (zero-based) */private int $currentSheetIndex;/** @var string The name of the sheet that was defined as active */private readonly ?string $activeSheetName;/** @var array<string, bool> Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE] */private array $sheetsVisibility;public function __construct(string $filePath,Options $options,ODS $escaper,SettingsHelper $settingsHelper) {$this->filePath = $filePath;$this->options = $options;$this->xmlReader = new XMLReader();$this->escaper = $escaper;$this->activeSheetName = $settingsHelper->getActiveSheetName($filePath);}/*** Rewind the Iterator to the first element.** @see http://php.net/manual/en/iterator.rewind.php** @throws IOException If unable to open the XML file containing sheets' data*/public function rewind(): void{$this->xmlReader->close();if (false === $this->xmlReader->openFileInZip($this->filePath, self::CONTENT_XML_FILE_PATH)) {$contentXmlFilePath = $this->filePath.'#'.self::CONTENT_XML_FILE_PATH;throw new IOException("Could not open \"{$contentXmlFilePath}\".");}try {$this->sheetsVisibility = $this->readSheetsVisibility();$this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);} catch (XMLProcessingException $exception) {throw new IOException("The content.xml file is invalid and cannot be read. [{$exception->getMessage()}]");}$this->currentSheetIndex = 0;}/*** Checks if current position is valid.** @see http://php.net/manual/en/iterator.valid.php*/public function valid(): bool{$valid = $this->hasFoundSheet;if (!$valid) {$this->xmlReader->close();}return $valid;}/*** Move forward to next element.** @see http://php.net/manual/en/iterator.next.php*/public function next(): void{$this->hasFoundSheet = $this->xmlReader->readUntilNodeFound(self::XML_NODE_TABLE);if ($this->hasFoundSheet) {++$this->currentSheetIndex;}}/*** Return the current element.** @see http://php.net/manual/en/iterator.current.php*/public function current(): Sheet{$escapedSheetName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_NAME);\assert(null !== $escapedSheetName);$sheetName = $this->escaper->unescape($escapedSheetName);$isSheetActive = $this->isSheetActive($sheetName, $this->currentSheetIndex, $this->activeSheetName);$sheetStyleName = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_TABLE_STYLE_NAME);\assert(null !== $sheetStyleName);$isSheetVisible = $this->isSheetVisible($sheetStyleName);return new Sheet(new RowIterator($this->options,new CellValueFormatter($this->options->SHOULD_FORMAT_DATES, new ODS()),new XMLProcessor($this->xmlReader)),$this->currentSheetIndex,$sheetName,$isSheetActive,$isSheetVisible);}/*** Return the key of the current element.** @see http://php.net/manual/en/iterator.key.php*/public function key(): int{return $this->currentSheetIndex + 1;}/*** Extracts the visibility of the sheets.** @return array<string, bool> Associative array [STYLE_NAME] => [IS_SHEET_VISIBLE]*/private function readSheetsVisibility(): array{$sheetsVisibility = [];$this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);$automaticStylesNode = $this->xmlReader->expand();\assert($automaticStylesNode instanceof DOMElement);$tableStyleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES);foreach ($tableStyleNodes as $tableStyleNode) {$isSheetVisible = ('false' !== $tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY));$parentStyleNode = $tableStyleNode->parentNode;\assert($parentStyleNode instanceof DOMElement);$styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);$sheetsVisibility[$styleName] = $isSheetVisible;}return $sheetsVisibility;}/*** Returns whether the current sheet was defined as the active one.** @param string $sheetName Name of the current sheet* @param int $sheetIndex Index of the current sheet* @param null|string $activeSheetName Name of the sheet that was defined as active or NULL if none defined** @return bool Whether the current sheet was defined as the active one*/private function isSheetActive(string $sheetName, int $sheetIndex, ?string $activeSheetName): bool{// The given sheet is active if its name matches the defined active sheet's name// or if no information about the active sheet was found, it defaults to the first sheet.return(null === $activeSheetName && 0 === $sheetIndex)|| ($activeSheetName === $sheetName);}/*** Returns whether the current sheet is visible.** @param string $sheetStyleName Name of the sheet style** @return bool Whether the current sheet is visible*/private function isSheetVisible(string $sheetStyleName): bool{return $this->sheetsVisibility[$sheetStyleName] ??true;}}