Methods separation

This commit is contained in:
Javier 2023-08-06 10:47:12 -03:00
parent f0b003ff4f
commit 7ba94991dd
2 changed files with 246 additions and 165 deletions

View File

@ -1337,169 +1337,4 @@ class SimpleXLSXGen
return $c . strval($y);
}
/**
* Convert A1 range reference format to R1C1 range reference format
*
* @param string $range Range reference in A1 format
*
* @return array Range reference in R1C1 format as a 4-element integer array: [top-left row, top-left col, bottom-right row, bottom-right col]
*/
public static function range2coord($range)
{
$temp = explode(':', $range);
if (empty($temp[1])) return self::cell2coord($temp[0]);
return array_merge(self::cell2coord($temp[0]), self::cell2coord($temp[1]));
}
/**
* Convert R1C1 range reference format (row/col 1-based) to A1 range reference format
*
* Possible parameters:
* top-left row number, top-left column number, bottom-right row number, bottom-right column number
* [top-left row number, top-left column number], [bottom-right row number, bottom-right column number]
* [top-left row number, top-left column number, bottom-right row number, bottom-right column number]
*
* @return string Range reference in A1 format
*/
public static function coord2range($p1, ...$p)
{
switch (count($p)) {
case 0: $yi = $p1[0]; $xi = $p1[1]; $yf = $p1[2]; $xf = $p1[3]; break;
case 1: $yi = $p1[0]; $xi = $p1[1]; $yf = $p[0][0]; $xf = $p[0][1]; break;
default: $yi = $p1; $xi = $p[0]; $yf = $p[1]; $xf = $p[2]; break;
}
return self::coord2cell($yi, $xi) . ':' . self::coord2cell($yf, $xf);
}
/**
* Change or add cell style parameters without losing the original content or previously established styles
*
* @param string $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
* @param string|array $cell Cell to modify in A1 format (string) or R1C1 format (2 integer elements array)
* @param array $style Associative array with style attributes name/value pairs to change/add. You can use wildcard for
* specific borders to retain original value.
* @param integer $edge Optional. If border attribute specified in $style, this parameter indicates on which edge is applied.
* The unspecified edges retain their original value (or 'none' if not specified).
* Default: all edges. You can combine (adding or or-ing) the constants A_TOP, A_RIGHT, A_BOTTOM, A_LEFT
* @return void
*/
public static function setCellStyle(&$data, $cell, $style, $edge = self::A_DEFAULT)
{
if (is_string($cell)) $cell = self::cell2coord($cell);//A1 to R1C1 if needed
if (!array_key_exists($cell[0] - 1, $data) || !array_key_exists($cell[1] - 1, $data[$cell[0] - 1])) return;//quit if cell doesn't exist
if ($edge === self::A_DEFAULT) $edge = self::A_TOP + self::A_RIGHT + self::A_BOTTOM + self::A_LEFT;
//border processing
if (array_key_exists('border', $style)) {
//get original border as 4 element array
$oldBorder = self::getTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', 'border');
if (empty($oldBorder)) {
$oldBorder = ['none', 'none', 'none', 'none']; //if border not present in original cell, create a no-border attribute
} else {
$oldBorder = explode(' ', $oldBorder);
if (count($oldBorder) == 1) $oldBorder = [$oldBorder[0], $oldBorder[0], $oldBorder[0], $oldBorder[0]];
}
//get desired border as 4 element array
$border = explode(' ', $style['border']);
if (count($border) == 1) $border = [$border[0], $border[0], $border[0], $border[0]];
//new border copied from old one
$newBorder = $oldBorder;
//set border as indicated by $edge, except when $border has wildcard
if ($edge & self::A_TOP && $border[0] != '*') $newBorder[0] = $border[0];
if ($edge & self::A_RIGHT && $border[1] != '*') $newBorder[1] = $border[1];
if ($edge & self::A_BOTTOM && $border[2] != '*') $newBorder[2] = $border[2];
if ($edge & self::A_LEFT && $border[3] != '*') $newBorder[3] = $border[3];
//implode to the new border
$style['border'] = implode(' ', $newBorder);
}
$data[$cell[0] - 1][$cell[1] - 1] = self::setTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', $style);
}
/**
* Apply style to a sheet range. Border style are only applied to edges of range.
*
* @param array $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
* @param string|array $range Range to modify in A1 format (string) or R1C1 format (4-element integer array)
* @param array $style Associative array with style attributes name/value pairs to change
*
* @return void
*/
public static function setRangeStyle(&$data, $range, $style)
{
if (is_string($range)) $range = self::range2coord($range);
// 1 cell range
if (!isset($range[2])) return self::setCellStyle($data, $range, $style); //1 cell case
// Edge of range
self::setCellStyle($data, [$range[0], $range[1]], $style, self::A_TOP + self::A_LEFT); //top-left corner
for ($i = $range[1] + 1; $i < $range[3]; $i++) self::setCellStyle($data, [$range[0], $i], $style, self::A_TOP); //loop top edge (except first and last column)
self::setCellStyle($data, [$range[0], $range[3]], $style, self::A_TOP + self::A_RIGHT); //top-right corner
for ($i = $range[0] + 1; $i < $range[2]; $i++) self::setCellStyle($data, [$i, $range[3]], $style, self::A_RIGHT); //loop right edge (except top and bottom row)
self::setCellStyle($data, [$range[2], $range[3]], $style, self::A_BOTTOM + self::A_RIGHT); //bottom-right corner
for ($i = $range[3] - 1; $i > $range[1]; $i--) self::setCellStyle($data, [$range[2], $i], $style, self::A_BOTTOM); //loop bottom edge (except right and left column)
self::setCellStyle($data, [$range[2], $range[1]], $style, self::A_BOTTOM + self::A_LEFT); //bottom-left corner
for ($i = $range[2] - 1; $i > $range[0]; $i--) self::setCellStyle($data, [$i, $range[1]], $style, self::A_LEFT); //loop left edge (except bottom and top row)
// Internal cells
if (array_key_exists('border', $style)) unset($style['border']);
for ($i = $range[0] + 1; $i < $range[2]; $i++) {
for ($j = $range[1] + 1; $j < $range[3]; $j++) {
self::setCellStyle($data, [$i, $j], $style);
}
}
}
/**
* Get tag attributes from a HTML-style tag as an associative array with attributes name/value pairs.
* If $attribute specified, get value of the specified attribute.
*
* @param string $str Text to extract tag attributes
* @param string $tag The tag to look for
* @param string $attribute Optional. Get the value of specified attribute. Default: get all attributes name/value as an array.
*
* @return array|string Attributes name/value as an array ($attribute == '') or attribute value as a string ($attribute != '').
*/
public static function getTagAttributes($str, $tag, $attribute = '')
{
$attributes = ($attribute === '') ? [] : '';
if (preg_match("/<{$tag}\s+([^>]+)(?:>)/i", $str, $m)) {
$tagcontent = $m[1];
if (preg_match_all('/([a-z0-9\-_]+)=(["\'])(.*?)\2/si', $tagcontent, $m, PREG_SET_ORDER)) {
foreach ($m as $match) {
if ($attribute !== '') {
if ($attribute === $match[1]) return $match[3];
} else {
$attributes[$match[1]] = $match[3];
}
}
}
}
return $attributes;
}
/**
* Change HTML-style tag attributes. If tag doesn't exist, is created surrounding the previous content.
*
* @param string $str Text to modify
* @param string $tag Tag in text to modify
* @param array $attributes Associative array with attribute name/value pairs
*
* @return string Modified text
*/
public static function setTagAttributes($str, $tag, $attributes)
{
if (empty($attributes)) return $str;
$opening = "<{$tag}";
if (preg_match("/(<{$tag}\s*[^>]*)(>.*<\/{$tag}>)/i", $str, $m)) {
$stropen = $m[1];
} else {
$str = "<{$tag}>{$str}</{$tag}>";
$stropen = $opening;
}
//merge original attributes and specified attributes (replacing if necessary)
$attributes = array_merge(self::getTagAttributes($str, $tag), $attributes);
//add attributes to tag
foreach ($attributes as $attr => $value) $opening .= " {$attr}=\"{$value}\"";
//replace original tag (with attributes) with the new one
$str = substr_replace($str, $opening, strpos($str, $stropen), strlen($stropen));
return $str;
}
}

