Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

   1  <?php
   2  
   3  namespace Box\Spout\Reader\ODS\Helper;
   4  
   5  use Box\Spout\Reader\Exception\InvalidValueException;
   6  
   7  /**
   8   * Class CellValueFormatter
   9   * This class provides helper functions to format cell values
  10   */
  11  class CellValueFormatter
  12  {
  13      /** Definition of all possible cell types */
  14      const CELL_TYPE_STRING = 'string';
  15      const CELL_TYPE_FLOAT = 'float';
  16      const CELL_TYPE_BOOLEAN = 'boolean';
  17      const CELL_TYPE_DATE = 'date';
  18      const CELL_TYPE_TIME = 'time';
  19      const CELL_TYPE_CURRENCY = 'currency';
  20      const CELL_TYPE_PERCENTAGE = 'percentage';
  21      const CELL_TYPE_VOID = 'void';
  22  
  23      /** Definition of XML nodes names used to parse data */
  24      const XML_NODE_P = 'p';
  25      const XML_NODE_S = 'text:s';
  26      const XML_NODE_A = 'text:a';
  27      const XML_NODE_SPAN = 'text:span';
  28  
  29      /** Definition of XML attributes used to parse data */
  30      const XML_ATTRIBUTE_TYPE = 'office:value-type';
  31      const XML_ATTRIBUTE_VALUE = 'office:value';
  32      const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
  33      const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
  34      const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
  35      const XML_ATTRIBUTE_CURRENCY = 'office:currency';
  36      const XML_ATTRIBUTE_C = 'text:c';
  37  
  38      /** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
  39      protected $shouldFormatDates;
  40  
  41      /** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
  42      protected $escaper;
  43  
  44      /**
  45       * @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
  46       * @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
  47       */
  48      public function __construct($shouldFormatDates, $escaper)
  49      {
  50          $this->shouldFormatDates = $shouldFormatDates;
  51          $this->escaper = $escaper;
  52      }
  53  
  54      /**
  55       * Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
  56       * @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
  57       *
  58       * @param \DOMNode $node
  59       * @throws InvalidValueException If the node value is not valid
  60       * @return string|int|float|bool|\DateTime|\DateInterval The value associated with the cell, empty string if cell's type is void/undefined
  61       */
  62      public function extractAndFormatNodeValue($node)
  63      {
  64          $cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE);
  65  
  66          switch ($cellType) {
  67              case self::CELL_TYPE_STRING:
  68                  return $this->formatStringCellValue($node);
  69              case self::CELL_TYPE_FLOAT:
  70                  return $this->formatFloatCellValue($node);
  71              case self::CELL_TYPE_BOOLEAN:
  72                  return $this->formatBooleanCellValue($node);
  73              case self::CELL_TYPE_DATE:
  74                  return $this->formatDateCellValue($node);
  75              case self::CELL_TYPE_TIME:
  76                  return $this->formatTimeCellValue($node);
  77              case self::CELL_TYPE_CURRENCY:
  78                  return $this->formatCurrencyCellValue($node);
  79              case self::CELL_TYPE_PERCENTAGE:
  80                  return $this->formatPercentageCellValue($node);
  81              case self::CELL_TYPE_VOID:
  82              default:
  83                  return '';
  84          }
  85      }
  86  
  87      /**
  88       * Returns the cell String value.
  89       *
  90       * @param \DOMNode $node
  91       * @return string The value associated with the cell
  92       */
  93      protected function formatStringCellValue($node)
  94      {
  95          $pNodeValues = [];
  96          $pNodes = $node->getElementsByTagName(self::XML_NODE_P);
  97  
  98          foreach ($pNodes as $pNode) {
  99              $currentPValue = '';
 100  
 101              foreach ($pNode->childNodes as $childNode) {
 102                  if ($childNode instanceof \DOMText) {
 103                      $currentPValue .= $childNode->nodeValue;
 104                  } elseif ($childNode->nodeName === self::XML_NODE_S) {
 105                      $spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
 106                      $numSpaces = (!empty($spaceAttribute)) ? (int) $spaceAttribute : 1;
 107                      $currentPValue .= str_repeat(' ', $numSpaces);
 108                  } elseif ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
 109                      $currentPValue .= $childNode->nodeValue;
 110                  }
 111              }
 112  
 113              $pNodeValues[] = $currentPValue;
 114          }
 115  
 116          $escapedCellValue = implode("\n", $pNodeValues);
 117          $cellValue = $this->escaper->unescape($escapedCellValue);
 118  
 119          return $cellValue;
 120      }
 121  
 122      /**
 123       * Returns the cell Numeric value from the given node.
 124       *
 125       * @param \DOMNode $node
 126       * @return int|float The value associated with the cell
 127       */
 128      protected function formatFloatCellValue($node)
 129      {
 130          $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
 131  
 132          $nodeIntValue = (int) $nodeValue;
 133          $nodeFloatValue = (float) $nodeValue;
 134          $cellValue = ((float) $nodeIntValue === $nodeFloatValue) ? $nodeIntValue : $nodeFloatValue;
 135  
 136          return $cellValue;
 137      }
 138  
 139      /**
 140       * Returns the cell Boolean value from the given node.
 141       *
 142       * @param \DOMNode $node
 143       * @return bool The value associated with the cell
 144       */
 145      protected function formatBooleanCellValue($node)
 146      {
 147          $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_BOOLEAN_VALUE);
 148  
 149          return (bool) $nodeValue;
 150      }
 151  
 152      /**
 153       * Returns the cell Date value from the given node.
 154       *
 155       * @param \DOMNode $node
 156       * @throws InvalidValueException If the value is not a valid date
 157       * @return \DateTime|string The value associated with the cell
 158       */
 159      protected function formatDateCellValue($node)
 160      {
 161          // The XML node looks like this:
 162          // <table:table-cell calcext:value-type="date" office:date-value="2016-05-19T16:39:00" office:value-type="date">
 163          //   <text:p>05/19/16 04:39 PM</text:p>
 164          // </table:table-cell>
 165  
 166          if ($this->shouldFormatDates) {
 167              // The date is already formatted in the "p" tag
 168              $nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
 169              $cellValue = $nodeWithValueAlreadyFormatted->nodeValue;
 170          } else {
 171              // otherwise, get it from the "date-value" attribute
 172              $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_DATE_VALUE);
 173              try {
 174                  $cellValue = new \DateTime($nodeValue);
 175              } catch (\Exception $e) {
 176                  throw new InvalidValueException($nodeValue);
 177              }
 178          }
 179  
 180          return $cellValue;
 181      }
 182  
 183      /**
 184       * Returns the cell Time value from the given node.
 185       *
 186       * @param \DOMNode $node
 187       * @throws InvalidValueException If the value is not a valid time
 188       * @return \DateInterval|string The value associated with the cell
 189       */
 190      protected function formatTimeCellValue($node)
 191      {
 192          // The XML node looks like this:
 193          // <table:table-cell calcext:value-type="time" office:time-value="PT13H24M00S" office:value-type="time">
 194          //   <text:p>01:24:00 PM</text:p>
 195          // </table:table-cell>
 196  
 197          if ($this->shouldFormatDates) {
 198              // The date is already formatted in the "p" tag
 199              $nodeWithValueAlreadyFormatted = $node->getElementsByTagName(self::XML_NODE_P)->item(0);
 200              $cellValue = $nodeWithValueAlreadyFormatted->nodeValue;
 201          } else {
 202              // otherwise, get it from the "time-value" attribute
 203              $nodeValue = $node->getAttribute(self::XML_ATTRIBUTE_TIME_VALUE);
 204              try {
 205                  $cellValue = new \DateInterval($nodeValue);
 206              } catch (\Exception $e) {
 207                  throw new InvalidValueException($nodeValue);
 208              }
 209          }
 210  
 211          return $cellValue;
 212      }
 213  
 214      /**
 215       * Returns the cell Currency value from the given node.
 216       *
 217       * @param \DOMNode $node
 218       * @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
 219       */
 220      protected function formatCurrencyCellValue($node)
 221      {
 222          $value = $node->getAttribute(self::XML_ATTRIBUTE_VALUE);
 223          $currency = $node->getAttribute(self::XML_ATTRIBUTE_CURRENCY);
 224  
 225          return "$value $currency";
 226      }
 227  
 228      /**
 229       * Returns the cell Percentage value from the given node.
 230       *
 231       * @param \DOMNode $node
 232       * @return int|float The value associated with the cell
 233       */
 234      protected function formatPercentageCellValue($node)
 235      {
 236          // percentages are formatted like floats
 237          return $this->formatFloatCellValue($node);
 238      }
 239  }