/**
  * Test that column names (letters) are converted to their positions (numbers) properly.
  *
  * @dataProvider data_letter_to_number
  *
  * @since 1.1.0
  *
  * @param string $letter Letter to convert.
  * @param int    $number Conversion result number.
  */
 public function test_letter_to_number($letter, $number)
 {
     $this->assertSame($number, TablePress::letter_to_number($letter));
 }
 /**
  * Parse and evaluate the content of a cell
  *
  * @since 1.0.0
  *
  * @param string $content Content of a cell
  * @param array $parents List of cells that depend on this cell (to prevent circle references)
  * @return string Result of the parsing/evaluation
  */
 protected function _evaluate_cell($content, array $parents = array())
 {
     if ('' == $content || '=' == $content || '=' != $content[0]) {
         return $content;
     }
     $content = substr($content, 1);
     // Support putting formulas in strings, like =Total: {A3+A4}
     $expressions = array();
     if (preg_match_all('#{(.+?)}#', $content, $expressions, PREG_SET_ORDER)) {
         $formula_in_string = true;
     } else {
         $formula_in_string = false;
         $expressions[] = array($content, $content);
         // fill array so that it has the same structure as if it came from preg_match_all()
     }
     foreach ($expressions as $expression) {
         $orig_expression = $expression[0];
         $expression = $expression[1];
         $replaced_references = $replaced_ranges = array();
         // remove all whitespace characters
         $expression = preg_replace('#[\\r\\n\\t ]#', '', $expression);
         // expand cell ranges (like A3:A6) to a list of single cells (like A3,A4,A5,A6)
         if (preg_match_all('#([A-Z]+)([0-9]+):([A-Z]+)([0-9]+)#', $expression, $referenced_cell_ranges, PREG_SET_ORDER)) {
             foreach ($referenced_cell_ranges as $cell_range) {
                 if (in_array($cell_range[0], $replaced_ranges, true)) {
                     continue;
                 }
                 $replaced_ranges[] = $cell_range[0];
                 if (isset($this->known_ranges[$cell_range[0]])) {
                     $expression = preg_replace('#(?<![A-Z])' . preg_quote($cell_range[0], '#') . '(?![0-9])#', $this->known_ranges[$cell_range[0]], $expression);
                     continue;
                 }
                 // no -1 necessary for this transformation, as we don't actually access the table
                 $first_col = TablePress::letter_to_number($cell_range[1]);
                 $first_row = $cell_range[2];
                 $last_col = TablePress::letter_to_number($cell_range[3]);
                 $last_row = $cell_range[4];
                 $col_start = min($first_col, $last_col);
                 $col_end = max($first_col, $last_col) + 1;
                 // +1 for loop below
                 $row_start = min($first_row, $last_row);
                 $row_end = max($first_row, $last_row) + 1;
                 // +1 for loop below
                 $cell_list = array();
                 for ($col = $col_start; $col < $col_end; $col++) {
                     for ($row = $row_start; $row < $row_end; $row++) {
                         $column = TablePress::number_to_letter($col);
                         $cell_list[] = "{$column}{$row}";
                     }
                 }
                 $cell_list = implode(',', $cell_list);
                 $expression = preg_replace('#(?<![A-Z])' . preg_quote($cell_range[0], '#') . '(?![0-9])#', $cell_list, $expression);
                 $this->known_ranges[$cell_range[0]] = $cell_list;
             }
         }
         // parse and evaluate single cell references (like A3 or XY312), while prohibiting circle references
         if (preg_match_all('#([A-Z]+)([0-9]+)#', $expression, $referenced_cells, PREG_SET_ORDER)) {
             foreach ($referenced_cells as $cell_reference) {
                 if (in_array($cell_reference[0], $parents, true)) {
                     return '!ERROR! Circle Reference';
                 }
                 if (in_array($cell_reference[0], $replaced_references, true)) {
                     continue;
                 }
                 $replaced_references[] = $cell_reference[0];
                 $ref_col = TablePress::letter_to_number($cell_reference[1]) - 1;
                 $ref_row = $cell_reference[2] - 1;
                 if (!isset($this->table['data'][$ref_row]) || !isset($this->table['data'][$ref_row][$ref_col])) {
                     return "!ERROR! Cell {$cell_reference[0]} does not exist";
                 }
                 $ref_parents = $parents;
                 $ref_parents[] = $cell_reference[0];
                 $result = $this->table['data'][$ref_row][$ref_col] = $this->_evaluate_cell($this->table['data'][$ref_row][$ref_col], $ref_parents);
                 // Bail if there was an error already
                 if (false !== strpos($result, '!ERROR!')) {
                     return $result;
                 }
                 // remove all whitespace characters
                 $result = preg_replace('#[\\r\\n\\t ]#', '', $result);
                 // Treat empty cells as 0
                 if ('' == $result) {
                     $result = 0;
                 }
                 // Bail if the cell does not result in a number (meaning it was a number or expression before being evaluated)
                 if (!is_numeric($result)) {
                     return "!ERROR! {$cell_reference[0]} does not contain a number or expression";
                 }
                 $expression = preg_replace('#(?<![A-Z])' . $cell_reference[0] . '(?![0-9])#', $result, $expression);
             }
         }
         $result = $this->_evaluate_math_expression($expression);
         // Support putting formulas in strings, like =Total: {A3+A4}
         if ($formula_in_string) {
             $content = str_replace($orig_expression, $result, $content);
         } else {
             $content = $result;
         }
     }
     return $content;
 }
