Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 39 and 311]

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