/** * 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; }
/** * 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); }