Beispiel #3
0
 /**
  * Remove all cells from the data set that shall not be rendered, because they are hidden.
  *
  * @since 1.0.0
  */
 protected function _prepare_render_data()
 {
     $orig_table = $this->table;
     $num_rows = count($this->table['data']);
     $num_columns = $num_rows > 0 ? count($this->table['data'][0]) : 0;
     // Evaluate show/hide_rows/columns parameters.
     $actions = array('show', 'hide');
     $elements = array('rows', 'columns');
     foreach ($actions as $action) {
         foreach ($elements as $element) {
             if (empty($this->render_options["{$action}_{$element}"])) {
                 $this->render_options["{$action}_{$element}"] = array();
                 continue;
             }
             // Add all rows/columns to array if "all" value set for one of the four parameters.
             if ('all' === $this->render_options["{$action}_{$element}"]) {
                 $this->render_options["{$action}_{$element}"] = range(0, ${'num_' . $element} - 1);
                 continue;
             }
             // We have a list of rows/columns (possibly with ranges in it).
             $this->render_options["{$action}_{$element}"] = explode(',', $this->render_options["{$action}_{$element}"]);
             // Support for ranges like 3-6 or A-BA.
             $range_cells = array();
             foreach ($this->render_options["{$action}_{$element}"] as $key => $value) {
                 $range_dash = strpos($value, '-');
                 if (false !== $range_dash) {
                     unset($this->render_options["{$action}_{$element}"][$key]);
                     $start = substr($value, 0, $range_dash);
                     if (!is_numeric($start)) {
                         $start = TablePress::letter_to_number($start);
                     }
                     $end = substr($value, $range_dash + 1);
                     if (!is_numeric($end)) {
                         $end = TablePress::letter_to_number($end);
                     }
                     $current_range = range($start, $end);
                     $range_cells = array_merge($range_cells, $current_range);
                 }
             }
             $this->render_options["{$action}_{$element}"] = array_merge($this->render_options["{$action}_{$element}"], $range_cells);
             /*
              * Parse single letters and change from regular numbering to zero-based numbering,
              * as rows/columns are indexed from 0 internally, but from 1 externally
              */
             foreach ($this->render_options["{$action}_{$element}"] as $key => $value) {
                 if (!is_numeric($value)) {
                     $value = TablePress::letter_to_number($value);
                 }
                 $this->render_options["{$action}_{$element}"][$key] = (int) $value - 1;
             }
             // Remove duplicate entries and sort the array.
             $this->render_options["{$action}_{$element}"] = array_unique($this->render_options["{$action}_{$element}"]);
             sort($this->render_options["{$action}_{$element}"], SORT_NUMERIC);
         }
     }
     // Load information about hidden rows and columns.
     // Get indexes of hidden rows (array value of 0).
     $hidden_rows = array_keys($this->table['visibility']['rows'], 0);
     $hidden_rows = array_merge($hidden_rows, $this->render_options['hide_rows']);
     $hidden_rows = array_diff($hidden_rows, $this->render_options['show_rows']);
     // Get indexes of hidden columns (array value of 0).
     $hidden_columns = array_keys($this->table['visibility']['columns'], 0);
     $hidden_columns = array_merge($hidden_columns, $this->render_options['hide_columns']);
     $hidden_columns = array_merge(array_diff($hidden_columns, $this->render_options['show_columns']));
     // Remove hidden rows and re-index.
     foreach ($hidden_rows as $row_idx) {
         unset($this->table['data'][$row_idx]);
     }
     $this->table['data'] = array_merge($this->table['data']);
     // Remove hidden columns and re-index.
     foreach ($this->table['data'] as $row_idx => $row) {
         foreach ($hidden_columns as $col_idx) {
             unset($row[$col_idx]);
         }
         $this->table['data'][$row_idx] = array_merge($row);
     }
     /**
      * Filter the table after processing the table visibility information.
      *
      * @since 1.0.0
      *
      * @param array $table          The processed table.
      * @param array $orig_table     The unprocessed table.
      * @param array $render_options The render options for the table.
      */
     $this->table = apply_filters('tablepress_table_render_data', $this->table, $orig_table, $this->render_options);
 }