246
src/SimpleXLSXGenEx.php Normal file
View File

@ -0,0 +1,246 @@
<?php
/** @noinspection ReturnTypeCanBeDeclaredInspection */
/** @noinspection PhpMissingReturnTypeInspection */
/** @noinspection NullCoalescingOperatorCanBeUsedInspection */
/** @noinspection PhpIssetCanBeReplacedWithCoalesceInspection */
namespace Shuchkin;
/**
* Class SimpleXLSXGenEx
*
* Helper methods to manage sheet data. It needs SimpleXLSXGen class.
*
* @author Sergey Shuchkin <sergey.shuchkin@gmail.com>
*/
class SimpleXLSXGenEx
{
const EDGE_LEFT = 1;
const EDGE_RIGHT = 2;
const EDGE_TOP = 4;
const EDGE_BOTTOM = 8;
const EDGE_ALL = self::EDGE_LEFT + self::EDGE_RIGHT + self::EDGE_TOP + self::EDGE_BOTTOM;
/**
* Convert A1 cell reference format to R1C1 cell reference format (row/col number starting from 1)
*
* @param string $cell Cell reference in A1 format
*
* @return array Cell reference in R1C1 format as a 2-element integer array: [row (ie. y coord), col (ie. x coord)]
*/
public static function cell2coord($cell)
{
$x = $y = 0;
$lettercount = 0;
$cell = str_replace([' ', '\t', '\r', '\n', '\v', '\0', '$'], '', $cell);
if (empty($cell)) {
return [];
}
$cell = strtoupper($cell);
for ($i = 0, $len = strlen($cell); $i < $len; $i++) {
if ($cell[$i] >= 'A' && $cell[$i] <= 'Z') {
$lettercount++;
}
}
if ($lettercount > 0) {
$x = ord($cell[$lettercount - 1]) - ord('A');
$e = 1;
for ($i = $lettercount - 2; $i >= 0; $i--) {
$x += (ord($cell[$i]) - ord('A') + 1) * (26 ** $e);
$e++;
}
$x++; //to make 1-based
}
if ($lettercount < strlen($cell)) {
$y = ((int)substr($cell, $lettercount));
}
return [$y, $x];
}
/**
* Convert R1C1 cell reference format (row/col 1-based) to A1 cell reference format
*
* @param integer $y Row number (starting from 1) or a 2-element integer array with cell reference.
* @param integer $x Optional. Column number (starting from 1). Not used if $y is an array.
*
* @return string Cell reference in A1 format
*/
public static function coord2cell($y, $x = null)
{
if (is_array($y)) {
$x = $y[1];
$y = $y[0];
}
$c = '';
for ($i = $x - 1; $i >= 0; $i = ((int)($i / 26)) - 1) {
$c = chr(ord('A') + $i % 26) . $c;
}
return $c . strval($y);
}
/**
* Convert A1 range reference format to R1C1 range reference format
*
* @param string $range Range reference in A1 format
*
* @return array Range reference in R1C1 format as a 4-element integer array: [top-left row, top-left col, bottom-right row, bottom-right col]
*/
public static function range2coord($range)
{
$temp = explode(':', $range);
if (empty($temp[1])) return self::cell2coord($temp[0]);
return array_merge(self::cell2coord($temp[0]), self::cell2coord($temp[1]));
}
/**
* Convert R1C1 range reference format (row/col 1-based) to A1 range reference format
*
* Possible parameters:
* top-left row number, top-left column number, bottom-right row number, bottom-right column number
* [top-left row number, top-left column number], [bottom-right row number, bottom-right column number]
* [top-left row number, top-left column number, bottom-right row number, bottom-right column number]
*
* @return string Range reference in A1 format
*/
public static function coord2range($p1, ...$p)
{
switch (count($p)) {
case 0: $yi = $p1[0]; $xi = $p1[1]; $yf = $p1[2]; $xf = $p1[3]; break;
case 1: $yi = $p1[0]; $xi = $p1[1]; $yf = $p[0][0]; $xf = $p[0][1]; break;
default: $yi = $p1; $xi = $p[0]; $yf = $p[1]; $xf = $p[2]; break;
}
return self::coord2cell($yi, $xi) . ':' . self::coord2cell($yf, $xf);
}
/**
* Change or add cell style parameters without losing the original content or previously established styles
*
* @param string $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
* @param string|array $cell Cell to modify in A1 format (string) or R1C1 format (2 integer elements array)
* @param array $style Associative array with style attributes name/value pairs to change/add. You can use wildcard for
* specific borders to retain original value.
* @param integer $edge Optional. If border attribute specified in $style, this parameter indicates on which edge is applied.
* The unspecified edges retain their original value (or 'none' if not specified).
* Default: all edges. You can combine (adding or or-ing) the constants EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM, EDGE_LEFT
* @return void
*/
public static function setCellStyle(&$data, $cell, $style, $edge = self::EDGE_ALL)
{
if (is_string($cell)) $cell = self::cell2coord($cell);//A1 to R1C1 if needed
if (!array_key_exists($cell[0] - 1, $data) || !array_key_exists($cell[1] - 1, $data[$cell[0] - 1])) return;//quit if cell doesn't exist
//border processing
if (array_key_exists('border', $style)) {
//get original border as 4 element array
$oldBorder = self::getTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', 'border');
if (empty($oldBorder)) {
$oldBorder = ['none', 'none', 'none', 'none']; //if border not present in original cell, create a no-border attribute
} else {
$oldBorder = explode(' ', $oldBorder);
if (count($oldBorder) == 1) $oldBorder = [$oldBorder[0], $oldBorder[0], $oldBorder[0], $oldBorder[0]];
}
//get desired border as 4 element array
$border = explode(' ', $style['border']);
if (count($border) == 1) $border = [$border[0], $border[0], $border[0], $border[0]];
//new border copied from old one
$newBorder = $oldBorder;
//set border as indicated by $edge, except when $border has wildcard
if ($edge & self::EDGE_TOP && $border[0] != '*') $newBorder[0] = $border[0];
if ($edge & self::EDGE_RIGHT && $border[1] != '*') $newBorder[1] = $border[1];
if ($edge & self::EDGE_BOTTOM && $border[2] != '*') $newBorder[2] = $border[2];
if ($edge & self::EDGE_LEFT && $border[3] != '*') $newBorder[3] = $border[3];
//implode to the new border
$style['border'] = implode(' ', $newBorder);
}
$data[$cell[0] - 1][$cell[1] - 1] = self::setTagAttributes($data[$cell[0] - 1][$cell[1] - 1], 'style', $style);
}
/**
* Apply style to a sheet range. Border style are only applied to edges of range.
*
* @param array $data Data matrix (2-dim 0-based array) where style will be modified. Passed by reference.
* @param string|array $range Range to modify in A1 format (string) or R1C1 format (4-element integer array)
* @param array $style Associative array with style attributes name/value pairs to change
*
* @return void
*/
public static function setRangeStyle(&$data, $range, $style)
{
if (is_string($range)) $range = self::range2coord($range);
// 1 cell range
if (!isset($range[2])) return self::setCellStyle($data, $range, $style); //1 cell case
// Edge of range
self::setCellStyle($data, [$range[0], $range[1]], $style, self::EDGE_TOP + self::EDGE_LEFT); //top-left corner
for ($i = $range[1] + 1; $i < $range[3]; $i++) self::setCellStyle($data, [$range[0], $i], $style, self::EDGE_TOP); //loop top edge (except first and last column)
self::setCellStyle($data, [$range[0], $range[3]], $style, self::EDGE_TOP + self::EDGE_RIGHT); //top-right corner
for ($i = $range[0] + 1; $i < $range[2]; $i++) self::setCellStyle($data, [$i, $range[3]], $style, self::EDGE_RIGHT); //loop right edge (except top and bottom row)
self::setCellStyle($data, [$range[2], $range[3]], $style, self::EDGE_BOTTOM + self::EDGE_RIGHT); //bottom-right corner
for ($i = $range[3] - 1; $i > $range[1]; $i--) self::setCellStyle($data, [$range[2], $i], $style, self::EDGE_BOTTOM); //loop bottom edge (except right and left column)
self::setCellStyle($data, [$range[2], $range[1]], $style, self::EDGE_BOTTOM + self::EDGE_LEFT); //bottom-left corner
for ($i = $range[2] - 1; $i > $range[0]; $i--) self::setCellStyle($data, [$i, $range[1]], $style, self::EDGE_LEFT); //loop left edge (except bottom and top row)
// Internal cells
if (array_key_exists('border', $style)) unset($style['border']);
for ($i = $range[0] + 1; $i < $range[2]; $i++) {
for ($j = $range[1] + 1; $j < $range[3]; $j++) {
self::setCellStyle($data, [$i, $j], $style);
}
}
}
/**
* Get tag attributes from a HTML-style tag as an associative array with attributes name/value pairs.
* If $attribute specified, get value of the specified attribute.
*
* @param string $str Text to extract tag attributes
* @param string $tag The tag to look for
* @param string $attribute Optional. Get the value of specified attribute. Default: get all attributes name/value as an array.
*
* @return array|string Attributes name/value as an array ($attribute == '') or attribute value as a string ($attribute != '').
*/
public static function getTagAttributes($str, $tag, $attribute = '')
{
$attributes = ($attribute === '') ? [] : '';
if (preg_match("/<{$tag}\s+([^>]+)(?:>)/i", $str, $m)) {
$tagcontent = $m[1];
if (preg_match_all('/([a-z0-9\-_]+)=(["\'])(.*?)\2/si', $tagcontent, $m, PREG_SET_ORDER)) {
foreach ($m as $match) {
if ($attribute !== '') {
if ($attribute === $match[1]) return $match[3];
} else {
$attributes[$match[1]] = $match[3];
}
}
}
}
return $attributes;
}
/**
* Change HTML-style tag attributes. If tag doesn't exist, is created surrounding the previous content.
*
* @param string $str Text to modify
* @param string $tag Tag in text to modify
* @param array $attributes Associative array with attribute name/value pairs
*
* @return string Modified text
*/
public static function setTagAttributes($str, $tag, $attributes)
{
if (empty($attributes)) return $str;
$opening = "<{$tag}";
if (preg_match("/(<{$tag}\s*[^>]*)(>.*<\/{$tag}>)/i", $str, $m)) {
$stropen = $m[1];
} else {
$str = "<{$tag}>{$str}</{$tag}>";
$stropen = $opening;
}
//merge original attributes and specified attributes (replacing if necessary)
$attributes = array_merge(self::getTagAttributes($str, $tag), $attributes);
//add attributes to tag
foreach ($attributes as $attr => $value) $opening .= " {$attr}=\"{$value}\"";
//replace original tag (with attributes) with the new one
$str = substr_replace($str, $opening, strpos($str, $stropen), strlen($stropen));
return $str;
}
}