/home/mip/mip/public/vendor/laravel-filemanager/files/folder-1/821668/phpoffice.tar
phpspreadsheet/LICENSE 0000644 00000002067 15243417764 0010612 0 ustar 00 MIT License
Copyright (c) 2019 PhpSpreadsheet Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
phpspreadsheet/src/PhpSpreadsheet/NamedRange.php 0000644 00000002342 15243417764 0016021 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class NamedRange extends DefinedName
{
/**
* Create a new Named Range.
*/
public function __construct(
string $name,
?Worksheet $worksheet = null,
string $range = 'A1',
bool $localOnly = false,
?Worksheet $scope = null
) {
if ($worksheet === null && $scope === null) {
throw new Exception('You must specify a worksheet or a scope for a Named Range');
}
parent::__construct($name, $worksheet, $range, $localOnly, $scope);
}
/**
* Get the range value.
*/
public function getRange(): string
{
return $this->value;
}
/**
* Set the range value.
*/
public function setRange(string $range): self
{
if (!empty($range)) {
$this->value = $range;
}
return $this;
}
public function getCellsInRange(): array
{
$range = $this->value;
if (substr($range, 0, 1) === '=') {
$range = substr($range, 1);
}
return Coordinate::extractAllCellReferencesInRange($range);
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php 0000644 00000002013 15243417764 0016607 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
class Handler
{
/** @var string */
private static $invalidHex = 'Y';
// A bunch of methods to show that we continue
// to capture messages even using PhpUnit 10.
public static function suppressed(): bool
{
return @trigger_error('hello');
}
public static function deprecated(): string
{
return (string) hexdec(self::$invalidHex);
}
public static function notice(string $value): void
{
date_default_timezone_set($value);
}
public static function warning(): bool
{
return file_get_contents(__FILE__ . 'noexist') !== false;
}
public static function userDeprecated(): bool
{
return trigger_error('hello', E_USER_DEPRECATED);
}
public static function userNotice(): bool
{
return trigger_error('userNotice', E_USER_NOTICE);
}
public static function userWarning(): bool
{
return trigger_error('userWarning', E_USER_WARNING);
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php 0000644 00000062636 15243417764 0016157 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMText;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Font;
class Html
{
private const COLOUR_MAP = [
'aliceblue' => 'f0f8ff',
'antiquewhite' => 'faebd7',
'antiquewhite1' => 'ffefdb',
'antiquewhite2' => 'eedfcc',
'antiquewhite3' => 'cdc0b0',
'antiquewhite4' => '8b8378',
'aqua' => '00ffff',
'aquamarine1' => '7fffd4',
'aquamarine2' => '76eec6',
'aquamarine4' => '458b74',
'azure1' => 'f0ffff',
'azure2' => 'e0eeee',
'azure3' => 'c1cdcd',
'azure4' => '838b8b',
'beige' => 'f5f5dc',
'bisque1' => 'ffe4c4',
'bisque2' => 'eed5b7',
'bisque3' => 'cdb79e',
'bisque4' => '8b7d6b',
'black' => '000000',
'blanchedalmond' => 'ffebcd',
'blue' => '0000ff',
'blue1' => '0000ff',
'blue2' => '0000ee',
'blue4' => '00008b',
'blueviolet' => '8a2be2',
'brown' => 'a52a2a',
'brown1' => 'ff4040',
'brown2' => 'ee3b3b',
'brown3' => 'cd3333',
'brown4' => '8b2323',
'burlywood' => 'deb887',
'burlywood1' => 'ffd39b',
'burlywood2' => 'eec591',
'burlywood3' => 'cdaa7d',
'burlywood4' => '8b7355',
'cadetblue' => '5f9ea0',
'cadetblue1' => '98f5ff',
'cadetblue2' => '8ee5ee',
'cadetblue3' => '7ac5cd',
'cadetblue4' => '53868b',
'chartreuse1' => '7fff00',
'chartreuse2' => '76ee00',
'chartreuse3' => '66cd00',
'chartreuse4' => '458b00',
'chocolate' => 'd2691e',
'chocolate1' => 'ff7f24',
'chocolate2' => 'ee7621',
'chocolate3' => 'cd661d',
'coral' => 'ff7f50',
'coral1' => 'ff7256',
'coral2' => 'ee6a50',
'coral3' => 'cd5b45',
'coral4' => '8b3e2f',
'cornflowerblue' => '6495ed',
'cornsilk1' => 'fff8dc',
'cornsilk2' => 'eee8cd',
'cornsilk3' => 'cdc8b1',
'cornsilk4' => '8b8878',
'cyan1' => '00ffff',
'cyan2' => '00eeee',
'cyan3' => '00cdcd',
'cyan4' => '008b8b',
'darkgoldenrod' => 'b8860b',
'darkgoldenrod1' => 'ffb90f',
'darkgoldenrod2' => 'eead0e',
'darkgoldenrod3' => 'cd950c',
'darkgoldenrod4' => '8b6508',
'darkgreen' => '006400',
'darkkhaki' => 'bdb76b',
'darkolivegreen' => '556b2f',
'darkolivegreen1' => 'caff70',
'darkolivegreen2' => 'bcee68',
'darkolivegreen3' => 'a2cd5a',
'darkolivegreen4' => '6e8b3d',
'darkorange' => 'ff8c00',
'darkorange1' => 'ff7f00',
'darkorange2' => 'ee7600',
'darkorange3' => 'cd6600',
'darkorange4' => '8b4500',
'darkorchid' => '9932cc',
'darkorchid1' => 'bf3eff',
'darkorchid2' => 'b23aee',
'darkorchid3' => '9a32cd',
'darkorchid4' => '68228b',
'darksalmon' => 'e9967a',
'darkseagreen' => '8fbc8f',
'darkseagreen1' => 'c1ffc1',
'darkseagreen2' => 'b4eeb4',
'darkseagreen3' => '9bcd9b',
'darkseagreen4' => '698b69',
'darkslateblue' => '483d8b',
'darkslategray' => '2f4f4f',
'darkslategray1' => '97ffff',
'darkslategray2' => '8deeee',
'darkslategray3' => '79cdcd',
'darkslategray4' => '528b8b',
'darkturquoise' => '00ced1',
'darkviolet' => '9400d3',
'deeppink1' => 'ff1493',
'deeppink2' => 'ee1289',
'deeppink3' => 'cd1076',
'deeppink4' => '8b0a50',
'deepskyblue1' => '00bfff',
'deepskyblue2' => '00b2ee',
'deepskyblue3' => '009acd',
'deepskyblue4' => '00688b',
'dimgray' => '696969',
'dodgerblue1' => '1e90ff',
'dodgerblue2' => '1c86ee',
'dodgerblue3' => '1874cd',
'dodgerblue4' => '104e8b',
'firebrick' => 'b22222',
'firebrick1' => 'ff3030',
'firebrick2' => 'ee2c2c',
'firebrick3' => 'cd2626',
'firebrick4' => '8b1a1a',
'floralwhite' => 'fffaf0',
'forestgreen' => '228b22',
'fuchsia' => 'ff00ff',
'gainsboro' => 'dcdcdc',
'ghostwhite' => 'f8f8ff',
'gold1' => 'ffd700',
'gold2' => 'eec900',
'gold3' => 'cdad00',
'gold4' => '8b7500',
'goldenrod' => 'daa520',
'goldenrod1' => 'ffc125',
'goldenrod2' => 'eeb422',
'goldenrod3' => 'cd9b1d',
'goldenrod4' => '8b6914',
'gray' => 'bebebe',
'gray1' => '030303',
'gray10' => '1a1a1a',
'gray11' => '1c1c1c',
'gray12' => '1f1f1f',
'gray13' => '212121',
'gray14' => '242424',
'gray15' => '262626',
'gray16' => '292929',
'gray17' => '2b2b2b',
'gray18' => '2e2e2e',
'gray19' => '303030',
'gray2' => '050505',
'gray20' => '333333',
'gray21' => '363636',
'gray22' => '383838',
'gray23' => '3b3b3b',
'gray24' => '3d3d3d',
'gray25' => '404040',
'gray26' => '424242',
'gray27' => '454545',
'gray28' => '474747',
'gray29' => '4a4a4a',
'gray3' => '080808',
'gray30' => '4d4d4d',
'gray31' => '4f4f4f',
'gray32' => '525252',
'gray33' => '545454',
'gray34' => '575757',
'gray35' => '595959',
'gray36' => '5c5c5c',
'gray37' => '5e5e5e',
'gray38' => '616161',
'gray39' => '636363',
'gray4' => '0a0a0a',
'gray40' => '666666',
'gray41' => '696969',
'gray42' => '6b6b6b',
'gray43' => '6e6e6e',
'gray44' => '707070',
'gray45' => '737373',
'gray46' => '757575',
'gray47' => '787878',
'gray48' => '7a7a7a',
'gray49' => '7d7d7d',
'gray5' => '0d0d0d',
'gray50' => '7f7f7f',
'gray51' => '828282',
'gray52' => '858585',
'gray53' => '878787',
'gray54' => '8a8a8a',
'gray55' => '8c8c8c',
'gray56' => '8f8f8f',
'gray57' => '919191',
'gray58' => '949494',
'gray59' => '969696',
'gray6' => '0f0f0f',
'gray60' => '999999',
'gray61' => '9c9c9c',
'gray62' => '9e9e9e',
'gray63' => 'a1a1a1',
'gray64' => 'a3a3a3',
'gray65' => 'a6a6a6',
'gray66' => 'a8a8a8',
'gray67' => 'ababab',
'gray68' => 'adadad',
'gray69' => 'b0b0b0',
'gray7' => '121212',
'gray70' => 'b3b3b3',
'gray71' => 'b5b5b5',
'gray72' => 'b8b8b8',
'gray73' => 'bababa',
'gray74' => 'bdbdbd',
'gray75' => 'bfbfbf',
'gray76' => 'c2c2c2',
'gray77' => 'c4c4c4',
'gray78' => 'c7c7c7',
'gray79' => 'c9c9c9',
'gray8' => '141414',
'gray80' => 'cccccc',
'gray81' => 'cfcfcf',
'gray82' => 'd1d1d1',
'gray83' => 'd4d4d4',
'gray84' => 'd6d6d6',
'gray85' => 'd9d9d9',
'gray86' => 'dbdbdb',
'gray87' => 'dedede',
'gray88' => 'e0e0e0',
'gray89' => 'e3e3e3',
'gray9' => '171717',
'gray90' => 'e5e5e5',
'gray91' => 'e8e8e8',
'gray92' => 'ebebeb',
'gray93' => 'ededed',
'gray94' => 'f0f0f0',
'gray95' => 'f2f2f2',
'gray97' => 'f7f7f7',
'gray98' => 'fafafa',
'gray99' => 'fcfcfc',
'green' => '00ff00',
'green1' => '00ff00',
'green2' => '00ee00',
'green3' => '00cd00',
'green4' => '008b00',
'greenyellow' => 'adff2f',
'honeydew1' => 'f0fff0',
'honeydew2' => 'e0eee0',
'honeydew3' => 'c1cdc1',
'honeydew4' => '838b83',
'hotpink' => 'ff69b4',
'hotpink1' => 'ff6eb4',
'hotpink2' => 'ee6aa7',
'hotpink3' => 'cd6090',
'hotpink4' => '8b3a62',
'indianred' => 'cd5c5c',
'indianred1' => 'ff6a6a',
'indianred2' => 'ee6363',
'indianred3' => 'cd5555',
'indianred4' => '8b3a3a',
'ivory1' => 'fffff0',
'ivory2' => 'eeeee0',
'ivory3' => 'cdcdc1',
'ivory4' => '8b8b83',
'khaki' => 'f0e68c',
'khaki1' => 'fff68f',
'khaki2' => 'eee685',
'khaki3' => 'cdc673',
'khaki4' => '8b864e',
'lavender' => 'e6e6fa',
'lavenderblush1' => 'fff0f5',
'lavenderblush2' => 'eee0e5',
'lavenderblush3' => 'cdc1c5',
'lavenderblush4' => '8b8386',
'lawngreen' => '7cfc00',
'lemonchiffon1' => 'fffacd',
'lemonchiffon2' => 'eee9bf',
'lemonchiffon3' => 'cdc9a5',
'lemonchiffon4' => '8b8970',
'light' => 'eedd82',
'lightblue' => 'add8e6',
'lightblue1' => 'bfefff',
'lightblue2' => 'b2dfee',
'lightblue3' => '9ac0cd',
'lightblue4' => '68838b',
'lightcoral' => 'f08080',
'lightcyan1' => 'e0ffff',
'lightcyan2' => 'd1eeee',
'lightcyan3' => 'b4cdcd',
'lightcyan4' => '7a8b8b',
'lightgoldenrod1' => 'ffec8b',
'lightgoldenrod2' => 'eedc82',
'lightgoldenrod3' => 'cdbe70',
'lightgoldenrod4' => '8b814c',
'lightgoldenrodyellow' => 'fafad2',
'lightgray' => 'd3d3d3',
'lightpink' => 'ffb6c1',
'lightpink1' => 'ffaeb9',
'lightpink2' => 'eea2ad',
'lightpink3' => 'cd8c95',
'lightpink4' => '8b5f65',
'lightsalmon1' => 'ffa07a',
'lightsalmon2' => 'ee9572',
'lightsalmon3' => 'cd8162',
'lightsalmon4' => '8b5742',
'lightseagreen' => '20b2aa',
'lightskyblue' => '87cefa',
'lightskyblue1' => 'b0e2ff',
'lightskyblue2' => 'a4d3ee',
'lightskyblue3' => '8db6cd',
'lightskyblue4' => '607b8b',
'lightslateblue' => '8470ff',
'lightslategray' => '778899',
'lightsteelblue' => 'b0c4de',
'lightsteelblue1' => 'cae1ff',
'lightsteelblue2' => 'bcd2ee',
'lightsteelblue3' => 'a2b5cd',
'lightsteelblue4' => '6e7b8b',
'lightyellow1' => 'ffffe0',
'lightyellow2' => 'eeeed1',
'lightyellow3' => 'cdcdb4',
'lightyellow4' => '8b8b7a',
'lime' => '00ff00',
'limegreen' => '32cd32',
'linen' => 'faf0e6',
'magenta' => 'ff00ff',
'magenta2' => 'ee00ee',
'magenta3' => 'cd00cd',
'magenta4' => '8b008b',
'maroon' => 'b03060',
'maroon1' => 'ff34b3',
'maroon2' => 'ee30a7',
'maroon3' => 'cd2990',
'maroon4' => '8b1c62',
'medium' => '66cdaa',
'mediumaquamarine' => '66cdaa',
'mediumblue' => '0000cd',
'mediumorchid' => 'ba55d3',
'mediumorchid1' => 'e066ff',
'mediumorchid2' => 'd15fee',
'mediumorchid3' => 'b452cd',
'mediumorchid4' => '7a378b',
'mediumpurple' => '9370db',
'mediumpurple1' => 'ab82ff',
'mediumpurple2' => '9f79ee',
'mediumpurple3' => '8968cd',
'mediumpurple4' => '5d478b',
'mediumseagreen' => '3cb371',
'mediumslateblue' => '7b68ee',
'mediumspringgreen' => '00fa9a',
'mediumturquoise' => '48d1cc',
'mediumvioletred' => 'c71585',
'midnightblue' => '191970',
'mintcream' => 'f5fffa',
'mistyrose1' => 'ffe4e1',
'mistyrose2' => 'eed5d2',
'mistyrose3' => 'cdb7b5',
'mistyrose4' => '8b7d7b',
'moccasin' => 'ffe4b5',
'navajowhite1' => 'ffdead',
'navajowhite2' => 'eecfa1',
'navajowhite3' => 'cdb38b',
'navajowhite4' => '8b795e',
'navy' => '000080',
'navyblue' => '000080',
'oldlace' => 'fdf5e6',
'olive' => '808000',
'olivedrab' => '6b8e23',
'olivedrab1' => 'c0ff3e',
'olivedrab2' => 'b3ee3a',
'olivedrab4' => '698b22',
'orange' => 'ffa500',
'orange1' => 'ffa500',
'orange2' => 'ee9a00',
'orange3' => 'cd8500',
'orange4' => '8b5a00',
'orangered1' => 'ff4500',
'orangered2' => 'ee4000',
'orangered3' => 'cd3700',
'orangered4' => '8b2500',
'orchid' => 'da70d6',
'orchid1' => 'ff83fa',
'orchid2' => 'ee7ae9',
'orchid3' => 'cd69c9',
'orchid4' => '8b4789',
'pale' => 'db7093',
'palegoldenrod' => 'eee8aa',
'palegreen' => '98fb98',
'palegreen1' => '9aff9a',
'palegreen2' => '90ee90',
'palegreen3' => '7ccd7c',
'palegreen4' => '548b54',
'paleturquoise' => 'afeeee',
'paleturquoise1' => 'bbffff',
'paleturquoise2' => 'aeeeee',
'paleturquoise3' => '96cdcd',
'paleturquoise4' => '668b8b',
'palevioletred' => 'db7093',
'palevioletred1' => 'ff82ab',
'palevioletred2' => 'ee799f',
'palevioletred3' => 'cd6889',
'palevioletred4' => '8b475d',
'papayawhip' => 'ffefd5',
'peachpuff1' => 'ffdab9',
'peachpuff2' => 'eecbad',
'peachpuff3' => 'cdaf95',
'peachpuff4' => '8b7765',
'pink' => 'ffc0cb',
'pink1' => 'ffb5c5',
'pink2' => 'eea9b8',
'pink3' => 'cd919e',
'pink4' => '8b636c',
'plum' => 'dda0dd',
'plum1' => 'ffbbff',
'plum2' => 'eeaeee',
'plum3' => 'cd96cd',
'plum4' => '8b668b',
'powderblue' => 'b0e0e6',
'purple' => 'a020f0',
'rebeccapurple' => '663399',
'purple1' => '9b30ff',
'purple2' => '912cee',
'purple3' => '7d26cd',
'purple4' => '551a8b',
'red' => 'ff0000',
'red1' => 'ff0000',
'red2' => 'ee0000',
'red3' => 'cd0000',
'red4' => '8b0000',
'rosybrown' => 'bc8f8f',
'rosybrown1' => 'ffc1c1',
'rosybrown2' => 'eeb4b4',
'rosybrown3' => 'cd9b9b',
'rosybrown4' => '8b6969',
'royalblue' => '4169e1',
'royalblue1' => '4876ff',
'royalblue2' => '436eee',
'royalblue3' => '3a5fcd',
'royalblue4' => '27408b',
'saddlebrown' => '8b4513',
'salmon' => 'fa8072',
'salmon1' => 'ff8c69',
'salmon2' => 'ee8262',
'salmon3' => 'cd7054',
'salmon4' => '8b4c39',
'sandybrown' => 'f4a460',
'seagreen1' => '54ff9f',
'seagreen2' => '4eee94',
'seagreen3' => '43cd80',
'seagreen4' => '2e8b57',
'seashell1' => 'fff5ee',
'seashell2' => 'eee5de',
'seashell3' => 'cdc5bf',
'seashell4' => '8b8682',
'sienna' => 'a0522d',
'sienna1' => 'ff8247',
'sienna2' => 'ee7942',
'sienna3' => 'cd6839',
'sienna4' => '8b4726',
'silver' => 'c0c0c0',
'skyblue' => '87ceeb',
'skyblue1' => '87ceff',
'skyblue2' => '7ec0ee',
'skyblue3' => '6ca6cd',
'skyblue4' => '4a708b',
'slateblue' => '6a5acd',
'slateblue1' => '836fff',
'slateblue2' => '7a67ee',
'slateblue3' => '6959cd',
'slateblue4' => '473c8b',
'slategray' => '708090',
'slategray1' => 'c6e2ff',
'slategray2' => 'b9d3ee',
'slategray3' => '9fb6cd',
'slategray4' => '6c7b8b',
'snow1' => 'fffafa',
'snow2' => 'eee9e9',
'snow3' => 'cdc9c9',
'snow4' => '8b8989',
'springgreen1' => '00ff7f',
'springgreen2' => '00ee76',
'springgreen3' => '00cd66',
'springgreen4' => '008b45',
'steelblue' => '4682b4',
'steelblue1' => '63b8ff',
'steelblue2' => '5cacee',
'steelblue3' => '4f94cd',
'steelblue4' => '36648b',
'tan' => 'd2b48c',
'tan1' => 'ffa54f',
'tan2' => 'ee9a49',
'tan3' => 'cd853f',
'tan4' => '8b5a2b',
'teal' => '008080',
'thistle' => 'd8bfd8',
'thistle1' => 'ffe1ff',
'thistle2' => 'eed2ee',
'thistle3' => 'cdb5cd',
'thistle4' => '8b7b8b',
'tomato1' => 'ff6347',
'tomato2' => 'ee5c42',
'tomato3' => 'cd4f39',
'tomato4' => '8b3626',
'turquoise' => '40e0d0',
'turquoise1' => '00f5ff',
'turquoise2' => '00e5ee',
'turquoise3' => '00c5cd',
'turquoise4' => '00868b',
'violet' => 'ee82ee',
'violetred' => 'd02090',
'violetred1' => 'ff3e96',
'violetred2' => 'ee3a8c',
'violetred3' => 'cd3278',
'violetred4' => '8b2252',
'wheat' => 'f5deb3',
'wheat1' => 'ffe7ba',
'wheat2' => 'eed8ae',
'wheat3' => 'cdba96',
'wheat4' => '8b7e66',
'white' => 'ffffff',
'whitesmoke' => 'f5f5f5',
'yellow' => 'ffff00',
'yellow1' => 'ffff00',
'yellow2' => 'eeee00',
'yellow3' => 'cdcd00',
'yellow4' => '8b8b00',
'yellowgreen' => '9acd32',
];
/** @var ?string */
private $face;
/** @var ?string */
private $size;
/** @var ?string */
private $color;
/** @var bool */
private $bold = false;
/** @var bool */
private $italic = false;
/** @var bool */
private $underline = false;
/** @var bool */
private $superscript = false;
/** @var bool */
private $subscript = false;
/** @var bool */
private $strikethrough = false;
private const START_TAG_CALLBACKS = [
'font' => 'startFontTag',
'b' => 'startBoldTag',
'strong' => 'startBoldTag',
'i' => 'startItalicTag',
'em' => 'startItalicTag',
'u' => 'startUnderlineTag',
'ins' => 'startUnderlineTag',
'del' => 'startStrikethruTag',
'sup' => 'startSuperscriptTag',
'sub' => 'startSubscriptTag',
];
private const END_TAG_CALLBACKS = [
'font' => 'endFontTag',
'b' => 'endBoldTag',
'strong' => 'endBoldTag',
'i' => 'endItalicTag',
'em' => 'endItalicTag',
'u' => 'endUnderlineTag',
'ins' => 'endUnderlineTag',
'del' => 'endStrikethruTag',
'sup' => 'endSuperscriptTag',
'sub' => 'endSubscriptTag',
'br' => 'breakTag',
'p' => 'breakTag',
'h1' => 'breakTag',
'h2' => 'breakTag',
'h3' => 'breakTag',
'h4' => 'breakTag',
'h5' => 'breakTag',
'h6' => 'breakTag',
];
/** @var array */
private $stack = [];
/** @var string */
private $stringData = '';
/**
* @var RichText
*/
private $richTextObject;
private function initialise(): void
{
$this->face = $this->size = $this->color = null;
$this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false;
$this->stack = [];
$this->stringData = '';
}
/**
* Parse HTML formatting and return the resulting RichText.
*
* @param string $html
*
* @return RichText
*/
public function toRichTextObject($html)
{
$this->initialise();
// Create a new DOM object
$dom = new DOMDocument();
// Load the HTML file into the DOM object
// Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
$prefix = '<?xml encoding="UTF-8">';
/** @scrutinizer ignore-unhandled */
@$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
// Discard excess white space
$dom->preserveWhiteSpace = false;
$this->richTextObject = new RichText();
$this->parseElements($dom);
// Clean any further spurious whitespace
$this->cleanWhitespace();
return $this->richTextObject;
}
private function cleanWhitespace(): void
{
foreach ($this->richTextObject->getRichTextElements() as $key => $element) {
$text = $element->getText();
// Trim any leading spaces on the first run
if ($key == 0) {
$text = ltrim($text);
}
// Trim any spaces immediately after a line break
$text = (string) preg_replace('/\n */mu', "\n", $text);
$element->setText($text);
}
}
private function buildTextRun(): void
{
$text = $this->stringData;
if (trim($text) === '') {
return;
}
$richtextRun = $this->richTextObject->createTextRun($this->stringData);
$font = $richtextRun->getFont();
if ($font !== null) {
if ($this->face) {
$font->setName($this->face);
}
if ($this->size) {
$font->setSize($this->size);
}
if ($this->color) {
$font->setColor(new Color('ff' . $this->color));
}
if ($this->bold) {
$font->setBold(true);
}
if ($this->italic) {
$font->setItalic(true);
}
if ($this->underline) {
$font->setUnderline(Font::UNDERLINE_SINGLE);
}
if ($this->superscript) {
$font->setSuperscript(true);
}
if ($this->subscript) {
$font->setSubscript(true);
}
if ($this->strikethrough) {
$font->setStrikethrough(true);
}
}
$this->stringData = '';
}
private function rgbToColour(string $rgbValue): string
{
preg_match_all('/\d+/', $rgbValue, $values);
foreach ($values[0] as &$value) {
$value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT);
}
return implode('', $values[0]);
}
public static function colourNameLookup(string $colorName): string
{
return self::COLOUR_MAP[$colorName] ?? '';
}
protected function startFontTag(DOMElement $tag): void
{
$attrs = $tag->attributes;
if ($attrs !== null) {
foreach ($attrs as $attribute) {
$attributeName = strtolower($attribute->name);
$attributeValue = $attribute->value;
if ($attributeName == 'color') {
if (preg_match('/rgb\s*\(/', $attributeValue)) {
$this->$attributeName = $this->rgbToColour($attributeValue);
} elseif (strpos(trim($attributeValue), '#') === 0) {
$this->$attributeName = ltrim($attributeValue, '#');
} else {
$this->$attributeName = static::colourNameLookup($attributeValue);
}
} else {
$this->$attributeName = $attributeValue;
}
}
}
}
protected function endFontTag(): void
{
$this->face = $this->size = $this->color = null;
}
protected function startBoldTag(): void
{
$this->bold = true;
}
protected function endBoldTag(): void
{
$this->bold = false;
}
protected function startItalicTag(): void
{
$this->italic = true;
}
protected function endItalicTag(): void
{
$this->italic = false;
}
protected function startUnderlineTag(): void
{
$this->underline = true;
}
protected function endUnderlineTag(): void
{
$this->underline = false;
}
protected function startSubscriptTag(): void
{
$this->subscript = true;
}
protected function endSubscriptTag(): void
{
$this->subscript = false;
}
protected function startSuperscriptTag(): void
{
$this->superscript = true;
}
protected function endSuperscriptTag(): void
{
$this->superscript = false;
}
protected function startStrikethruTag(): void
{
$this->strikethrough = true;
}
protected function endStrikethruTag(): void
{
$this->strikethrough = false;
}
protected function breakTag(): void
{
$this->stringData .= "\n";
}
private function parseTextNode(DOMText $textNode): void
{
$domText = (string) preg_replace(
'/\s+/u',
' ',
str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '')
);
$this->stringData .= $domText;
$this->buildTextRun();
}
/**
* @param string $callbackTag
*/
private function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void
{
if (isset($callbacks[$callbackTag])) {
$elementHandler = $callbacks[$callbackTag];
if (method_exists($this, $elementHandler)) {
/** @var callable */
$callable = [$this, $elementHandler];
call_user_func($callable, $element);
}
}
}
private function parseElementNode(DOMElement $element): void
{
$callbackTag = strtolower($element->nodeName);
$this->stack[] = $callbackTag;
$this->handleCallback($element, $callbackTag, self::START_TAG_CALLBACKS);
$this->parseElements($element);
array_pop($this->stack);
$this->handleCallback($element, $callbackTag, self::END_TAG_CALLBACKS);
}
private function parseElements(DOMNode $element): void
{
foreach ($element->childNodes as $child) {
if ($child instanceof DOMText) {
$this->parseTextNode($child);
} elseif ($child instanceof DOMElement) {
$this->parseElementNode($child);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php 0000644 00000001636 15243417764 0016156 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
class Size
{
const REGEXP_SIZE_VALIDATION = '/^(?P<size>\d*\.?\d+)(?P<unit>pt|px|em)?$/i';
/**
* @var bool
*/
protected $valid;
/**
* @var string
*/
protected $size = '';
/**
* @var string
*/
protected $unit = '';
public function __construct(string $size)
{
$this->valid = (bool) preg_match(self::REGEXP_SIZE_VALIDATION, $size, $matches);
if ($this->valid) {
$this->size = $matches['size'];
$this->unit = $matches['unit'] ?? 'pt';
}
}
public function valid(): bool
{
return $this->valid;
}
public function size(): string
{
return $this->size;
}
public function unit(): string
{
return $this->unit;
}
public function __toString()
{
return $this->size . $this->unit;
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php 0000644 00000005300 15243417764 0017332 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
use PhpOffice\PhpSpreadsheet\Exception;
class Downloader
{
protected string $filepath;
protected string $filename;
protected string $filetype;
protected const CONTENT_TYPES = [
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xls' => 'application/vnd.ms-excel',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'csv' => 'text/csv',
'html' => 'text/html',
'pdf' => 'application/pdf',
];
public function __construct(string $folder, string $filename, ?string $filetype = null)
{
if ((is_dir($folder) === false) || (is_readable($folder) === false)) {
throw new Exception("Folder {$folder} is not accessable");
}
$filepath = "{$folder}/{$filename}";
$this->filepath = (string) realpath($filepath);
$this->filename = basename($filepath);
if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) {
throw new Exception("{$this->filename} not found, or cannot be read");
}
$filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
throw new Exception("Invalid filetype: {$filetype} cannot be downloaded");
}
$this->filetype = strtolower($filetype);
}
public function download(): void
{
$this->headers();
readfile($this->filepath);
}
public function headers(): void
{
ob_clean();
$this->contentType();
$this->contentDisposition();
$this->cacheHeaders();
$this->fileSize();
flush();
}
protected function contentType(): void
{
header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]);
}
protected function contentDisposition(): void
{
header('Content-Disposition: attachment;filename="' . $this->filename . '"');
}
protected function cacheHeaders(): void
{
header('Cache-Control: max-age=0');
// If you're serving to IE 9, then the following may be needed
header('Cache-Control: max-age=1');
// If you're serving to IE over SSL, then the following may be needed
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
header('Pragma: public'); // HTTP/1.0
}
protected function fileSize(): void
{
header('Content-Length: ' . filesize($this->filepath));
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php 0000644 00000006351 15243417764 0017170 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Shared\Drawing;
use PhpOffice\PhpSpreadsheet\Style\Font;
class Dimension
{
public const UOM_CENTIMETERS = 'cm';
public const UOM_MILLIMETERS = 'mm';
public const UOM_INCHES = 'in';
public const UOM_PIXELS = 'px';
public const UOM_POINTS = 'pt';
public const UOM_PICA = 'pc';
/**
* Based on 96 dpi.
*/
const ABSOLUTE_UNITS = [
self::UOM_CENTIMETERS => 96.0 / 2.54,
self::UOM_MILLIMETERS => 96.0 / 25.4,
self::UOM_INCHES => 96.0,
self::UOM_PIXELS => 1.0,
self::UOM_POINTS => 96.0 / 72,
self::UOM_PICA => 96.0 * 12 / 72,
];
/**
* Based on a standard column width of 8.54 units in MS Excel.
*/
const RELATIVE_UNITS = [
'em' => 10.0 / 8.54,
'ex' => 10.0 / 8.54,
'ch' => 10.0 / 8.54,
'rem' => 10.0 / 8.54,
'vw' => 8.54,
'vh' => 8.54,
'vmin' => 8.54,
'vmax' => 8.54,
'%' => 8.54 / 100,
];
/**
* @var float|int If this is a width, then size is measured in pixels (if is set)
* or in Excel's default column width units if $unit is null.
* If this is a height, then size is measured in pixels ()
* or in points () if $unit is null.
*/
protected $size;
/**
* @var null|string
*/
protected $unit;
/**
* Phpstan bug has been fixed; this function allows us to
* pass Phpstan whether fixed or not.
*
* @param mixed $value
*/
private static function stanBugFixed($value): array
{
return is_array($value) ? $value : [null, null];
}
public function __construct(string $dimension)
{
[$size, $unit] = self::stanBugFixed(sscanf($dimension, '%[1234567890.]%s'));
$unit = strtolower(trim($unit ?? ''));
$size = (float) $size;
// If a UoM is specified, then convert the size to pixels for internal storage
if (isset(self::ABSOLUTE_UNITS[$unit])) {
$size *= self::ABSOLUTE_UNITS[$unit];
$this->unit = self::UOM_PIXELS;
} elseif (isset(self::RELATIVE_UNITS[$unit])) {
$size *= self::RELATIVE_UNITS[$unit];
$size = round($size, 4);
}
$this->size = $size;
}
public function width(): float
{
return (float) ($this->unit === null)
? $this->size
: round(Drawing::pixelsToCellDimension((int) $this->size, new Font(false)), 4);
}
public function height(): float
{
return (float) ($this->unit === null)
? $this->size
: $this->toUnit(self::UOM_POINTS);
}
public function toUnit(string $unitOfMeasure): float
{
$unitOfMeasure = strtolower($unitOfMeasure);
if (!array_key_exists($unitOfMeasure, self::ABSOLUTE_UNITS)) {
throw new Exception("{$unitOfMeasure} is not a vaid unit of measure");
}
$size = $this->size;
if ($this->unit === null) {
$size = Drawing::cellDimensionToPixels($size, new Font(false));
}
return $size / self::ABSOLUTE_UNITS[$unitOfMeasure];
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php 0000644 00000022341 15243417764 0016461 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RecursiveRegexIterator;
use ReflectionClass;
use RegexIterator;
use RuntimeException;
use Throwable;
/**
* Helper class to be used in sample code.
*/
class Sample
{
/**
* Returns whether we run on CLI or browser.
*
* @return bool
*/
public function isCli()
{
return PHP_SAPI === 'cli';
}
/**
* Return the filename currently being executed.
*
* @return string
*/
public function getScriptFilename()
{
return basename($_SERVER['SCRIPT_FILENAME'], '.php');
}
/**
* Whether we are executing the index page.
*
* @return bool
*/
public function isIndex()
{
return $this->getScriptFilename() === 'index';
}
/**
* Return the page title.
*
* @return string
*/
public function getPageTitle()
{
return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
}
/**
* Return the page heading.
*
* @return string
*/
public function getPageHeading()
{
return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
}
/**
* Returns an array of all known samples.
*
* @return string[][] [$name => $path]
*/
public function getSamples()
{
// Populate samples
$baseDir = realpath(__DIR__ . '/../../../samples');
if ($baseDir === false) {
// @codeCoverageIgnoreStart
throw new RuntimeException('realpath returned false');
// @codeCoverageIgnoreEnd
}
$directory = new RecursiveDirectoryIterator($baseDir);
$iterator = new RecursiveIteratorIterator($directory);
$regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
$files = [];
foreach ($regex as $file) {
$file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
if (is_array($file)) {
// @codeCoverageIgnoreStart
throw new RuntimeException('str_replace returned array');
// @codeCoverageIgnoreEnd
}
$info = pathinfo($file);
$category = str_replace('_', ' ', $info['dirname'] ?? '');
$name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
if (!in_array($category, ['.', 'boostrap', 'templates'])) {
if (!isset($files[$category])) {
$files[$category] = [];
}
$files[$category][$name] = $file;
}
}
// Sort everything
ksort($files);
foreach ($files as &$f) {
asort($f);
}
return $files;
}
/**
* Write documents.
*
* @param string $filename
* @param string[] $writers
*/
public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void
{
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
$spreadsheet->setActiveSheetIndex(0);
// Write documents
foreach ($writers as $writerType) {
$path = $this->getFilename($filename, mb_strtolower($writerType));
$writer = IOFactory::createWriter($spreadsheet, $writerType);
$writer->setIncludeCharts($withCharts);
if ($writerCallback !== null) {
$writerCallback($writer);
}
$callStartTime = microtime(true);
$writer->save($path);
$this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime);
if ($this->isCli() === false) {
echo '<a href="/download.php?type=' . pathinfo($path, PATHINFO_EXTENSION) . '&name=' . basename($path) . '">Download ' . basename($path) . '</a><br />';
}
}
$this->logEndingNotes();
}
protected function isDirOrMkdir(string $folder): bool
{
return \is_dir($folder) || \mkdir($folder);
}
/**
* Returns the temporary directory and make sure it exists.
*
* @return string
*/
public function getTemporaryFolder()
{
$tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
if (!$this->isDirOrMkdir($tempFolder)) {
throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
}
return $tempFolder;
}
/**
* Returns the filename that should be used for sample output.
*
* @param string $filename
* @param string $extension
*/
public function getFilename($filename, $extension = 'xlsx'): string
{
$originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
return $this->getTemporaryFolder() . '/' . str_replace('.' . /** @scrutinizer ignore-type */ $originalExtension, '.' . $extension, basename($filename));
}
/**
* Return a random temporary file name.
*
* @param string $extension
*
* @return string
*/
public function getTemporaryFilename($extension = 'xlsx')
{
$temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
if ($temporaryFilename === false) {
// @codeCoverageIgnoreStart
throw new RuntimeException('tempnam returned false');
// @codeCoverageIgnoreEnd
}
unlink($temporaryFilename);
return $temporaryFilename . '.' . $extension;
}
public function log(string $message): void
{
$eol = $this->isCli() ? PHP_EOL : '<br />';
echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
}
public function renderChart(Chart $chart, string $fileName): void
{
if ($this->isCli() === true) {
return;
}
Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
$fileName = $this->getFilename($fileName, 'png');
try {
$chart->render($fileName);
$this->log('Rendered image: ' . $fileName);
$imageData = file_get_contents($fileName);
if ($imageData !== false) {
echo '<div><img src="data:image/gif;base64,' . base64_encode($imageData) . '" /></div>';
}
} catch (Throwable $e) {
$this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
}
}
public function titles(string $category, string $functionName, ?string $description = null): void
{
$this->log(sprintf('%s Functions:', $category));
$description === null
? $this->log(sprintf('Function: %s()', rtrim($functionName, '()')))
: $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.')));
}
public function displayGrid(array $matrix): void
{
$renderer = new TextGrid($matrix, $this->isCli());
echo $renderer->render();
}
public function logCalculationResult(
Worksheet $worksheet,
string $functionName,
string $formulaCell,
?string $descriptionCell = null
): void {
if ($descriptionCell !== null) {
$this->log($worksheet->getCell($descriptionCell)->getValue());
}
$this->log($worksheet->getCell($formulaCell)->getValue());
$this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue());
}
/**
* Log ending notes.
*/
public function logEndingNotes(): void
{
// Do not show execution time for index
$this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
}
/**
* Log a line about the write operation.
*
* @param string $path
* @param float $callStartTime
*/
public function logWrite(IWriter $writer, $path, $callStartTime): void
{
$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
$reflection = new ReflectionClass($writer);
$format = $reflection->getShortName();
$message = ($this->isCli() === true)
? "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds'
: "Write {$format} format to <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
$this->log($message);
}
/**
* Log a line about the read operation.
*
* @param string $format
* @param string $path
* @param float $callStartTime
*/
public function logRead($format, $path, $callStartTime): void
{
$callEndTime = microtime(true);
$callTime = $callEndTime - $callStartTime;
$message = "Read {$format} format from <code>{$path}</code> in " . sprintf('%.4f', $callTime) . ' seconds';
$this->log($message);
}
}
phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php 0000644 00000010135 15243417764 0016770 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Helper;
class TextGrid
{
/**
* @var bool
*/
private $isCli = true;
/**
* @var array
*/
protected $matrix;
/**
* @var array
*/
protected $rows;
/**
* @var array
*/
protected $columns;
/**
* @var string
*/
private $gridDisplay;
public function __construct(array $matrix, bool $isCli = true)
{
$this->rows = array_keys($matrix);
$this->columns = array_keys($matrix[$this->rows[0]]);
$matrix = array_values($matrix);
array_walk(
$matrix,
function (&$row): void {
$row = array_values($row);
}
);
$this->matrix = $matrix;
$this->isCli = $isCli;
}
public function render(): string
{
$this->gridDisplay = $this->isCli ? '' : '<pre>';
$maxRow = max($this->rows);
$maxRowLength = mb_strlen((string) $maxRow) + 1;
$columnWidths = $this->getColumnWidths();
$this->renderColumnHeader($maxRowLength, $columnWidths);
$this->renderRows($maxRowLength, $columnWidths);
$this->renderFooter($maxRowLength, $columnWidths);
$this->gridDisplay .= $this->isCli ? '' : '</pre>';
return $this->gridDisplay;
}
private function renderRows(int $maxRowLength, array $columnWidths): void
{
foreach ($this->matrix as $row => $rowData) {
$this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' ';
$this->renderCells($rowData, $columnWidths);
$this->gridDisplay .= '|' . PHP_EOL;
}
}
private function renderCells(array $rowData, array $columnWidths): void
{
foreach ($rowData as $column => $cell) {
$displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
$this->gridDisplay .= '| ';
$this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
}
}
private function renderColumnHeader(int $maxRowLength, array $columnWidths): void
{
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
foreach ($this->columns as $column => $reference) {
$this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1);
}
$this->gridDisplay .= '+' . PHP_EOL;
$this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
foreach ($this->columns as $column => $reference) {
$this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' ');
}
$this->gridDisplay .= '|' . PHP_EOL;
$this->renderFooter($maxRowLength, $columnWidths);
}
private function renderFooter(int $maxRowLength, array $columnWidths): void
{
$this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1);
foreach ($this->columns as $column => $reference) {
$this->gridDisplay .= '+-';
$this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-');
}
$this->gridDisplay .= '+' . PHP_EOL;
}
private function getColumnWidths(): array
{
$columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix);
$columnWidths = [];
for ($column = 0; $column < $columnCount; ++$column) {
$columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column));
}
return $columnWidths;
}
private function getColumnWidth(array $columnData): int
{
$columnWidth = 0;
$columnData = array_values($columnData);
foreach ($columnData as $columnValue) {
if (is_string($columnValue)) {
$columnWidth = max($columnWidth, mb_strlen($columnValue));
} elseif (is_bool($columnValue)) {
$columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
}
$columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
}
return $columnWidth;
}
}
phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php 0000644 00000001715 15243417764 0016375 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class NamedFormula extends DefinedName
{
/**
* Create a new Named Formula.
*/
public function __construct(
string $name,
?Worksheet $worksheet = null,
?string $formula = null,
bool $localOnly = false,
?Worksheet $scope = null
) {
// Validate data
if (!isset($formula)) {
throw new Exception('You must specify a Formula value for a Named Formula');
}
parent::__construct($name, $worksheet, $formula, $localOnly, $scope);
}
/**
* Get the formula value.
*/
public function getFormula(): string
{
return $this->value;
}
/**
* Set the formula value.
*/
public function setFormula(string $formula): self
{
if (!empty($formula)) {
$this->value = $formula;
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/IOFactory.php 0000644 00000021474 15243417764 0015666 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Reader\IReader;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
/**
* Factory to create readers and writers easily.
*
* It is not required to use this class, but it should make it easier to read and write files.
* Especially for reading files with an unknown format.
*/
abstract class IOFactory
{
public const READER_XLSX = 'Xlsx';
public const READER_XLS = 'Xls';
public const READER_XML = 'Xml';
public const READER_ODS = 'Ods';
public const READER_SYLK = 'Slk';
public const READER_SLK = 'Slk';
public const READER_GNUMERIC = 'Gnumeric';
public const READER_HTML = 'Html';
public const READER_CSV = 'Csv';
public const WRITER_XLSX = 'Xlsx';
public const WRITER_XLS = 'Xls';
public const WRITER_ODS = 'Ods';
public const WRITER_CSV = 'Csv';
public const WRITER_HTML = 'Html';
/** @var string[] */
private static $readers = [
self::READER_XLSX => Reader\Xlsx::class,
self::READER_XLS => Reader\Xls::class,
self::READER_XML => Reader\Xml::class,
self::READER_ODS => Reader\Ods::class,
self::READER_SLK => Reader\Slk::class,
self::READER_GNUMERIC => Reader\Gnumeric::class,
self::READER_HTML => Reader\Html::class,
self::READER_CSV => Reader\Csv::class,
];
/** @var string[] */
private static $writers = [
self::WRITER_XLS => Writer\Xls::class,
self::WRITER_XLSX => Writer\Xlsx::class,
self::WRITER_ODS => Writer\Ods::class,
self::WRITER_CSV => Writer\Csv::class,
self::WRITER_HTML => Writer\Html::class,
'Tcpdf' => Writer\Pdf\Tcpdf::class,
'Dompdf' => Writer\Pdf\Dompdf::class,
'Mpdf' => Writer\Pdf\Mpdf::class,
];
/**
* Create Writer\IWriter.
*/
public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
{
if (!isset(self::$writers[$writerType])) {
throw new Writer\Exception("No writer found for type $writerType");
}
// Instantiate writer
/** @var IWriter */
$className = self::$writers[$writerType];
return new $className($spreadsheet);
}
/**
* Create IReader.
*/
public static function createReader(string $readerType): IReader
{
if (!isset(self::$readers[$readerType])) {
throw new Reader\Exception("No reader found for type $readerType");
}
// Instantiate reader
/** @var IReader */
$className = self::$readers[$readerType];
return new $className();
}
/**
* Loads Spreadsheet from file using automatic Reader\IReader resolution.
*
* @param string $filename The name of the spreadsheet file
* @param int $flags the optional second parameter flags may be used to identify specific elements
* that should be loaded, but which won't be loaded by default, using these values:
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file.
* IReader::READ_DATA_ONLY - Read cell values only, not formatting or merge structure.
* IReader::IGNORE_EMPTY_CELLS - Don't load empty cells into the model.
* @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
* all possible Readers until it finds a match; but this allows you to pass in a
* list of Readers so it will only try the subset that you specify here.
* Values in this list can be any of the constant values defined in the set
* IOFactory::READER_*.
*/
public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet
{
$reader = self::createReaderForFile($filename, $readers);
return $reader->load($filename, $flags);
}
/**
* Identify file type using automatic IReader resolution.
*/
public static function identify(string $filename, ?array $readers = null): string
{
$reader = self::createReaderForFile($filename, $readers);
$className = get_class($reader);
$classType = explode('\\', $className);
unset($reader);
return array_pop($classType);
}
/**
* Create Reader\IReader for file using automatic IReader resolution.
*
* @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
* all possible Readers until it finds a match; but this allows you to pass in a
* list of Readers so it will only try the subset that you specify here.
* Values in this list can be any of the constant values defined in the set
* IOFactory::READER_*.
*/
public static function createReaderForFile(string $filename, ?array $readers = null): IReader
{
File::assertFile($filename);
$testReaders = self::$readers;
if ($readers !== null) {
$readers = array_map('strtoupper', $readers);
$testReaders = array_filter(
self::$readers,
function (string $readerType) use ($readers) {
return in_array(strtoupper($readerType), $readers, true);
},
ARRAY_FILTER_USE_KEY
);
}
// First, lucky guess by inspecting file extension
$guessedReader = self::getReaderTypeFromExtension($filename);
if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) {
$reader = self::createReader($guessedReader);
// Let's see if we are lucky
if ($reader->canRead($filename)) {
return $reader;
}
}
// If we reach here then "lucky guess" didn't give any result
// Try walking through all the options in self::$readers (or the selected subset)
foreach ($testReaders as $readerType => $class) {
// Ignore our original guess, we know that won't work
if ($readerType !== $guessedReader) {
$reader = self::createReader($readerType);
if ($reader->canRead($filename)) {
return $reader;
}
}
}
throw new Reader\Exception('Unable to identify a reader for this file');
}
/**
* Guess a reader type from the file extension, if any.
*/
private static function getReaderTypeFromExtension(string $filename): ?string
{
$pathinfo = pathinfo($filename);
if (!isset($pathinfo['extension'])) {
return null;
}
switch (strtolower($pathinfo['extension'])) {
case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
case 'xltx': // Excel (OfficeOpenXML) Template
case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
return 'Xlsx';
case 'xls': // Excel (BIFF) Spreadsheet
case 'xlt': // Excel (BIFF) Template
return 'Xls';
case 'ods': // Open/Libre Offic Calc
case 'ots': // Open/Libre Offic Calc Template
return 'Ods';
case 'slk':
return 'Slk';
case 'xml': // Excel 2003 SpreadSheetML
return 'Xml';
case 'gnumeric':
return 'Gnumeric';
case 'htm':
case 'html':
return 'Html';
case 'csv':
// Do nothing
// We must not try to use CSV reader since it loads
// all files including Excel files etc.
return null;
default:
return null;
}
}
/**
* Register a writer with its type and class name.
*/
public static function registerWriter(string $writerType, string $writerClass): void
{
if (!is_a($writerClass, IWriter::class, true)) {
throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
}
self::$writers[$writerType] = $writerClass;
}
/**
* Register a reader with its type and class name.
*/
public static function registerReader(string $readerType, string $readerClass): void
{
if (!is_a($readerClass, IReader::class, true)) {
throw new Reader\Exception('Registered readers must implement ' . IReader::class);
}
self::$readers[$readerType] = $readerClass;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php 0000644 00000011002 15243417764 0017407 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Exception;
/**
* Validate a cell value according to its validation rules.
*/
class DataValidator
{
/**
* Does this cell contain valid value?
*
* @param Cell $cell Cell to check the value
*
* @return bool
*/
public function isValid(Cell $cell)
{
if (!$cell->hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) {
return true;
}
$cellValue = $cell->getValue();
$dataValidation = $cell->getDataValidation();
if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) {
return false;
}
$returnValue = false;
$type = $dataValidation->getType();
if ($type === DataValidation::TYPE_LIST) {
$returnValue = $this->isValueInList($cell);
} elseif ($type === DataValidation::TYPE_WHOLE) {
if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) {
$returnValue = false;
} else {
$returnValue = $this->numericOperator($dataValidation, (int) $cellValue);
}
} elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) {
if (!is_numeric($cellValue)) {
$returnValue = false;
} else {
$returnValue = $this->numericOperator($dataValidation, (float) $cellValue);
}
} elseif ($type === DataValidation::TYPE_TEXTLENGTH) {
$returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue));
}
return $returnValue;
}
/** @param float|int $cellValue */
private function numericOperator(DataValidation $dataValidation, $cellValue): bool
{
$operator = $dataValidation->getOperator();
$formula1 = $dataValidation->getFormula1();
$formula2 = $dataValidation->getFormula2();
$returnValue = false;
if ($operator === DataValidation::OPERATOR_BETWEEN) {
$returnValue = $cellValue >= $formula1 && $cellValue <= $formula2;
} elseif ($operator === DataValidation::OPERATOR_NOTBETWEEN) {
$returnValue = $cellValue < $formula1 || $cellValue > $formula2;
} elseif ($operator === DataValidation::OPERATOR_EQUAL) {
$returnValue = $cellValue == $formula1;
} elseif ($operator === DataValidation::OPERATOR_NOTEQUAL) {
$returnValue = $cellValue != $formula1;
} elseif ($operator === DataValidation::OPERATOR_LESSTHAN) {
$returnValue = $cellValue < $formula1;
} elseif ($operator === DataValidation::OPERATOR_LESSTHANOREQUAL) {
$returnValue = $cellValue <= $formula1;
} elseif ($operator === DataValidation::OPERATOR_GREATERTHAN) {
$returnValue = $cellValue > $formula1;
} elseif ($operator === DataValidation::OPERATOR_GREATERTHANOREQUAL) {
$returnValue = $cellValue >= $formula1;
}
return $returnValue;
}
/**
* Does this cell contain valid value, based on list?
*
* @param Cell $cell Cell to check the value
*
* @return bool
*/
private function isValueInList(Cell $cell)
{
$cellValue = $cell->getValue();
$dataValidation = $cell->getDataValidation();
$formula1 = $dataValidation->getFormula1();
if (!empty($formula1)) {
// inline values list
if ($formula1[0] === '"') {
return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true);
} elseif (strpos($formula1, ':') > 0) {
// values list cells
$matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)';
$calculation = Calculation::getInstance($cell->getWorksheet()->getParent());
try {
$result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell);
while (is_array($result)) {
$result = array_pop($result);
}
return $result !== ExcelError::NA();
} catch (Exception $ex) {
return false;
}
}
}
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php 0000644 00000011366 15243417764 0016541 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class CellRange implements AddressRange
{
/**
* @var CellAddress
*/
protected $from;
/**
* @var CellAddress
*/
protected $to;
public function __construct(CellAddress $from, CellAddress $to)
{
$this->validateFromTo($from, $to);
}
private function validateFromTo(CellAddress $from, CellAddress $to): void
{
// Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left)
$firstColumn = min($from->columnId(), $to->columnId());
$firstRow = min($from->rowId(), $to->rowId());
$lastColumn = max($from->columnId(), $to->columnId());
$lastRow = max($from->rowId(), $to->rowId());
$fromWorksheet = $from->worksheet();
$toWorksheet = $to->worksheet();
$this->validateWorksheets($fromWorksheet, $toWorksheet);
$this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet);
$this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet);
}
private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void
{
if ($fromWorksheet !== null && $toWorksheet !== null) {
// We could simply compare worksheets rather than worksheet titles; but at some point we may introduce
// support for 3d ranges; and at that point we drop this check and let the validation fall through
// to the check for same workbook; but unless we check on titles, this test will also detect if the
// worksheets are in different spreadsheets, and the next check will never execute or throw its
// own exception.
if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) {
throw new Exception('3d Cell Ranges are not supported');
} elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) {
throw new Exception('Worksheets must be in the same spreadsheet');
}
}
}
private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null): CellAddress
{
$cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row;
return new class ($cellAddress, $worksheet) extends CellAddress {
public function nextRow(int $offset = 1): CellAddress
{
/** @var CellAddress $result */
$result = parent::nextRow($offset);
$this->rowId = $result->rowId;
$this->cellAddress = $result->cellAddress;
return $this;
}
public function previousRow(int $offset = 1): CellAddress
{
/** @var CellAddress $result */
$result = parent::previousRow($offset);
$this->rowId = $result->rowId;
$this->cellAddress = $result->cellAddress;
return $this;
}
public function nextColumn(int $offset = 1): CellAddress
{
/** @var CellAddress $result */
$result = parent::nextColumn($offset);
$this->columnId = $result->columnId;
$this->columnName = $result->columnName;
$this->cellAddress = $result->cellAddress;
return $this;
}
public function previousColumn(int $offset = 1): CellAddress
{
/** @var CellAddress $result */
$result = parent::previousColumn($offset);
$this->columnId = $result->columnId;
$this->columnName = $result->columnName;
$this->cellAddress = $result->cellAddress;
return $this;
}
};
}
public function from(): CellAddress
{
// Re-order from/to in case the cell addresses have been modified
$this->validateFromTo($this->from, $this->to);
return $this->from;
}
public function to(): CellAddress
{
// Re-order from/to in case the cell addresses have been modified
$this->validateFromTo($this->from, $this->to);
return $this->to;
}
public function __toString(): string
{
// Re-order from/to in case the cell addresses have been modified
$this->validateFromTo($this->from, $this->to);
if ($this->from->cellAddress() === $this->to->cellAddress()) {
return "{$this->from->fullCellAddress()}";
}
$fromAddress = $this->from->fullCellAddress();
$toAddress = $this->to->cellAddress();
return "{$fromAddress}:{$toAddress}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php 0000644 00000005256 15243417764 0020413 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class DefaultValueBinder implements IValueBinder
{
/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*
* @return bool
*/
public function bindValue(Cell $cell, $value)
{
// sanitize UTF-8 strings
if (is_string($value)) {
$value = StringHelper::sanitizeUTF8($value);
} elseif (is_object($value)) {
// Handle any objects that might be injected
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
} elseif (!($value instanceof RichText)) {
// Attempt to cast any unexpected objects to string
$value = (string) $value; // @phpstan-ignore-line
}
}
// Set value explicit
$cell->setValueExplicit($value, static::dataTypeForValue($value));
// Done!
return true;
}
/**
* DataType for value.
*
* @param mixed $value
*
* @return string
*/
public static function dataTypeForValue($value)
{
// Match the value against a few data types
if ($value === null) {
return DataType::TYPE_NULL;
} elseif (is_float($value) || is_int($value)) {
return DataType::TYPE_NUMERIC;
} elseif (is_bool($value)) {
return DataType::TYPE_BOOL;
} elseif ($value === '') {
return DataType::TYPE_STRING;
} elseif ($value instanceof RichText) {
return DataType::TYPE_INLINE;
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
return DataType::TYPE_FORMULA;
} elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) {
$tValue = ltrim($value, '+-');
if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') {
return DataType::TYPE_STRING;
} elseif ((strpos($value, '.') === false) && ($value > PHP_INT_MAX)) {
return DataType::TYPE_STRING;
} elseif (!is_numeric($value)) {
return DataType::TYPE_STRING;
}
return DataType::TYPE_NUMERIC;
} elseif (is_string($value)) {
$errorCodes = DataType::getErrorCodes();
if (isset($errorCodes[$value])) {
return DataType::TYPE_ERROR;
}
}
return DataType::TYPE_STRING;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php 0000644 00000000456 15243417764 0017214 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
interface IValueBinder
{
/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*
* @return bool
*/
public function bindValue(Cell $cell, $value);
}
phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php 0000644 00000051112 15243417764 0016765 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
/**
* Helper class to manipulate cell coordinates.
*
* Columns indexes and rows are always based on 1, **not** on 0. This match the behavior
* that Excel users are used to, and also match the Excel functions `COLUMN()` and `ROW()`.
*/
abstract class Coordinate
{
public const A1_COORDINATE_REGEX = '/^(?<col>\$?[A-Z]{1,3})(?<row>\$?\d{1,7})$/i';
/**
* Default range variable constant.
*
* @var string
*/
const DEFAULT_RANGE = 'A1:A1';
/**
* Convert string coordinate to [0 => int column index, 1 => int row index].
*
* @param string $cellAddress eg: 'A1'
*
* @return array{0: string, 1: string} Array containing column and row (indexes 0 and 1)
*/
public static function coordinateFromString($cellAddress): array
{
if (preg_match(self::A1_COORDINATE_REGEX, $cellAddress, $matches)) {
return [$matches['col'], $matches['row']];
} elseif (self::coordinateIsRange($cellAddress)) {
throw new Exception('Cell coordinate string can not be a range of cells');
} elseif ($cellAddress == '') {
throw new Exception('Cell coordinate can not be zero-length string');
}
throw new Exception('Invalid cell coordinate ' . $cellAddress);
}
/**
* Convert string coordinate to [0 => int column index, 1 => int row index, 2 => string column string].
*
* @param string $coordinates eg: 'A1', '$B$12'
*
* @return array{0: int, 1: int, 2: string} Array containing column and row index, and column string
*/
public static function indexesFromString(string $coordinates): array
{
[$column, $row] = self::coordinateFromString($coordinates);
$column = ltrim($column, '$');
return [
self::columnIndexFromString($column),
(int) ltrim($row, '$'),
$column,
];
}
/**
* Checks if a Cell Address represents a range of cells.
*
* @param string $cellAddress eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2'
*
* @return bool Whether the coordinate represents a range of cells
*/
public static function coordinateIsRange($cellAddress)
{
return (strpos($cellAddress, ':') !== false) || (strpos($cellAddress, ',') !== false);
}
/**
* Make string row, column or cell coordinate absolute.
*
* @param string $cellAddress e.g. 'A' or '1' or 'A1'
* Note that this value can be a row or column reference as well as a cell reference
*
* @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
*/
public static function absoluteReference($cellAddress)
{
if (self::coordinateIsRange($cellAddress)) {
throw new Exception('Cell coordinate string can not be a range of cells');
}
// Split out any worksheet name from the reference
[$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if ($worksheet > '') {
$worksheet .= '!';
}
// Create absolute coordinate
$cellAddress = "$cellAddress";
if (ctype_digit($cellAddress)) {
return $worksheet . '$' . $cellAddress;
} elseif (ctype_alpha($cellAddress)) {
return $worksheet . '$' . strtoupper($cellAddress);
}
return $worksheet . self::absoluteCoordinate($cellAddress);
}
/**
* Make string coordinate absolute.
*
* @param string $cellAddress e.g. 'A1'
*
* @return string Absolute coordinate e.g. '$A$1'
*/
public static function absoluteCoordinate($cellAddress)
{
if (self::coordinateIsRange($cellAddress)) {
throw new Exception('Cell coordinate string can not be a range of cells');
}
// Split out any worksheet name from the coordinate
[$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
if ($worksheet > '') {
$worksheet .= '!';
}
// Create absolute coordinate
[$column, $row] = self::coordinateFromString($cellAddress);
$column = ltrim($column, '$');
$row = ltrim($row, '$');
return $worksheet . '$' . $column . '$' . $row;
}
/**
* Split range into coordinate strings.
*
* @param string $range e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
*
* @return array Array containing one or more arrays containing one or two coordinate strings
* e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']]
* or ['B4']
*/
public static function splitRange($range)
{
// Ensure $pRange is a valid range
if (empty($range)) {
$range = self::DEFAULT_RANGE;
}
$exploded = explode(',', $range);
$counter = count($exploded);
for ($i = 0; $i < $counter; ++$i) {
// @phpstan-ignore-next-line
$exploded[$i] = explode(':', $exploded[$i]);
}
return $exploded;
}
/**
* Build range from coordinate strings.
*
* @param array $range Array containing one or more arrays containing one or two coordinate strings
*
* @return string String representation of $pRange
*/
public static function buildRange(array $range)
{
// Verify range
if (empty($range) || !is_array($range[0])) {
throw new Exception('Range does not contain any information');
}
// Build range
$counter = count($range);
for ($i = 0; $i < $counter; ++$i) {
$range[$i] = implode(':', $range[$i]);
}
return implode(',', $range);
}
/**
* Calculate range boundaries.
*
* @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3)
*
* @return array Range coordinates [Start Cell, End Cell]
* where Start Cell and End Cell are arrays (Column Number, Row Number)
*/
public static function rangeBoundaries(string $range): array
{
// Ensure $pRange is a valid range
if (empty($range)) {
$range = self::DEFAULT_RANGE;
}
// Uppercase coordinate
$range = strtoupper($range);
// Extract range
if (strpos($range, ':') === false) {
$rangeA = $rangeB = $range;
} else {
[$rangeA, $rangeB] = explode(':', $range);
}
if (is_numeric($rangeA) && is_numeric($rangeB)) {
$rangeA = 'A' . $rangeA;
$rangeB = AddressRange::MAX_COLUMN . $rangeB;
}
if (ctype_alpha($rangeA) && ctype_alpha($rangeB)) {
$rangeA = $rangeA . '1';
$rangeB = $rangeB . AddressRange::MAX_ROW;
}
// Calculate range outer borders
$rangeStart = self::coordinateFromString($rangeA);
$rangeEnd = self::coordinateFromString($rangeB);
// Translate column into index
$rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
$rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
return [$rangeStart, $rangeEnd];
}
/**
* Calculate range dimension.
*
* @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3)
*
* @return array Range dimension (width, height)
*/
public static function rangeDimension($range)
{
// Calculate range outer borders
[$rangeStart, $rangeEnd] = self::rangeBoundaries($range);
return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
}
/**
* Calculate range boundaries.
*
* @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3)
*
* @return array Range coordinates [Start Cell, End Cell]
* where Start Cell and End Cell are arrays [Column ID, Row Number]
*/
public static function getRangeBoundaries($range)
{
[$rangeA, $rangeB] = self::rangeBoundaries($range);
return [
[self::stringFromColumnIndex($rangeA[0]), $rangeA[1]],
[self::stringFromColumnIndex($rangeB[0]), $rangeB[1]],
];
}
/**
* Column index from string.
*
* @param ?string $columnAddress eg 'A'
*
* @return int Column index (A = 1)
*/
public static function columnIndexFromString($columnAddress)
{
// Using a lookup cache adds a slight memory overhead, but boosts speed
// caching using a static within the method is faster than a class static,
// though it's additional memory overhead
static $indexCache = [];
$columnAddress = $columnAddress ?? '';
if (isset($indexCache[$columnAddress])) {
return $indexCache[$columnAddress];
}
// It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array
// rather than use ord() and make it case insensitive to get rid of the strtoupper() as well.
// Because it's a static, there's no significant memory overhead either.
static $columnLookup = [
'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10,
'K' => 11, 'L' => 12, 'M' => 13, 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19,
'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10,
'k' => 11, 'l' => 12, 'm' => 13, 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19,
't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26,
];
// We also use the language construct isset() rather than the more costly strlen() function to match the
// length of $columnAddress for improved performance
if (isset($columnAddress[0])) {
if (!isset($columnAddress[1])) {
$indexCache[$columnAddress] = $columnLookup[$columnAddress];
return $indexCache[$columnAddress];
} elseif (!isset($columnAddress[2])) {
$indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 26
+ $columnLookup[$columnAddress[1]];
return $indexCache[$columnAddress];
} elseif (!isset($columnAddress[3])) {
$indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 676
+ $columnLookup[$columnAddress[1]] * 26
+ $columnLookup[$columnAddress[2]];
return $indexCache[$columnAddress];
}
}
throw new Exception(
'Column string index can not be ' . ((isset($columnAddress[0])) ? 'longer than 3 characters' : 'empty')
);
}
/**
* String from column index.
*
* @param int $columnIndex Column index (A = 1)
*
* @return string
*/
public static function stringFromColumnIndex($columnIndex)
{
static $indexCache = [];
static $lookupCache = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (!isset($indexCache[$columnIndex])) {
$indexValue = $columnIndex;
$base26 = '';
do {
$characterValue = ($indexValue % 26) ?: 26;
$indexValue = ($indexValue - $characterValue) / 26;
$base26 = $lookupCache[$characterValue] . $base26;
} while ($indexValue > 0);
$indexCache[$columnIndex] = $base26;
}
return $indexCache[$columnIndex];
}
/**
* Extract all cell references in range, which may be comprised of multiple cell ranges.
*
* @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3'
*
* @return array Array containing single cell references
*/
public static function extractAllCellReferencesInRange($cellRange): array
{
if (substr_count($cellRange, '!') > 1) {
throw new Exception('3-D Range References are not supported');
}
[$worksheet, $cellRange] = Worksheet::extractSheetTitle($cellRange, true);
$quoted = '';
if ($worksheet > '') {
$quoted = Worksheet::nameRequiresQuotes($worksheet) ? "'" : '';
if (substr($worksheet, 0, 1) === "'" && substr($worksheet, -1, 1) === "'") {
$worksheet = substr($worksheet, 1, -1);
}
$worksheet = str_replace("'", "''", $worksheet);
}
[$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange);
$cells = [];
foreach ($ranges as $range) {
$cells[] = self::getReferencesForCellBlock($range);
}
$cells = self::processRangeSetOperators($operators, $cells);
if (empty($cells)) {
return [];
}
$cellList = array_merge(...$cells);
return array_map(
function ($cellAddress) use ($worksheet, $quoted) {
return ($worksheet !== '') ? "{$quoted}{$worksheet}{$quoted}!{$cellAddress}" : $cellAddress;
},
self::sortCellReferenceArray($cellList)
);
}
private static function processRangeSetOperators(array $operators, array $cells): array
{
$operatorCount = count($operators);
for ($offset = 0; $offset < $operatorCount; ++$offset) {
$operator = $operators[$offset];
if ($operator !== ' ') {
continue;
}
$cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]);
unset($operators[$offset], $cells[$offset + 1]);
$operators = array_values($operators);
$cells = array_values($cells);
--$offset;
--$operatorCount;
}
return $cells;
}
private static function sortCellReferenceArray(array $cellList): array
{
// Sort the result by column and row
$sortKeys = [];
foreach ($cellList as $coordinate) {
$column = '';
$row = 0;
sscanf($coordinate, '%[A-Z]%d', $column, $row);
$key = (--$row * 16384) + self::columnIndexFromString((string) $column);
$sortKeys[$key] = $coordinate;
}
ksort($sortKeys);
return array_values($sortKeys);
}
/**
* Get all cell references for an individual cell block.
*
* @param string $cellBlock A cell range e.g. A4:B5
*
* @return array All individual cells in that range
*/
private static function getReferencesForCellBlock($cellBlock)
{
$returnValue = [];
// Single cell?
if (!self::coordinateIsRange($cellBlock)) {
return (array) $cellBlock;
}
// Range...
$ranges = self::splitRange($cellBlock);
foreach ($ranges as $range) {
// Single cell?
if (!isset($range[1])) {
$returnValue[] = $range[0];
continue;
}
// Range...
[$rangeStart, $rangeEnd] = $range;
[$startColumn, $startRow] = self::coordinateFromString($rangeStart);
[$endColumn, $endRow] = self::coordinateFromString($rangeEnd);
$startColumnIndex = self::columnIndexFromString($startColumn);
$endColumnIndex = self::columnIndexFromString($endColumn);
++$endColumnIndex;
// Current data
$currentColumnIndex = $startColumnIndex;
$currentRow = $startRow;
self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, (int) $currentRow, (int) $endRow);
// Loop cells
while ($currentColumnIndex < $endColumnIndex) {
while ($currentRow <= $endRow) {
$returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow;
++$currentRow;
}
++$currentColumnIndex;
$currentRow = $startRow;
}
}
return $returnValue;
}
/**
* Convert an associative array of single cell coordinates to values to an associative array
* of cell ranges to values. Only adjacent cell coordinates with the same
* value will be merged. If the value is an object, it must implement the method getHashCode().
*
* For example, this function converts:
*
* [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ]
*
* to:
*
* [ 'A1:A3' => 'x', 'A4' => 'y' ]
*
* @param array $coordinateCollection associative array mapping coordinates to values
*
* @return array associative array mapping coordinate ranges to valuea
*/
public static function mergeRangesInCollection(array $coordinateCollection)
{
$hashedValues = [];
$mergedCoordCollection = [];
foreach ($coordinateCollection as $coord => $value) {
if (self::coordinateIsRange($coord)) {
$mergedCoordCollection[$coord] = $value;
continue;
}
[$column, $row] = self::coordinateFromString($coord);
$row = (int) (ltrim($row, '$'));
$hashCode = $column . '-' . ((is_object($value) && method_exists($value, 'getHashCode')) ? $value->getHashCode() : $value);
if (!isset($hashedValues[$hashCode])) {
$hashedValues[$hashCode] = (object) [
'value' => $value,
'col' => $column,
'rows' => [$row],
];
} else {
$hashedValues[$hashCode]->rows[] = $row;
}
}
ksort($hashedValues);
foreach ($hashedValues as $hashedValue) {
sort($hashedValue->rows);
$rowStart = null;
$rowEnd = null;
$ranges = [];
foreach ($hashedValue->rows as $row) {
if ($rowStart === null) {
$rowStart = $row;
$rowEnd = $row;
} elseif ($rowEnd === $row - 1) {
$rowEnd = $row;
} else {
if ($rowStart == $rowEnd) {
$ranges[] = $hashedValue->col . $rowStart;
} else {
$ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
}
$rowStart = $row;
$rowEnd = $row;
}
}
if ($rowStart !== null) {
if ($rowStart == $rowEnd) {
$ranges[] = $hashedValue->col . $rowStart;
} else {
$ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
}
}
foreach ($ranges as $range) {
$mergedCoordCollection[$range] = $hashedValue->value;
}
}
return $mergedCoordCollection;
}
/**
* Get the individual cell blocks from a range string, removing any $ characters.
* then splitting by operators and returning an array with ranges and operators.
*
* @param string $rangeString
*
* @return array[]
*/
private static function getCellBlocksFromRangeString($rangeString)
{
$rangeString = str_replace('$', '', strtoupper($rangeString));
// split range sets on intersection (space) or union (,) operators
$tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE);
/** @phpstan-ignore-next-line */
$split = array_chunk($tokens, 2);
$ranges = array_column($split, 0);
$operators = array_column($split, 1);
return [$ranges, $operators];
}
/**
* Check that the given range is valid, i.e. that the start column and row are not greater than the end column and
* row.
*
* @param string $cellBlock The original range, for displaying a meaningful error message
* @param int $startColumnIndex
* @param int $endColumnIndex
* @param int $currentRow
* @param int $endRow
*/
private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow): void
{
if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) {
throw new Exception('Invalid range: "' . $cellBlock . '"');
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php 0000644 00000010021 15243417764 0017055 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class CellAddress
{
/**
* @var ?Worksheet
*/
protected $worksheet;
/**
* @var string
*/
protected $cellAddress;
/**
* @var string
*/
protected $columnName;
/**
* @var int
*/
protected $columnId;
/**
* @var int
*/
protected $rowId;
public function __construct(string $cellAddress, ?Worksheet $worksheet = null)
{
$this->cellAddress = str_replace('$', '', $cellAddress);
[$this->columnId, $this->rowId, $this->columnName] = Coordinate::indexesFromString($this->cellAddress);
$this->worksheet = $worksheet;
}
/**
* @param mixed $columnId
* @param mixed $rowId
*/
private static function validateColumnAndRow($columnId, $rowId): void
{
if (!is_numeric($columnId) || $columnId <= 0 || !is_numeric($rowId) || $rowId <= 0) {
throw new Exception('Row and Column Ids must be positive integer values');
}
}
/**
* @param mixed $columnId
* @param mixed $rowId
*/
public static function fromColumnAndRow($columnId, $rowId, ?Worksheet $worksheet = null): self
{
self::validateColumnAndRow($columnId, $rowId);
/** @phpstan-ignore-next-line */
return new static(Coordinate::stringFromColumnIndex($columnId) . ((string) $rowId), $worksheet);
}
public static function fromColumnRowArray(array $array, ?Worksheet $worksheet = null): self
{
[$columnId, $rowId] = $array;
return static::fromColumnAndRow($columnId, $rowId, $worksheet);
}
/**
* @param mixed $cellAddress
*/
public static function fromCellAddress($cellAddress, ?Worksheet $worksheet = null): self
{
/** @phpstan-ignore-next-line */
return new static($cellAddress, $worksheet);
}
/**
* The returned address string will contain the worksheet name as well, if available,
* (ie. if a Worksheet was provided to the constructor).
* e.g. "'Mark''s Worksheet'!C5".
*/
public function fullCellAddress(): string
{
if ($this->worksheet !== null) {
$title = str_replace("'", "''", $this->worksheet->getTitle());
return "'{$title}'!{$this->cellAddress}";
}
return $this->cellAddress;
}
public function worksheet(): ?Worksheet
{
return $this->worksheet;
}
/**
* The returned address string will contain just the column/row address,
* (even if a Worksheet was provided to the constructor).
* e.g. "C5".
*/
public function cellAddress(): string
{
return $this->cellAddress;
}
public function rowId(): int
{
return $this->rowId;
}
public function columnId(): int
{
return $this->columnId;
}
public function columnName(): string
{
return $this->columnName;
}
public function nextRow(int $offset = 1): self
{
$newRowId = $this->rowId + $offset;
if ($newRowId < 1) {
$newRowId = 1;
}
return static::fromColumnAndRow($this->columnId, $newRowId);
}
public function previousRow(int $offset = 1): self
{
return $this->nextRow(0 - $offset);
}
public function nextColumn(int $offset = 1): self
{
$newColumnId = $this->columnId + $offset;
if ($newColumnId < 1) {
$newColumnId = 1;
}
return static::fromColumnAndRow($newColumnId, $this->rowId);
}
public function previousColumn(int $offset = 1): self
{
return $this->nextColumn(0 - $offset);
}
/**
* The returned address string will contain the worksheet name as well, if available,
* (ie. if a Worksheet was provided to the constructor).
* e.g. "'Mark''s Worksheet'!C5".
*/
public function __toString()
{
return $this->fullCellAddress();
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php 0000644 00000020450 15243417764 0017563 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
class DataValidation
{
// Data validation types
const TYPE_NONE = 'none';
const TYPE_CUSTOM = 'custom';
const TYPE_DATE = 'date';
const TYPE_DECIMAL = 'decimal';
const TYPE_LIST = 'list';
const TYPE_TEXTLENGTH = 'textLength';
const TYPE_TIME = 'time';
const TYPE_WHOLE = 'whole';
// Data validation error styles
const STYLE_STOP = 'stop';
const STYLE_WARNING = 'warning';
const STYLE_INFORMATION = 'information';
// Data validation operators
const OPERATOR_BETWEEN = 'between';
const OPERATOR_EQUAL = 'equal';
const OPERATOR_GREATERTHAN = 'greaterThan';
const OPERATOR_GREATERTHANOREQUAL = 'greaterThanOrEqual';
const OPERATOR_LESSTHAN = 'lessThan';
const OPERATOR_LESSTHANOREQUAL = 'lessThanOrEqual';
const OPERATOR_NOTBETWEEN = 'notBetween';
const OPERATOR_NOTEQUAL = 'notEqual';
/**
* Formula 1.
*
* @var string
*/
private $formula1 = '';
/**
* Formula 2.
*
* @var string
*/
private $formula2 = '';
/**
* Type.
*
* @var string
*/
private $type = self::TYPE_NONE;
/**
* Error style.
*
* @var string
*/
private $errorStyle = self::STYLE_STOP;
/**
* Operator.
*
* @var string
*/
private $operator = self::OPERATOR_BETWEEN;
/**
* Allow Blank.
*
* @var bool
*/
private $allowBlank = false;
/**
* Show DropDown.
*
* @var bool
*/
private $showDropDown = false;
/**
* Show InputMessage.
*
* @var bool
*/
private $showInputMessage = false;
/**
* Show ErrorMessage.
*
* @var bool
*/
private $showErrorMessage = false;
/**
* Error title.
*
* @var string
*/
private $errorTitle = '';
/**
* Error.
*
* @var string
*/
private $error = '';
/**
* Prompt title.
*
* @var string
*/
private $promptTitle = '';
/**
* Prompt.
*
* @var string
*/
private $prompt = '';
/**
* Create a new DataValidation.
*/
public function __construct()
{
}
/**
* Get Formula 1.
*
* @return string
*/
public function getFormula1()
{
return $this->formula1;
}
/**
* Set Formula 1.
*
* @param string $formula
*
* @return $this
*/
public function setFormula1($formula)
{
$this->formula1 = $formula;
return $this;
}
/**
* Get Formula 2.
*
* @return string
*/
public function getFormula2()
{
return $this->formula2;
}
/**
* Set Formula 2.
*
* @param string $formula
*
* @return $this
*/
public function setFormula2($formula)
{
$this->formula2 = $formula;
return $this;
}
/**
* Get Type.
*
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* Set Type.
*
* @param string $type
*
* @return $this
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get Error style.
*
* @return string
*/
public function getErrorStyle()
{
return $this->errorStyle;
}
/**
* Set Error style.
*
* @param string $errorStyle see self::STYLE_*
*
* @return $this
*/
public function setErrorStyle($errorStyle)
{
$this->errorStyle = $errorStyle;
return $this;
}
/**
* Get Operator.
*
* @return string
*/
public function getOperator()
{
return $this->operator;
}
/**
* Set Operator.
*
* @param string $operator
*
* @return $this
*/
public function setOperator($operator)
{
$this->operator = $operator;
return $this;
}
/**
* Get Allow Blank.
*
* @return bool
*/
public function getAllowBlank()
{
return $this->allowBlank;
}
/**
* Set Allow Blank.
*
* @param bool $allowBlank
*
* @return $this
*/
public function setAllowBlank($allowBlank)
{
$this->allowBlank = $allowBlank;
return $this;
}
/**
* Get Show DropDown.
*
* @return bool
*/
public function getShowDropDown()
{
return $this->showDropDown;
}
/**
* Set Show DropDown.
*
* @param bool $showDropDown
*
* @return $this
*/
public function setShowDropDown($showDropDown)
{
$this->showDropDown = $showDropDown;
return $this;
}
/**
* Get Show InputMessage.
*
* @return bool
*/
public function getShowInputMessage()
{
return $this->showInputMessage;
}
/**
* Set Show InputMessage.
*
* @param bool $showInputMessage
*
* @return $this
*/
public function setShowInputMessage($showInputMessage)
{
$this->showInputMessage = $showInputMessage;
return $this;
}
/**
* Get Show ErrorMessage.
*
* @return bool
*/
public function getShowErrorMessage()
{
return $this->showErrorMessage;
}
/**
* Set Show ErrorMessage.
*
* @param bool $showErrorMessage
*
* @return $this
*/
public function setShowErrorMessage($showErrorMessage)
{
$this->showErrorMessage = $showErrorMessage;
return $this;
}
/**
* Get Error title.
*
* @return string
*/
public function getErrorTitle()
{
return $this->errorTitle;
}
/**
* Set Error title.
*
* @param string $errorTitle
*
* @return $this
*/
public function setErrorTitle($errorTitle)
{
$this->errorTitle = $errorTitle;
return $this;
}
/**
* Get Error.
*
* @return string
*/
public function getError()
{
return $this->error;
}
/**
* Set Error.
*
* @param string $error
*
* @return $this
*/
public function setError($error)
{
$this->error = $error;
return $this;
}
/**
* Get Prompt title.
*
* @return string
*/
public function getPromptTitle()
{
return $this->promptTitle;
}
/**
* Set Prompt title.
*
* @param string $promptTitle
*
* @return $this
*/
public function setPromptTitle($promptTitle)
{
$this->promptTitle = $promptTitle;
return $this;
}
/**
* Get Prompt.
*
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Set Prompt.
*
* @param string $prompt
*
* @return $this
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->formula1 .
$this->formula2 .
$this->type .
$this->errorStyle .
$this->operator .
($this->allowBlank ? 't' : 'f') .
($this->showDropDown ? 't' : 'f') .
($this->showInputMessage ? 't' : 'f') .
($this->showErrorMessage ? 't' : 'f') .
$this->errorTitle .
$this->error .
$this->promptTitle .
$this->prompt .
$this->sqref .
__CLASS__
);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
/** @var ?string */
private $sqref;
public function getSqref(): ?string
{
return $this->sqref;
}
public function setSqref(?string $str): self
{
$this->sqref = $str;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php 0000644 00000015122 15243417764 0017424 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Exception;
class AddressHelper
{
public const R1C1_COORDINATE_REGEX = '/(R((?:\[-?\d*\])|(?:\d*))?)(C((?:\[-?\d*\])|(?:\d*))?)/i';
/** @return string[] */
public static function getRowAndColumnChars()
{
$rowChar = 'R';
$colChar = 'C';
if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL) {
$rowColChars = Calculation::localeFunc('*RC');
if (mb_strlen($rowColChars) === 2) {
$rowChar = mb_substr($rowColChars, 0, 1);
$colChar = mb_substr($rowColChars, 1, 1);
}
}
return [$rowChar, $colChar];
}
/**
* Converts an R1C1 format cell address to an A1 format cell address.
*/
public static function convertToA1(
string $address,
int $currentRowNumber = 1,
int $currentColumnNumber = 1,
bool $useLocale = true
): string {
[$rowChar, $colChar] = $useLocale ? self::getRowAndColumnChars() : ['R', 'C'];
$regex = '/^(' . $rowChar . '(\[?[-+]?\d*\]?))(' . $colChar . '(\[?[-+]?\d*\]?))$/i';
$validityCheck = preg_match($regex, $address, $cellReference);
if (empty($validityCheck)) {
throw new Exception('Invalid R1C1-format Cell Reference');
}
$rowReference = $cellReference[2];
// Empty R reference is the current row
if ($rowReference === '') {
$rowReference = (string) $currentRowNumber;
}
// Bracketed R references are relative to the current row
if ($rowReference[0] === '[') {
$rowReference = $currentRowNumber + (int) trim($rowReference, '[]');
}
$columnReference = $cellReference[4];
// Empty C reference is the current column
if ($columnReference === '') {
$columnReference = (string) $currentColumnNumber;
}
// Bracketed C references are relative to the current column
if (is_string($columnReference) && $columnReference[0] === '[') {
$columnReference = $currentColumnNumber + (int) trim($columnReference, '[]');
}
$columnReference = (int) $columnReference;
if ($columnReference <= 0 || $rowReference <= 0) {
throw new Exception('Invalid R1C1-format Cell Reference, Value out of range');
}
$A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference;
return $A1CellReference;
}
protected static function convertSpreadsheetMLFormula(string $formula): string
{
$formula = substr($formula, 3);
$temp = explode('"', $formula);
$key = false;
foreach ($temp as &$value) {
// Only replace in alternate array entries (i.e. non-quoted blocks)
$key = $key === false;
if ($key) {
$value = str_replace(['[.', ':.', ']'], ['', ':', ''], $value);
}
}
unset($value);
return implode('"', $temp);
}
/**
* Converts a formula that uses R1C1/SpreadsheetXML format cell address to an A1 format cell address.
*/
public static function convertFormulaToA1(
string $formula,
int $currentRowNumber = 1,
int $currentColumnNumber = 1
): string {
if (substr($formula, 0, 3) == 'of:') {
// We have an old-style SpreadsheetML Formula
return self::convertSpreadsheetMLFormula($formula);
}
// Convert R1C1 style references to A1 style references (but only when not quoted)
$temp = explode('"', $formula);
$key = false;
foreach ($temp as &$value) {
// Only replace in alternate array entries (i.e. non-quoted blocks)
$key = $key === false;
if ($key) {
preg_match_all(self::R1C1_COORDINATE_REGEX, $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
// Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
// through the formula from left to right. Reversing means that we work right to left.through
// the formula
$cellReferences = array_reverse($cellReferences);
// Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
// then modify the formula to use that new reference
foreach ($cellReferences as $cellReference) {
$A1CellReference = self::convertToA1($cellReference[0][0], $currentRowNumber, $currentColumnNumber, false);
$value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
}
}
}
unset($value);
// Then rebuild the formula string
return implode('"', $temp);
}
/**
* Converts an A1 format cell address to an R1C1 format cell address.
* If $currentRowNumber or $currentColumnNumber are provided, then the R1C1 address will be formatted as a relative address.
*/
public static function convertToR1C1(
string $address,
?int $currentRowNumber = null,
?int $currentColumnNumber = null
): string {
$validityCheck = preg_match(Coordinate::A1_COORDINATE_REGEX, $address, $cellReference);
if ($validityCheck === 0) {
throw new Exception('Invalid A1-format Cell Reference');
}
if ($cellReference['col'][0] === '$') {
// Column must be absolute address
$currentColumnNumber = null;
}
$columnId = Coordinate::columnIndexFromString(ltrim($cellReference['col'], '$'));
if ($cellReference['row'][0] === '$') {
// Row must be absolute address
$currentRowNumber = null;
}
$rowId = (int) ltrim($cellReference['row'], '$');
if ($currentRowNumber !== null) {
if ($rowId === $currentRowNumber) {
$rowId = '';
} else {
$rowId = '[' . ($rowId - $currentRowNumber) . ']';
}
}
if ($currentColumnNumber !== null) {
if ($columnId === $currentColumnNumber) {
$columnId = '';
} else {
$columnId = '[' . ($columnId - $currentColumnNumber) . ']';
}
}
$R1C1Address = "R{$rowId}C{$columnId}";
return $R1C1Address;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php 0000644 00000002256 15243417764 0017467 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
class IgnoredErrors
{
/** @var bool */
private $numberStoredAsText = false;
/** @var bool */
private $formula = false;
/** @var bool */
private $twoDigitTextYear = false;
/** @var bool */
private $evalError = false;
public function setNumberStoredAsText(bool $value): self
{
$this->numberStoredAsText = $value;
return $this;
}
public function getNumberStoredAsText(): bool
{
return $this->numberStoredAsText;
}
public function setFormula(bool $value): self
{
$this->formula = $value;
return $this;
}
public function getFormula(): bool
{
return $this->formula;
}
public function setTwoDigitTextYear(bool $value): self
{
$this->twoDigitTextYear = $value;
return $this;
}
public function getTwoDigitTextYear(): bool
{
return $this->twoDigitTextYear;
}
public function setEvalError(bool $value): self
{
$this->evalError = $value;
return $this;
}
public function getEvalError(): bool
{
return $this->evalError;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php 0000644 00000006003 15243417764 0017107 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ColumnRange implements AddressRange
{
/**
* @var ?Worksheet
*/
protected $worksheet;
/**
* @var int
*/
protected $from;
/**
* @var int
*/
protected $to;
public function __construct(string $from, ?string $to = null, ?Worksheet $worksheet = null)
{
$this->validateFromTo(
Coordinate::columnIndexFromString($from),
Coordinate::columnIndexFromString($to ?? $from)
);
$this->worksheet = $worksheet;
}
public static function fromColumnIndexes(int $from, int $to, ?Worksheet $worksheet = null): self
{
return new self(Coordinate::stringFromColumnIndex($from), Coordinate::stringFromColumnIndex($to), $worksheet);
}
/**
* @param array<int|string> $array
*/
public static function fromArray(array $array, ?Worksheet $worksheet = null): self
{
array_walk(
$array,
function (&$column): void {
$column = is_numeric($column) ? Coordinate::stringFromColumnIndex((int) $column) : $column;
}
);
/** @var string $from */
/** @var string $to */
[$from, $to] = $array;
return new self($from, $to, $worksheet);
}
private function validateFromTo(int $from, int $to): void
{
// Identify actual top and bottom values (in case we've been given bottom and top)
$this->from = min($from, $to);
$this->to = max($from, $to);
}
public function columnCount(): int
{
return $this->to - $this->from + 1;
}
public function shiftDown(int $offset = 1): self
{
$newFrom = $this->from + $offset;
$newFrom = ($newFrom < 1) ? 1 : $newFrom;
$newTo = $this->to + $offset;
$newTo = ($newTo < 1) ? 1 : $newTo;
return self::fromColumnIndexes($newFrom, $newTo, $this->worksheet);
}
public function shiftUp(int $offset = 1): self
{
return $this->shiftDown(0 - $offset);
}
public function from(): string
{
return Coordinate::stringFromColumnIndex($this->from);
}
public function to(): string
{
return Coordinate::stringFromColumnIndex($this->to);
}
public function fromIndex(): int
{
return $this->from;
}
public function toIndex(): int
{
return $this->to;
}
public function toCellRange(): CellRange
{
return new CellRange(
CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet),
CellAddress::fromColumnAndRow($this->to, AddressRange::MAX_ROW)
);
}
public function __toString(): string
{
$from = $this->from();
$to = $this->to();
if ($this->worksheet !== null) {
$title = str_replace("'", "''", $this->worksheet->getTitle());
return "'{$title}'!{$from}:{$to}";
}
return "{$from}:{$to}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php 0000644 00000000555 15243417764 0017245 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
interface AddressRange
{
public const MAX_ROW = 1048576;
public const MAX_COLUMN = 'XFD';
public const MAX_COLUMN_INT = 16384;
/**
* @return mixed
*/
public function from();
/**
* @return mixed
*/
public function to();
public function __toString(): string;
}
phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php 0000644 00000006615 15243417764 0020275 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use DateTimeInterface;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class StringValueBinder implements IValueBinder
{
/**
* @var bool
*/
protected $convertNull = true;
/**
* @var bool
*/
protected $convertBoolean = true;
/**
* @var bool
*/
protected $convertNumeric = true;
/**
* @var bool
*/
protected $convertFormula = true;
public function setNullConversion(bool $suppressConversion = false): self
{
$this->convertNull = $suppressConversion;
return $this;
}
public function setBooleanConversion(bool $suppressConversion = false): self
{
$this->convertBoolean = $suppressConversion;
return $this;
}
public function getBooleanConversion(): bool
{
return $this->convertBoolean;
}
public function setNumericConversion(bool $suppressConversion = false): self
{
$this->convertNumeric = $suppressConversion;
return $this;
}
public function setFormulaConversion(bool $suppressConversion = false): self
{
$this->convertFormula = $suppressConversion;
return $this;
}
public function setConversionForAllValueTypes(bool $suppressConversion = false): self
{
$this->convertNull = $suppressConversion;
$this->convertBoolean = $suppressConversion;
$this->convertNumeric = $suppressConversion;
$this->convertFormula = $suppressConversion;
return $this;
}
/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*/
public function bindValue(Cell $cell, $value)
{
if (is_object($value)) {
return $this->bindObjectValue($cell, $value);
}
// sanitize UTF-8 strings
if (is_string($value)) {
$value = StringHelper::sanitizeUTF8($value);
}
if ($value === null && $this->convertNull === false) {
$cell->setValueExplicit($value, DataType::TYPE_NULL);
} elseif (is_bool($value) && $this->convertBoolean === false) {
$cell->setValueExplicit($value, DataType::TYPE_BOOL);
} elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) {
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) {
$cell->setValueExplicit($value, DataType::TYPE_FORMULA);
} else {
if (is_string($value) && strlen($value) > 1 && $value[0] === '=') {
$cell->getStyle()->setQuotePrefix(true);
}
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
}
return true;
}
protected function bindObjectValue(Cell $cell, object $value): bool
{
// Handle any objects that might be injected
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
} elseif ($value instanceof RichText) {
$cell->setValueExplicit($value, DataType::TYPE_INLINE);
return true;
}
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING); // @phpstan-ignore-line
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php 0000644 00000020430 15243417764 0020523 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
{
/**
* Bind value to a cell.
*
* @param Cell $cell Cell to bind value to
* @param mixed $value Value to bind in cell
*
* @return bool
*/
public function bindValue(Cell $cell, $value = null)
{
if ($value === null) {
return parent::bindValue($cell, $value);
} elseif (is_string($value)) {
// sanitize UTF-8 strings
$value = StringHelper::sanitizeUTF8($value);
}
// Find out data type
$dataType = parent::dataTypeForValue($value);
// Style logic - strings
if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
// Test for booleans using locale-setting
if (StringHelper::strToUpper($value) === Calculation::getTRUE()) {
$cell->setValueExplicit(true, DataType::TYPE_BOOL);
return true;
} elseif (StringHelper::strToUpper($value) === Calculation::getFALSE()) {
$cell->setValueExplicit(false, DataType::TYPE_BOOL);
return true;
}
// Check for fractions
if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) {
return $this->setProperFraction($matches, $cell);
} elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) {
return $this->setImproperFraction($matches, $cell);
}
$decimalSeparatorNoPreg = StringHelper::getDecimalSeparator();
$decimalSeparator = preg_quote($decimalSeparatorNoPreg, '/');
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
// Check for percentage
if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
return $this->setPercentage(preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell);
}
// Check for currency
if (preg_match(FormattedNumber::currencyMatcherRegexp(), preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) {
// Convert value to number
$sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null;
$currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'];
$value = (float) ($sign . trim(str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
return $this->setCurrency($value, $cell, $currencyCode); // @phpstan-ignore-line
}
// Check for time without seconds e.g. '9:45', '09:45'
if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) {
return $this->setTimeHoursMinutes($value, $cell);
}
// Check for time with seconds '9:45:59', '09:45:59'
if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) {
return $this->setTimeHoursMinutesSeconds($value, $cell);
}
// Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10'
if (($d = Date::stringToExcel($value)) !== false) {
// Convert value to number
$cell->setValueExplicit($d, DataType::TYPE_NUMERIC);
// Determine style. Either there is a time part or not. Look for ':'
if (strpos($value, ':') !== false) {
$formatCode = 'yyyy-mm-dd h:mm';
} else {
$formatCode = 'yyyy-mm-dd';
}
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode($formatCode);
return true;
}
// Check for newline character "\n"
if (strpos($value, "\n") !== false) {
$cell->setValueExplicit($value, DataType::TYPE_STRING);
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getAlignment()->setWrapText(true);
return true;
}
}
// Not bound yet? Use parent...
return parent::bindValue($cell, $value);
}
protected function setImproperFraction(array $matches, Cell $cell): bool
{
// Convert value to number
$value = $matches[2] + ($matches[3] / $matches[4]);
if ($matches[1] === '-') {
$value = 0 - $value;
}
$cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
// Build the number format mask based on the size of the matched values
$dividend = str_repeat('?', strlen($matches[3]));
$divisor = str_repeat('?', strlen($matches[4]));
$fractionMask = "# {$dividend}/{$divisor}";
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode($fractionMask);
return true;
}
protected function setProperFraction(array $matches, Cell $cell): bool
{
// Convert value to number
$value = $matches[2] / $matches[3];
if ($matches[1] === '-') {
$value = 0 - $value;
}
$cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC);
// Build the number format mask based on the size of the matched values
$dividend = str_repeat('?', strlen($matches[2]));
$divisor = str_repeat('?', strlen($matches[3]));
$fractionMask = "{$dividend}/{$divisor}";
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode($fractionMask);
return true;
}
protected function setPercentage(string $value, Cell $cell): bool
{
// Convert value to number
$value = ((float) str_replace('%', '', $value)) / 100;
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00);
return true;
}
protected function setCurrency(float $value, Cell $cell, string $currencyCode): bool
{
$cell->setValueExplicit($value, DataType::TYPE_NUMERIC);
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode(
str_replace('$', '[$' . $currencyCode . ']', NumberFormat::FORMAT_CURRENCY_USD)
);
return true;
}
protected function setTimeHoursMinutes(string $value, Cell $cell): bool
{
// Convert value to number
[$hours, $minutes] = explode(':', $value);
$hours = (int) $hours;
$minutes = (int) $minutes;
$days = ($hours / 24) + ($minutes / 1440);
$cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3);
return true;
}
protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool
{
// Convert value to number
[$hours, $minutes, $seconds] = explode(':', $value);
$hours = (int) $hours;
$minutes = (int) $minutes;
$seconds = (int) $seconds;
$days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400);
$cell->setValueExplicit($days, DataType::TYPE_NUMERIC);
// Set style
$cell->getWorksheet()->getStyle($cell->getCoordinate())
->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4);
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php 0000644 00000004223 15243417764 0016423 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class RowRange implements AddressRange
{
/**
* @var ?Worksheet
*/
protected $worksheet;
/**
* @var int
*/
protected $from;
/**
* @var int
*/
protected $to;
public function __construct(int $from, ?int $to = null, ?Worksheet $worksheet = null)
{
$this->validateFromTo($from, $to ?? $from);
$this->worksheet = $worksheet;
}
public static function fromArray(array $array, ?Worksheet $worksheet = null): self
{
[$from, $to] = $array;
return new self($from, $to, $worksheet);
}
private function validateFromTo(int $from, int $to): void
{
// Identify actual top and bottom values (in case we've been given bottom and top)
$this->from = min($from, $to);
$this->to = max($from, $to);
}
public function from(): int
{
return $this->from;
}
public function to(): int
{
return $this->to;
}
public function rowCount(): int
{
return $this->to - $this->from + 1;
}
public function shiftRight(int $offset = 1): self
{
$newFrom = $this->from + $offset;
$newFrom = ($newFrom < 1) ? 1 : $newFrom;
$newTo = $this->to + $offset;
$newTo = ($newTo < 1) ? 1 : $newTo;
return new self($newFrom, $newTo, $this->worksheet);
}
public function shiftLeft(int $offset = 1): self
{
return $this->shiftRight(0 - $offset);
}
public function toCellRange(): CellRange
{
return new CellRange(
CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString('A'), $this->from, $this->worksheet),
CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(AddressRange::MAX_COLUMN), $this->to)
);
}
public function __toString(): string
{
if ($this->worksheet !== null) {
$title = str_replace("'", "''", $this->worksheet->getTitle());
return "'{$title}'!{$this->from}:{$this->to}";
}
return "{$this->from}:{$this->to}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php 0000644 00000004240 15243417764 0016411 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class DataType
{
// Data types
const TYPE_STRING2 = 'str';
const TYPE_STRING = 's';
const TYPE_FORMULA = 'f';
const TYPE_NUMERIC = 'n';
const TYPE_BOOL = 'b';
const TYPE_NULL = 'null';
const TYPE_INLINE = 'inlineStr';
const TYPE_ERROR = 'e';
const TYPE_ISO_DATE = 'd';
/**
* List of error codes.
*
* @var array<string, int>
*/
private static $errorCodes = [
'#NULL!' => 0,
'#DIV/0!' => 1,
'#VALUE!' => 2,
'#REF!' => 3,
'#NAME?' => 4,
'#NUM!' => 5,
'#N/A' => 6,
'#CALC!' => 7,
];
public const MAX_STRING_LENGTH = 32767;
/**
* Get list of error codes.
*
* @return array<string, int>
*/
public static function getErrorCodes()
{
return self::$errorCodes;
}
/**
* Check a string that it satisfies Excel requirements.
*
* @param null|RichText|string $textValue Value to sanitize to an Excel string
*
* @return RichText|string Sanitized value
*/
public static function checkString($textValue)
{
if ($textValue instanceof RichText) {
// TODO: Sanitize Rich-Text string (max. character count is 32,767)
return $textValue;
}
// string must never be longer than 32,767 characters, truncate if necessary
$textValue = StringHelper::substring((string) $textValue, 0, self::MAX_STRING_LENGTH);
// we require that newline is represented as "\n" in core, not as "\r\n" or "\r"
$textValue = str_replace(["\r\n", "\r"], "\n", $textValue);
return $textValue;
}
/**
* Check a value that it is a valid error code.
*
* @param mixed $value Value to sanitize to an Excel error code
*
* @return string Sanitized value
*/
public static function checkErrorCode($value)
{
$value = (string) $value;
if (!isset(self::$errorCodes[$value])) {
$value = '#NULL!';
}
return $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php 0000644 00000054377 15243417764 0015575 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Collection\Cells;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Cell
{
/**
* Value binder to use.
*
* @var IValueBinder
*/
private static $valueBinder;
/**
* Value of the cell.
*
* @var mixed
*/
private $value;
/**
* Calculated value of the cell (used for caching)
* This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
* create the original spreadsheet file.
* Note that this value is not guaranteed to reflect the actual calculated value because it is
* possible that auto-calculation was disabled in the original spreadsheet, and underlying data
* values used by the formula have changed since it was last calculated.
*
* @var mixed
*/
private $calculatedValue;
/**
* Type of the cell data.
*
* @var string
*/
private $dataType;
/**
* The collection of cells that this cell belongs to (i.e. The Cell Collection for the parent Worksheet).
*
* @var ?Cells
*/
private $parent;
/**
* Index to the cellXf reference for the styling of this cell.
*
* @var int
*/
private $xfIndex = 0;
/**
* Attributes of the formula.
*
* @var mixed
*/
private $formulaAttributes;
/** @var IgnoredErrors */
private $ignoredErrors;
/**
* Update the cell into the cell collection.
*
* @return $this
*/
public function updateInCollection(): self
{
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot update when cell is not bound to a worksheet');
}
$parent->update($this);
return $this;
}
public function detach(): void
{
$this->parent = null;
}
public function attach(Cells $parent): void
{
$this->parent = $parent;
}
/**
* Create a new Cell.
*
* @param mixed $value
*/
public function __construct($value, ?string $dataType, Worksheet $worksheet)
{
// Initialise cell value
$this->value = $value;
// Set worksheet cache
$this->parent = $worksheet->getCellCollection();
// Set datatype?
if ($dataType !== null) {
if ($dataType == DataType::TYPE_STRING2) {
$dataType = DataType::TYPE_STRING;
}
$this->dataType = $dataType;
} elseif (self::getValueBinder()->bindValue($this, $value) === false) {
throw new Exception('Value could not be bound to cell.');
}
$this->ignoredErrors = new IgnoredErrors();
}
/**
* Get cell coordinate column.
*
* @return string
*/
public function getColumn()
{
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot get column when cell is not bound to a worksheet');
}
return $parent->getCurrentColumn();
}
/**
* Get cell coordinate row.
*
* @return int
*/
public function getRow()
{
$parent = $this->parent;
if ($parent === null) {
throw new Exception('Cannot get row when cell is not bound to a worksheet');
}
return $parent->getCurrentRow();
}
/**
* Get cell coordinate.
*
* @return string
*/
public function getCoordinate()
{
$parent = $this->parent;
if ($parent !== null) {
$coordinate = $parent->getCurrentCoordinate();
} else {
$coordinate = null;
}
if ($coordinate === null) {
throw new Exception('Coordinate no longer exists');
}
return $coordinate;
}
/**
* Get cell value.
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* Get cell value with formatting.
*/
public function getFormattedValue(): string
{
return (string) NumberFormat::toFormattedString(
$this->getCalculatedValue(),
(string) $this->getStyle()->getNumberFormat()->getFormatCode()
);
}
/**
* @param mixed $oldValue
* @param mixed $newValue
*/
protected static function updateIfCellIsTableHeader(?Worksheet $workSheet, self $cell, $oldValue, $newValue): void
{
if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '') || $workSheet === null) {
return;
}
foreach ($workSheet->getTableCollection() as $table) {
/** @var Table $table */
if ($cell->isInRange($table->getRange())) {
$rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange());
if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) {
Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue);
}
return;
}
}
}
/**
* Set cell value.
*
* Sets the value for a cell, automatically determining the datatype using the value binder
*
* @param mixed $value Value
* @param null|IValueBinder $binder Value Binder to override the currently set Value Binder
*
* @throws Exception
*
* @return $this
*/
public function setValue($value, ?IValueBinder $binder = null): self
{
$binder ??= self::getValueBinder();
if (!$binder->bindValue($this, $value)) {
throw new Exception('Value could not be bound to cell.');
}
return $this;
}
/**
* Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder).
*
* @param mixed $value Value
* @param string $dataType Explicit data type, see DataType::TYPE_*
* Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this
* method, then it is your responsibility as an end-user developer to validate that the value and
* the datatype match.
* If you do mismatch value and datatype, then the value you enter may be changed to match the datatype
* that you specify.
*
* @return Cell
*/
public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING)
{
$oldValue = $this->value;
// set the value according to data type
switch ($dataType) {
case DataType::TYPE_NULL:
$this->value = null;
break;
case DataType::TYPE_STRING2:
$dataType = DataType::TYPE_STRING;
// no break
case DataType::TYPE_STRING:
// Synonym for string
case DataType::TYPE_INLINE:
// Rich text
$this->value = DataType::checkString($value);
break;
case DataType::TYPE_NUMERIC:
if (is_string($value) && !is_numeric($value)) {
throw new Exception('Invalid numeric value for datatype Numeric');
}
$this->value = 0 + $value;
break;
case DataType::TYPE_FORMULA:
$this->value = (string) $value;
break;
case DataType::TYPE_BOOL:
$this->value = (bool) $value;
break;
case DataType::TYPE_ISO_DATE:
$this->value = SharedDate::convertIsoDate($value);
$dataType = DataType::TYPE_NUMERIC;
break;
case DataType::TYPE_ERROR:
$this->value = DataType::checkErrorCode($value);
break;
default:
throw new Exception('Invalid datatype: ' . $dataType);
}
// set the datatype
$this->dataType = $dataType;
$this->updateInCollection();
$cellCoordinate = $this->getCoordinate();
self::updateIfCellIsTableHeader($this->getParent()->getParent(), $this, $oldValue, $value); // @phpstan-ignore-line
return $this->getParent()->get($cellCoordinate); // @phpstan-ignore-line
}
public const CALCULATE_DATE_TIME_ASIS = 0;
public const CALCULATE_DATE_TIME_FLOAT = 1;
public const CALCULATE_TIME_FLOAT = 2;
/** @var int */
private static $calculateDateTimeType = self::CALCULATE_DATE_TIME_ASIS;
public static function getCalculateDateTimeType(): int
{
return self::$calculateDateTimeType;
}
public static function setCalculateDateTimeType(int $calculateDateTimeType): void
{
switch ($calculateDateTimeType) {
case self::CALCULATE_DATE_TIME_ASIS:
case self::CALCULATE_DATE_TIME_FLOAT:
case self::CALCULATE_TIME_FLOAT:
self::$calculateDateTimeType = $calculateDateTimeType;
break;
default:
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception("Invalid value $calculateDateTimeType for calculated date time type");
}
}
/**
* Convert date, time, or datetime from int to float if desired.
*
* @param mixed $result
*
* @return mixed
*/
private function convertDateTimeInt($result)
{
if (is_int($result)) {
if (self::$calculateDateTimeType === self::CALCULATE_TIME_FLOAT) {
if (SharedDate::isDateTime($this, $result, false)) {
$result = (float) $result;
}
} elseif (self::$calculateDateTimeType === self::CALCULATE_DATE_TIME_FLOAT) {
if (SharedDate::isDateTime($this, $result, true)) {
$result = (float) $result;
}
}
}
return $result;
}
/**
* Get calculated cell value.
*
* @param bool $resetLog Whether the calculation engine logger should be reset or not
*
* @return mixed
*/
public function getCalculatedValue(bool $resetLog = true)
{
if ($this->dataType === DataType::TYPE_FORMULA) {
try {
$index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex();
$selected = $this->getWorksheet()->getSelectedCells();
$result = Calculation::getInstance(
$this->getWorksheet()->getParent()
)->calculateCellValue($this, $resetLog);
$result = $this->convertDateTimeInt($result);
$this->getWorksheet()->setSelectedCells($selected);
$this->getWorksheet()->getParentOrThrow()->setActiveSheetIndex($index);
// We don't yet handle array returns
if (is_array($result)) {
while (is_array($result)) {
$result = array_shift($result);
}
}
} catch (Exception $ex) {
if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
return $this->calculatedValue; // Fallback for calculations referencing external files.
} elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) {
return ExcelError::NAME();
}
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
$this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage(),
$ex->getCode(),
$ex
);
}
if ($result === '#Not Yet Implemented') {
return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
}
return $result;
} elseif ($this->value instanceof RichText) {
return $this->value->getPlainText();
}
return $this->convertDateTimeInt($this->value);
}
/**
* Set old calculated value (cached).
*
* @param mixed $originalValue Value
*/
public function setCalculatedValue($originalValue): self
{
if ($originalValue !== null) {
$this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue;
}
return $this->updateInCollection();
}
/**
* Get old calculated value (cached)
* This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
* create the original spreadsheet file.
* Note that this value is not guaranteed to reflect the actual calculated value because it is
* possible that auto-calculation was disabled in the original spreadsheet, and underlying data
* values used by the formula have changed since it was last calculated.
*
* @return mixed
*/
public function getOldCalculatedValue()
{
return $this->calculatedValue;
}
/**
* Get cell data type.
*/
public function getDataType(): string
{
return $this->dataType;
}
/**
* Set cell data type.
*
* @param string $dataType see DataType::TYPE_*
*/
public function setDataType($dataType): self
{
if ($dataType == DataType::TYPE_STRING2) {
$dataType = DataType::TYPE_STRING;
}
$this->dataType = $dataType;
return $this->updateInCollection();
}
/**
* Identify if the cell contains a formula.
*/
public function isFormula(): bool
{
return $this->dataType === DataType::TYPE_FORMULA && $this->getStyle()->getQuotePrefix() === false;
}
/**
* Does this cell contain Data validation rules?
*/
public function hasDataValidation(): bool
{
if (!isset($this->parent)) {
throw new Exception('Cannot check for data validation when cell is not bound to a worksheet');
}
return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
}
/**
* Get Data validation rules.
*/
public function getDataValidation(): DataValidation
{
if (!isset($this->parent)) {
throw new Exception('Cannot get data validation for cell that is not bound to a worksheet');
}
return $this->getWorksheet()->getDataValidation($this->getCoordinate());
}
/**
* Set Data validation rules.
*/
public function setDataValidation(?DataValidation $dataValidation = null): self
{
if (!isset($this->parent)) {
throw new Exception('Cannot set data validation for cell that is not bound to a worksheet');
}
$this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation);
return $this->updateInCollection();
}
/**
* Does this cell contain valid value?
*/
public function hasValidValue(): bool
{
$validator = new DataValidator();
return $validator->isValid($this);
}
/**
* Does this cell contain a Hyperlink?
*/
public function hasHyperlink(): bool
{
if (!isset($this->parent)) {
throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
}
return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
}
/**
* Get Hyperlink.
*/
public function getHyperlink(): Hyperlink
{
if (!isset($this->parent)) {
throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
}
return $this->getWorksheet()->getHyperlink($this->getCoordinate());
}
/**
* Set Hyperlink.
*/
public function setHyperlink(?Hyperlink $hyperlink = null): self
{
if (!isset($this->parent)) {
throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
}
$this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink);
return $this->updateInCollection();
}
/**
* Get cell collection.
*
* @return ?Cells
*/
public function getParent()
{
return $this->parent;
}
/**
* Get parent worksheet.
*/
public function getWorksheet(): Worksheet
{
$parent = $this->parent;
if ($parent !== null) {
$worksheet = $parent->getParent();
} else {
$worksheet = null;
}
if ($worksheet === null) {
throw new Exception('Worksheet no longer exists');
}
return $worksheet;
}
public function getWorksheetOrNull(): ?Worksheet
{
$parent = $this->parent;
if ($parent !== null) {
$worksheet = $parent->getParent();
} else {
$worksheet = null;
}
return $worksheet;
}
/**
* Is this cell in a merge range.
*/
public function isInMergeRange(): bool
{
return (bool) $this->getMergeRange();
}
/**
* Is this cell the master (top left cell) in a merge range (that holds the actual data value).
*/
public function isMergeRangeValueCell(): bool
{
if ($mergeRange = $this->getMergeRange()) {
$mergeRange = Coordinate::splitRange($mergeRange);
[$startCell] = $mergeRange[0];
return $this->getCoordinate() === $startCell;
}
return false;
}
/**
* If this cell is in a merge range, then return the range.
*
* @return false|string
*/
public function getMergeRange()
{
foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
if ($this->isInRange($mergeRange)) {
return $mergeRange;
}
}
return false;
}
/**
* Get cell style.
*/
public function getStyle(): Style
{
return $this->getWorksheet()->getStyle($this->getCoordinate());
}
/**
* Get cell style.
*/
public function getAppliedStyle(): Style
{
if ($this->getWorksheet()->conditionalStylesExists($this->getCoordinate()) === false) {
return $this->getStyle();
}
$range = $this->getWorksheet()->getConditionalRange($this->getCoordinate());
if ($range === null) {
return $this->getStyle();
}
$matcher = new CellStyleAssessor($this, $range);
return $matcher->matchConditions($this->getWorksheet()->getConditionalStyles($this->getCoordinate()));
}
/**
* Re-bind parent.
*/
public function rebindParent(Worksheet $parent): self
{
$this->parent = $parent->getCellCollection();
return $this->updateInCollection();
}
/**
* Is cell in a specific range?
*
* @param string $range Cell range (e.g. A1:A1)
*/
public function isInRange(string $range): bool
{
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range);
// Translate properties
$myColumn = Coordinate::columnIndexFromString($this->getColumn());
$myRow = $this->getRow();
// Verify if cell is in range
return ($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow);
}
/**
* Compare 2 cells.
*
* @param Cell $a Cell a
* @param Cell $b Cell b
*
* @return int Result of comparison (always -1 or 1, never zero!)
*/
public static function compareCells(self $a, self $b): int
{
if ($a->getRow() < $b->getRow()) {
return -1;
} elseif ($a->getRow() > $b->getRow()) {
return 1;
} elseif (Coordinate::columnIndexFromString($a->getColumn()) < Coordinate::columnIndexFromString($b->getColumn())) {
return -1;
}
return 1;
}
/**
* Get value binder to use.
*/
public static function getValueBinder(): IValueBinder
{
if (self::$valueBinder === null) {
self::$valueBinder = new DefaultValueBinder();
}
return self::$valueBinder;
}
/**
* Set value binder to use.
*/
public static function setValueBinder(IValueBinder $binder): void
{
self::$valueBinder = $binder;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $propertyName => $propertyValue) {
if ((is_object($propertyValue)) && ($propertyName !== 'parent')) {
$this->$propertyName = clone $propertyValue;
} else {
$this->$propertyName = $propertyValue;
}
}
}
/**
* Get index to cellXf.
*/
public function getXfIndex(): int
{
return $this->xfIndex;
}
/**
* Set index to cellXf.
*/
public function setXfIndex(int $indexValue): self
{
$this->xfIndex = $indexValue;
return $this->updateInCollection();
}
/**
* Set the formula attributes.
*
* @param mixed $attributes
*
* @return $this
*/
public function setFormulaAttributes($attributes): self
{
$this->formulaAttributes = $attributes;
return $this;
}
/**
* Get the formula attributes.
*
* @return mixed
*/
public function getFormulaAttributes()
{
return $this->formulaAttributes;
}
/**
* Convert to string.
*
* @return string
*/
public function __toString()
{
return (string) $this->getValue();
}
public function getIgnoredErrors(): IgnoredErrors
{
return $this->ignoredErrors;
}
}
phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php 0000644 00000003512 15243417764 0016644 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Cell;
class Hyperlink
{
/**
* URL to link the cell to.
*
* @var string
*/
private $url;
/**
* Tooltip to display on the hyperlink.
*
* @var string
*/
private $tooltip;
/**
* Create a new Hyperlink.
*
* @param string $url Url to link the cell to
* @param string $tooltip Tooltip to display on the hyperlink
*/
public function __construct($url = '', $tooltip = '')
{
// Initialise member variables
$this->url = $url;
$this->tooltip = $tooltip;
}
/**
* Get URL.
*
* @return string
*/
public function getUrl()
{
return $this->url;
}
/**
* Set URL.
*
* @param string $url
*
* @return $this
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Get tooltip.
*
* @return string
*/
public function getTooltip()
{
return $this->tooltip;
}
/**
* Set tooltip.
*
* @param string $tooltip
*
* @return $this
*/
public function setTooltip($tooltip)
{
$this->tooltip = $tooltip;
return $this;
}
/**
* Is this hyperlink internal? (to another worksheet).
*
* @return bool
*/
public function isInternal()
{
return strpos($this->url, 'sheet://') !== false;
}
/**
* @return string
*/
public function getTypeHyperlink()
{
return $this->isInternal() ? '' : 'External';
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->url .
$this->tooltip .
__CLASS__
);
}
}
phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php 0000644 00000010406 15243417764 0017656 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class CellReferenceHelper
{
/**
* @var string
*/
protected $beforeCellAddress;
/**
* @var int
*/
protected $beforeColumn;
/**
* @var int
*/
protected $beforeRow;
/**
* @var int
*/
protected $numberOfColumns;
/**
* @var int
*/
protected $numberOfRows;
public function __construct(string $beforeCellAddress = 'A1', int $numberOfColumns = 0, int $numberOfRows = 0)
{
$this->beforeCellAddress = str_replace('$', '', $beforeCellAddress);
$this->numberOfColumns = $numberOfColumns;
$this->numberOfRows = $numberOfRows;
// Get coordinate of $beforeCellAddress
[$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress);
$this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn);
$this->beforeRow = (int) $beforeRow;
}
public function beforeCellAddress(): string
{
return $this->beforeCellAddress;
}
public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool
{
return $this->beforeCellAddress !== $beforeCellAddress ||
$this->numberOfColumns !== $numberOfColumns ||
$this->numberOfRows !== $numberOfRows;
}
public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false): string
{
if (Coordinate::coordinateIsRange($cellReference)) {
throw new Exception('Only single cell references may be passed to this method.');
}
// Get coordinate of $cellReference
[$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference);
$newColumnIndex = (int) Coordinate::columnIndexFromString(str_replace('$', '', $newColumn));
$newRowIndex = (int) str_replace('$', '', $newRow);
$absoluteColumn = $newColumn[0] === '$' ? '$' : '';
$absoluteRow = $newRow[0] === '$' ? '$' : '';
// Verify which parts should be updated
if ($includeAbsoluteReferences === false) {
$updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn);
$updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow);
} else {
$updateColumn = ($newColumnIndex >= $this->beforeColumn);
$updateRow = ($newRowIndex >= $this->beforeRow);
}
// Create new column reference
if ($updateColumn) {
$newColumn = $this->updateColumnReference($newColumnIndex, $absoluteColumn);
}
// Create new row reference
if ($updateRow) {
$newRow = $this->updateRowReference($newRowIndex, $absoluteRow);
}
// Return new reference
return "{$newColumn}{$newRow}";
}
public function cellAddressInDeleteRange(string $cellAddress): bool
{
[$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress);
$cellColumnIndex = Coordinate::columnIndexFromString($cellColumn);
// Is cell within the range of rows/columns if we're deleting
if (
$this->numberOfRows < 0 &&
($cellRow >= ($this->beforeRow + $this->numberOfRows)) &&
($cellRow < $this->beforeRow)
) {
return true;
} elseif (
$this->numberOfColumns < 0 &&
($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) &&
($cellColumnIndex < $this->beforeColumn)
) {
return true;
}
return false;
}
protected function updateColumnReference(int $newColumnIndex, string $absoluteColumn): string
{
$newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));
return "{$absoluteColumn}{$newColumn}";
}
protected function updateRowReference(int $newRowIndex, string $absoluteRow): string
{
$newRow = $newRowIndex + $this->numberOfRows;
$newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow;
return "{$absoluteRow}{$newRow}";
}
}
phpspreadsheet/src/PhpSpreadsheet/Document/Security.php 0000644 00000006004 15243417764 0017404 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Document;
use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
class Security
{
/**
* LockRevision.
*
* @var bool
*/
private $lockRevision = false;
/**
* LockStructure.
*
* @var bool
*/
private $lockStructure = false;
/**
* LockWindows.
*
* @var bool
*/
private $lockWindows = false;
/**
* RevisionsPassword.
*
* @var string
*/
private $revisionsPassword = '';
/**
* WorkbookPassword.
*
* @var string
*/
private $workbookPassword = '';
/**
* Create a new Document Security instance.
*/
public function __construct()
{
}
/**
* Is some sort of document security enabled?
*/
public function isSecurityEnabled(): bool
{
return $this->lockRevision ||
$this->lockStructure ||
$this->lockWindows;
}
public function getLockRevision(): bool
{
return $this->lockRevision;
}
public function setLockRevision(?bool $locked): self
{
if ($locked !== null) {
$this->lockRevision = $locked;
}
return $this;
}
public function getLockStructure(): bool
{
return $this->lockStructure;
}
public function setLockStructure(?bool $locked): self
{
if ($locked !== null) {
$this->lockStructure = $locked;
}
return $this;
}
public function getLockWindows(): bool
{
return $this->lockWindows;
}
public function setLockWindows(?bool $locked): self
{
if ($locked !== null) {
$this->lockWindows = $locked;
}
return $this;
}
public function getRevisionsPassword(): string
{
return $this->revisionsPassword;
}
/**
* Set RevisionsPassword.
*
* @param string $password
* @param bool $alreadyHashed If the password has already been hashed, set this to true
*
* @return $this
*/
public function setRevisionsPassword(?string $password, bool $alreadyHashed = false)
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->revisionsPassword = $password;
}
return $this;
}
public function getWorkbookPassword(): string
{
return $this->workbookPassword;
}
/**
* Set WorkbookPassword.
*
* @param string $password
* @param bool $alreadyHashed If the password has already been hashed, set this to true
*
* @return $this
*/
public function setWorkbookPassword(?string $password, bool $alreadyHashed = false)
{
if ($password !== null) {
if (!$alreadyHashed) {
$password = PasswordHasher::hashPassword($password);
}
$this->workbookPassword = $password;
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php 0000644 00000030455 15243417764 0017740 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Document;
use DateTime;
use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat;
class Properties
{
/** constants */
public const PROPERTY_TYPE_BOOLEAN = 'b';
public const PROPERTY_TYPE_INTEGER = 'i';
public const PROPERTY_TYPE_FLOAT = 'f';
public const PROPERTY_TYPE_DATE = 'd';
public const PROPERTY_TYPE_STRING = 's';
public const PROPERTY_TYPE_UNKNOWN = 'u';
private const VALID_PROPERTY_TYPE_LIST = [
self::PROPERTY_TYPE_BOOLEAN,
self::PROPERTY_TYPE_INTEGER,
self::PROPERTY_TYPE_FLOAT,
self::PROPERTY_TYPE_DATE,
self::PROPERTY_TYPE_STRING,
];
/**
* Creator.
*
* @var string
*/
private $creator = 'Unknown Creator';
/**
* LastModifiedBy.
*
* @var string
*/
private $lastModifiedBy;
/**
* Created.
*
* @var float|int
*/
private $created;
/**
* Modified.
*
* @var float|int
*/
private $modified;
/**
* Title.
*
* @var string
*/
private $title = 'Untitled Spreadsheet';
/**
* Description.
*
* @var string
*/
private $description = '';
/**
* Subject.
*
* @var string
*/
private $subject = '';
/**
* Keywords.
*
* @var string
*/
private $keywords = '';
/**
* Category.
*
* @var string
*/
private $category = '';
/**
* Manager.
*
* @var string
*/
private $manager = '';
/**
* Company.
*
* @var string
*/
private $company = '';
/**
* Custom Properties.
*
* @var array{value: mixed, type: string}[]
*/
private $customProperties = [];
private string $hyperlinkBase = '';
/**
* Create a new Document Properties instance.
*/
public function __construct()
{
// Initialise values
$this->lastModifiedBy = $this->creator;
$this->created = self::intOrFloatTimestamp(null);
$this->modified = $this->created;
}
/**
* Get Creator.
*/
public function getCreator(): string
{
return $this->creator;
}
/**
* Set Creator.
*
* @return $this
*/
public function setCreator(string $creator): self
{
$this->creator = $creator;
return $this;
}
/**
* Get Last Modified By.
*/
public function getLastModifiedBy(): string
{
return $this->lastModifiedBy;
}
/**
* Set Last Modified By.
*
* @return $this
*/
public function setLastModifiedBy(string $modifiedBy): self
{
$this->lastModifiedBy = $modifiedBy;
return $this;
}
/**
* @param null|float|int|string $timestamp
*
* @return float|int
*/
private static function intOrFloatTimestamp($timestamp)
{
if ($timestamp === null) {
$timestamp = (float) (new DateTime())->format('U');
} elseif (is_string($timestamp)) {
if (is_numeric($timestamp)) {
$timestamp = (float) $timestamp;
} else {
$timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp);
$timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp);
$timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp);
$timestamp = (float) (new DateTime($timestamp))->format('U');
}
}
return IntOrFloat::evaluate($timestamp);
}
/**
* Get Created.
*
* @return float|int
*/
public function getCreated()
{
return $this->created;
}
/**
* Set Created.
*
* @param null|float|int|string $timestamp
*
* @return $this
*/
public function setCreated($timestamp): self
{
$this->created = self::intOrFloatTimestamp($timestamp);
return $this;
}
/**
* Get Modified.
*
* @return float|int
*/
public function getModified()
{
return $this->modified;
}
/**
* Set Modified.
*
* @param null|float|int|string $timestamp
*
* @return $this
*/
public function setModified($timestamp): self
{
$this->modified = self::intOrFloatTimestamp($timestamp);
return $this;
}
/**
* Get Title.
*/
public function getTitle(): string
{
return $this->title;
}
/**
* Set Title.
*
* @return $this
*/
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
/**
* Get Description.
*/
public function getDescription(): string
{
return $this->description;
}
/**
* Set Description.
*
* @return $this
*/
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
/**
* Get Subject.
*/
public function getSubject(): string
{
return $this->subject;
}
/**
* Set Subject.
*
* @return $this
*/
public function setSubject(string $subject): self
{
$this->subject = $subject;
return $this;
}
/**
* Get Keywords.
*/
public function getKeywords(): string
{
return $this->keywords;
}
/**
* Set Keywords.
*
* @return $this
*/
public function setKeywords(string $keywords): self
{
$this->keywords = $keywords;
return $this;
}
/**
* Get Category.
*/
public function getCategory(): string
{
return $this->category;
}
/**
* Set Category.
*
* @return $this
*/
public function setCategory(string $category): self
{
$this->category = $category;
return $this;
}
/**
* Get Company.
*/
public function getCompany(): string
{
return $this->company;
}
/**
* Set Company.
*
* @return $this
*/
public function setCompany(string $company): self
{
$this->company = $company;
return $this;
}
/**
* Get Manager.
*/
public function getManager(): string
{
return $this->manager;
}
/**
* Set Manager.
*
* @return $this
*/
public function setManager(string $manager): self
{
$this->manager = $manager;
return $this;
}
/**
* Get a List of Custom Property Names.
*
* @return string[]
*/
public function getCustomProperties(): array
{
return array_keys($this->customProperties);
}
/**
* Check if a Custom Property is defined.
*/
public function isCustomPropertySet(string $propertyName): bool
{
return array_key_exists($propertyName, $this->customProperties);
}
/**
* Get a Custom Property Value.
*
* @return mixed
*/
public function getCustomPropertyValue(string $propertyName)
{
if (isset($this->customProperties[$propertyName])) {
return $this->customProperties[$propertyName]['value'];
}
return null;
}
/**
* Get a Custom Property Type.
*
* @return null|string
*/
public function getCustomPropertyType(string $propertyName)
{
return $this->customProperties[$propertyName]['type'] ?? null;
}
/**
* @param mixed $propertyValue
*/
private function identifyPropertyType($propertyValue): string
{
if (is_float($propertyValue)) {
return self::PROPERTY_TYPE_FLOAT;
}
if (is_int($propertyValue)) {
return self::PROPERTY_TYPE_INTEGER;
}
if (is_bool($propertyValue)) {
return self::PROPERTY_TYPE_BOOLEAN;
}
return self::PROPERTY_TYPE_STRING;
}
/**
* Set a Custom Property.
*
* @param mixed $propertyValue
* @param string $propertyType
* 'i' : Integer
* 'f' : Floating Point
* 's' : String
* 'd' : Date/Time
* 'b' : Boolean
*
* @return $this
*/
public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self
{
if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) {
$propertyType = $this->identifyPropertyType($propertyValue);
}
if (!is_object($propertyValue)) {
$this->customProperties[$propertyName] = [
'value' => self::convertProperty($propertyValue, $propertyType),
'type' => $propertyType,
];
}
return $this;
}
private const PROPERTY_TYPE_ARRAY = [
'i' => self::PROPERTY_TYPE_INTEGER, // Integer
'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer
'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer
'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer
'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer
'int' => self::PROPERTY_TYPE_INTEGER, // Integer
'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer
'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer
'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer
'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer
'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer
'f' => self::PROPERTY_TYPE_FLOAT, // Real Number
'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number
'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number
'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal
's' => self::PROPERTY_TYPE_STRING, // String
'empty' => self::PROPERTY_TYPE_STRING, // Empty
'null' => self::PROPERTY_TYPE_STRING, // Null
'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR
'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR
'bstr' => self::PROPERTY_TYPE_STRING, // Basic String
'd' => self::PROPERTY_TYPE_DATE, // Date and Time
'date' => self::PROPERTY_TYPE_DATE, // Date and Time
'filetime' => self::PROPERTY_TYPE_DATE, // File Time
'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean
'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean
];
private const SPECIAL_TYPES = [
'empty' => '',
'null' => null,
];
/**
* Convert property to form desired by Excel.
*
* @param mixed $propertyValue
*
* @return mixed
*/
public static function convertProperty($propertyValue, string $propertyType)
{
return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType);
}
/**
* Convert property to form desired by Excel.
*
* @param mixed $propertyValue
*
* @return mixed
*/
private static function convertProperty2($propertyValue, string $type)
{
$propertyType = self::convertPropertyType($type);
switch ($propertyType) {
case self::PROPERTY_TYPE_INTEGER:
$intValue = (int) $propertyValue;
return ($type[0] === 'u') ? abs($intValue) : $intValue;
case self::PROPERTY_TYPE_FLOAT:
return (float) $propertyValue;
case self::PROPERTY_TYPE_DATE:
return self::intOrFloatTimestamp($propertyValue);
case self::PROPERTY_TYPE_BOOLEAN:
return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true');
default: // includes string
return $propertyValue;
}
}
public static function convertPropertyType(string $propertyType): string
{
return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN;
}
public function getHyperlinkBase(): string
{
return $this->hyperlinkBase;
}
public function setHyperlinkBase(string $hyperlinkBase): self
{
$this->hyperlinkBase = $hyperlinkBase;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php 0000644 00001076244 15243417764 0016005 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont;
use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\FillPattern;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\CodePage;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\Escher;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLERead;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Shared\Xls as SharedXls;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Worksheet\SheetView;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
// Original file header of ParseXL (used as the base for this class):
// --------------------------------------------------------------------------------
// Adapted from Excel_Spreadsheet_Reader developed by users bizon153,
// trex005, and mmp11 (SourceForge.net)
// https://sourceforge.net/projects/phpexcelreader/
// Primary changes made by canyoncasa (dvc) for ParseXL 1.00 ...
// Modelled moreso after Perl Excel Parse/Write modules
// Added Parse_Excel_Spreadsheet object
// Reads a whole worksheet or tab as row,column array or as
// associated hash of indexed rows and named column fields
// Added variables for worksheet (tab) indexes and names
// Added an object call for loading individual woorksheets
// Changed default indexing defaults to 0 based arrays
// Fixed date/time and percent formats
// Includes patches found at SourceForge...
// unicode patch by nobody
// unpack("d") machine depedency patch by matchy
// boundsheet utf16 patch by bjaenichen
// Renamed functions for shorter names
// General code cleanup and rigor, including <80 column width
// Included a testcase Excel file and PHP example calls
// Code works for PHP 5.x
// Primary changes made by canyoncasa (dvc) for ParseXL 1.10 ...
// http://sourceforge.net/tracker/index.php?func=detail&aid=1466964&group_id=99160&atid=623334
// Decoding of formula conditions, results, and tokens.
// Support for user-defined named cells added as an array "namedcells"
// Patch code for user-defined named cells supports single cells only.
// NOTE: this patch only works for BIFF8 as BIFF5-7 use a different
// external sheet reference structure
class Xls extends BaseReader
{
// ParseXL definitions
const XLS_BIFF8 = 0x0600;
const XLS_BIFF7 = 0x0500;
const XLS_WORKBOOKGLOBALS = 0x0005;
const XLS_WORKSHEET = 0x0010;
// record identifiers
const XLS_TYPE_FORMULA = 0x0006;
const XLS_TYPE_EOF = 0x000a;
const XLS_TYPE_PROTECT = 0x0012;
const XLS_TYPE_OBJECTPROTECT = 0x0063;
const XLS_TYPE_SCENPROTECT = 0x00dd;
const XLS_TYPE_PASSWORD = 0x0013;
const XLS_TYPE_HEADER = 0x0014;
const XLS_TYPE_FOOTER = 0x0015;
const XLS_TYPE_EXTERNSHEET = 0x0017;
const XLS_TYPE_DEFINEDNAME = 0x0018;
const XLS_TYPE_VERTICALPAGEBREAKS = 0x001a;
const XLS_TYPE_HORIZONTALPAGEBREAKS = 0x001b;
const XLS_TYPE_NOTE = 0x001c;
const XLS_TYPE_SELECTION = 0x001d;
const XLS_TYPE_DATEMODE = 0x0022;
const XLS_TYPE_EXTERNNAME = 0x0023;
const XLS_TYPE_LEFTMARGIN = 0x0026;
const XLS_TYPE_RIGHTMARGIN = 0x0027;
const XLS_TYPE_TOPMARGIN = 0x0028;
const XLS_TYPE_BOTTOMMARGIN = 0x0029;
const XLS_TYPE_PRINTGRIDLINES = 0x002b;
const XLS_TYPE_FILEPASS = 0x002f;
const XLS_TYPE_FONT = 0x0031;
const XLS_TYPE_CONTINUE = 0x003c;
const XLS_TYPE_PANE = 0x0041;
const XLS_TYPE_CODEPAGE = 0x0042;
const XLS_TYPE_DEFCOLWIDTH = 0x0055;
const XLS_TYPE_OBJ = 0x005d;
const XLS_TYPE_COLINFO = 0x007d;
const XLS_TYPE_IMDATA = 0x007f;
const XLS_TYPE_SHEETPR = 0x0081;
const XLS_TYPE_HCENTER = 0x0083;
const XLS_TYPE_VCENTER = 0x0084;
const XLS_TYPE_SHEET = 0x0085;
const XLS_TYPE_PALETTE = 0x0092;
const XLS_TYPE_SCL = 0x00a0;
const XLS_TYPE_PAGESETUP = 0x00a1;
const XLS_TYPE_MULRK = 0x00bd;
const XLS_TYPE_MULBLANK = 0x00be;
const XLS_TYPE_DBCELL = 0x00d7;
const XLS_TYPE_XF = 0x00e0;
const XLS_TYPE_MERGEDCELLS = 0x00e5;
const XLS_TYPE_MSODRAWINGGROUP = 0x00eb;
const XLS_TYPE_MSODRAWING = 0x00ec;
const XLS_TYPE_SST = 0x00fc;
const XLS_TYPE_LABELSST = 0x00fd;
const XLS_TYPE_EXTSST = 0x00ff;
const XLS_TYPE_EXTERNALBOOK = 0x01ae;
const XLS_TYPE_DATAVALIDATIONS = 0x01b2;
const XLS_TYPE_TXO = 0x01b6;
const XLS_TYPE_HYPERLINK = 0x01b8;
const XLS_TYPE_DATAVALIDATION = 0x01be;
const XLS_TYPE_DIMENSION = 0x0200;
const XLS_TYPE_BLANK = 0x0201;
const XLS_TYPE_NUMBER = 0x0203;
const XLS_TYPE_LABEL = 0x0204;
const XLS_TYPE_BOOLERR = 0x0205;
const XLS_TYPE_STRING = 0x0207;
const XLS_TYPE_ROW = 0x0208;
const XLS_TYPE_INDEX = 0x020b;
const XLS_TYPE_ARRAY = 0x0221;
const XLS_TYPE_DEFAULTROWHEIGHT = 0x0225;
const XLS_TYPE_WINDOW2 = 0x023e;
const XLS_TYPE_RK = 0x027e;
const XLS_TYPE_STYLE = 0x0293;
const XLS_TYPE_FORMAT = 0x041e;
const XLS_TYPE_SHAREDFMLA = 0x04bc;
const XLS_TYPE_BOF = 0x0809;
const XLS_TYPE_SHEETPROTECTION = 0x0867;
const XLS_TYPE_RANGEPROTECTION = 0x0868;
const XLS_TYPE_SHEETLAYOUT = 0x0862;
const XLS_TYPE_XFEXT = 0x087d;
const XLS_TYPE_PAGELAYOUTVIEW = 0x088b;
const XLS_TYPE_CFHEADER = 0x01b0;
const XLS_TYPE_CFRULE = 0x01b1;
const XLS_TYPE_UNKNOWN = 0xffff;
// Encryption type
const MS_BIFF_CRYPTO_NONE = 0;
const MS_BIFF_CRYPTO_XOR = 1;
const MS_BIFF_CRYPTO_RC4 = 2;
// Size of stream blocks when using RC4 encryption
const REKEY_BLOCK = 0x400;
/**
* Summary Information stream data.
*
* @var ?string
*/
private $summaryInformation;
/**
* Extended Summary Information stream data.
*
* @var ?string
*/
private $documentSummaryInformation;
/**
* Workbook stream data. (Includes workbook globals substream as well as sheet substreams).
*
* @var string
*/
private $data;
/**
* Size in bytes of $this->data.
*
* @var int
*/
private $dataSize;
/**
* Current position in stream.
*
* @var int
*/
private $pos;
/**
* Workbook to be returned by the reader.
*
* @var Spreadsheet
*/
private $spreadsheet;
/**
* Worksheet that is currently being built by the reader.
*
* @var Worksheet
*/
private $phpSheet;
/**
* BIFF version.
*
* @var int
*/
private $version;
/**
* Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95)
* For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'.
*
* @var string
*/
private $codepage;
/**
* Shared formats.
*
* @var array
*/
private $formats;
/**
* Shared fonts.
*
* @var Font[]
*/
private $objFonts;
/**
* Color palette.
*
* @var array
*/
private $palette;
/**
* Worksheets.
*
* @var array
*/
private $sheets;
/**
* External books.
*
* @var array
*/
private $externalBooks;
/**
* REF structures. Only applies to BIFF8.
*
* @var array
*/
private $ref;
/**
* External names.
*
* @var array
*/
private $externalNames;
/**
* Defined names.
*
* @var array
*/
private $definedname;
/**
* Shared strings. Only applies to BIFF8.
*
* @var array
*/
private $sst;
/**
* Panes are frozen? (in sheet currently being read). See WINDOW2 record.
*
* @var bool
*/
private $frozen;
/**
* Fit printout to number of pages? (in sheet currently being read). See SHEETPR record.
*
* @var bool
*/
private $isFitToPages;
/**
* Objects. One OBJ record contributes with one entry.
*
* @var array
*/
private $objs;
/**
* Text Objects. One TXO record corresponds with one entry.
*
* @var array
*/
private $textObjects;
/**
* Cell Annotations (BIFF8).
*
* @var array
*/
private $cellNotes;
/**
* The combined MSODRAWINGGROUP data.
*
* @var string
*/
private $drawingGroupData;
/**
* The combined MSODRAWING data (per sheet).
*
* @var string
*/
private $drawingData;
/**
* Keep track of XF index.
*
* @var int
*/
private $xfIndex;
/**
* Mapping of XF index (that is a cell XF) to final index in cellXf collection.
*
* @var array
*/
private $mapCellXfIndex;
/**
* Mapping of XF index (that is a style XF) to final index in cellStyleXf collection.
*
* @var array
*/
private $mapCellStyleXfIndex;
/**
* The shared formulas in a sheet. One SHAREDFMLA record contributes with one value.
*
* @var array
*/
private $sharedFormulas;
/**
* The shared formula parts in a sheet. One FORMULA record contributes with one value if it
* refers to a shared formula.
*
* @var array
*/
private $sharedFormulaParts;
/**
* The type of encryption in use.
*
* @var int
*/
private $encryption = 0;
/**
* The position in the stream after which contents are encrypted.
*
* @var int
*/
private $encryptionStartPos = 0;
/**
* The current RC4 decryption object.
*
* @var ?Xls\RC4
*/
private $rc4Key;
/**
* The position in the stream that the RC4 decryption object was left at.
*
* @var int
*/
private $rc4Pos = 0;
/**
* The current MD5 context state.
* It is never set in the program, so code which uses it is suspect.
*
* @var string
*/
private $md5Ctxt; // @phpstan-ignore-line
/**
* @var int
*/
private $textObjRef;
/**
* @var string
*/
private $baseCell;
/**
* Create a new Xls Reader instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
if (File::testFileNoThrow($filename) === false) {
return false;
}
try {
// Use ParseXL for the hard work.
$ole = new OLERead();
// get excel data
$ole->read($filename);
if ($ole->wrkbook === null) {
throw new Exception('The filename ' . $filename . ' is not recognised as a Spreadsheet file');
}
return true;
} catch (PhpSpreadsheetException $e) {
return false;
}
}
public function setCodepage(string $codepage): void
{
if (CodePage::validate($codepage) === false) {
throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage);
}
$this->codepage = $codepage;
}
/**
* Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
*
* @param string $filename
*
* @return array
*/
public function listWorksheetNames($filename)
{
File::assertFile($filename);
$worksheetNames = [];
// Read the OLE file
$this->loadOLE($filename);
// total byte size of Excel data (workbook global substream + sheet substreams)
$this->dataSize = strlen($this->data);
$this->pos = 0;
$this->sheets = [];
// Parse Workbook Global Substream
while ($this->pos < $this->dataSize) {
$code = self::getUInt2d($this->data, $this->pos);
switch ($code) {
case self::XLS_TYPE_BOF:
$this->readBof();
break;
case self::XLS_TYPE_SHEET:
$this->readSheet();
break;
case self::XLS_TYPE_EOF:
$this->readDefault();
break 2;
default:
$this->readDefault();
break;
}
}
foreach ($this->sheets as $sheet) {
if ($sheet['sheetType'] != 0x00) {
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
continue;
}
$worksheetNames[] = $sheet['name'];
}
return $worksheetNames;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
File::assertFile($filename);
$worksheetInfo = [];
// Read the OLE file
$this->loadOLE($filename);
// total byte size of Excel data (workbook global substream + sheet substreams)
$this->dataSize = strlen($this->data);
// initialize
$this->pos = 0;
$this->sheets = [];
// Parse Workbook Global Substream
while ($this->pos < $this->dataSize) {
$code = self::getUInt2d($this->data, $this->pos);
switch ($code) {
case self::XLS_TYPE_BOF:
$this->readBof();
break;
case self::XLS_TYPE_SHEET:
$this->readSheet();
break;
case self::XLS_TYPE_EOF:
$this->readDefault();
break 2;
default:
$this->readDefault();
break;
}
}
// Parse the individual sheets
foreach ($this->sheets as $sheet) {
if ($sheet['sheetType'] != 0x00) {
// 0x00: Worksheet
// 0x02: Chart
// 0x06: Visual Basic module
continue;
}
$tmpInfo = [];
$tmpInfo['worksheetName'] = $sheet['name'];
$tmpInfo['lastColumnLetter'] = 'A';
$tmpInfo['lastColumnIndex'] = 0;
$tmpInfo['totalRows'] = 0;
$tmpInfo['totalColumns'] = 0;
$this->pos = $sheet['offset'];
while ($this->pos <= $this->dataSize - 4) {
$code = self::getUInt2d($this->data, $this->pos);
switch ($code) {
case self::XLS_TYPE_RK:
case self::XLS_TYPE_LABELSST:
case self::XLS_TYPE_NUMBER:
case self::XLS_TYPE_FORMULA:
case self::XLS_TYPE_BOOLERR:
case self::XLS_TYPE_LABEL:
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
$rowIndex = self::getUInt2d($recordData, 0) + 1;
$columnIndex = self::getUInt2d($recordData, 2);
$tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
$tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
break;
case self::XLS_TYPE_BOF:
$this->readBof();
break;
case self::XLS_TYPE_EOF:
$this->readDefault();
break 2;
default:
$this->readDefault();
break;
}
}
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
$tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
$worksheetInfo[] = $tmpInfo;
}
return $worksheetInfo;
}
/**
* Loads PhpSpreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Read the OLE file
$this->loadOLE($filename);
// Initialisations
$this->spreadsheet = new Spreadsheet();
$this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet
if (!$this->readDataOnly) {
$this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style
$this->spreadsheet->removeCellXfByIndex(0); // remove the default style
}
// Read the summary information stream (containing meta data)
$this->readSummaryInformation();
// Read the Additional document summary information stream (containing application-specific meta data)
$this->readDocumentSummaryInformation();
// total byte size of Excel data (workbook global substream + sheet substreams)
$this->dataSize = strlen($this->data);
// initialize
$this->pos = 0;
$this->codepage = $this->codepage ?: CodePage::DEFAULT_CODE_PAGE;
$this->formats = [];
$this->objFonts = [];
$this->palette = [];
$this->sheets = [];
$this->externalBooks = [];
$this->ref = [];
$this->definedname = [];
$this->sst = [];
$this->drawingGroupData = '';
$this->xfIndex = 0;
$this->mapCellXfIndex = [];
$this->mapCellStyleXfIndex = [];
// Parse Workbook Global Substream
while ($this->pos < $this->dataSize) {
$code = self::getUInt2d($this->data, $this->pos);
switch ($code) {
case self::XLS_TYPE_BOF:
$this->readBof();
break;
case self::XLS_TYPE_FILEPASS:
$this->readFilepass();
break;
case self::XLS_TYPE_CODEPAGE:
$this->readCodepage();
break;
case self::XLS_TYPE_DATEMODE:
$this->readDateMode();
break;
case self::XLS_TYPE_FONT:
$this->readFont();
break;
case self::XLS_TYPE_FORMAT:
$this->readFormat();
break;
case self::XLS_TYPE_XF:
$this->readXf();
break;
case self::XLS_TYPE_XFEXT:
$this->readXfExt();
break;
case self::XLS_TYPE_STYLE:
$this->readStyle();
break;
case self::XLS_TYPE_PALETTE:
$this->readPalette();
break;
case self::XLS_TYPE_SHEET:
$this->readSheet();
break;
case self::XLS_TYPE_EXTERNALBOOK:
$this->readExternalBook();
break;
case self::XLS_TYPE_EXTERNNAME:
$this->readExternName();
break;
case self::XLS_TYPE_EXTERNSHEET:
$this->readExternSheet();
break;
case self::XLS_TYPE_DEFINEDNAME:
$this->readDefinedName();
break;
case self::XLS_TYPE_MSODRAWINGGROUP:
$this->readMsoDrawingGroup();
break;
case self::XLS_TYPE_SST:
$this->readSst();
break;
case self::XLS_TYPE_EOF:
$this->readDefault();
break 2;
default:
$this->readDefault();
break;
}
}
// Resolve indexed colors for font, fill, and border colors
// Cannot be resolved already in XF record, because PALETTE record comes afterwards
if (!$this->readDataOnly) {
foreach ($this->objFonts as $objFont) {
if (isset($objFont->colorIndex)) {
$color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version);
$objFont->getColor()->setRGB($color['rgb']);
}
}
foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) {
// fill start and end color
$fill = $objStyle->getFill();
if (isset($fill->startcolorIndex)) {
$startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version);
$fill->getStartColor()->setRGB($startColor['rgb']);
}
if (isset($fill->endcolorIndex)) {
$endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version);
$fill->getEndColor()->setRGB($endColor['rgb']);
}
// border colors
$top = $objStyle->getBorders()->getTop();
$right = $objStyle->getBorders()->getRight();
$bottom = $objStyle->getBorders()->getBottom();
$left = $objStyle->getBorders()->getLeft();
$diagonal = $objStyle->getBorders()->getDiagonal();
if (isset($top->colorIndex)) {
$borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version);
$top->getColor()->setRGB($borderTopColor['rgb']);
}
if (isset($right->colorIndex)) {
$borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version);
$right->getColor()->setRGB($borderRightColor['rgb']);
}
if (isset($bottom->colorIndex)) {
$borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version);
$bottom->getColor()->setRGB($borderBottomColor['rgb']);
}
if (isset($left->colorIndex)) {
$borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version);
$left->getColor()->setRGB($borderLeftColor['rgb']);
}
if (isset($diagonal->colorIndex)) {
$borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version);
$diagonal->getColor()->setRGB($borderDiagonalColor['rgb']);
}
}
}
// treat MSODRAWINGGROUP records, workbook-level Escher
$escherWorkbook = null;
if (!$this->readDataOnly && $this->drawingGroupData) {
$escher = new Escher();
$reader = new Xls\Escher($escher);
$escherWorkbook = $reader->load($this->drawingGroupData);
}
// Parse the individual sheets
foreach ($this->sheets as $sheet) {
if ($sheet['sheetType'] != 0x00) {
// 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module
continue;
}
// check if sheet should be skipped
if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) {
continue;
}
// add sheet to PhpSpreadsheet object
$this->phpSheet = $this->spreadsheet->createSheet();
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
// name in line with the formula, not the reverse
$this->phpSheet->setTitle($sheet['name'], false, false);
$this->phpSheet->setSheetState($sheet['sheetState']);
$this->pos = $sheet['offset'];
// Initialize isFitToPages. May change after reading SHEETPR record.
$this->isFitToPages = false;
// Initialize drawingData
$this->drawingData = '';
// Initialize objs
$this->objs = [];
// Initialize shared formula parts
$this->sharedFormulaParts = [];
// Initialize shared formulas
$this->sharedFormulas = [];
// Initialize text objs
$this->textObjects = [];
// Initialize cell annotations
$this->cellNotes = [];
$this->textObjRef = -1;
while ($this->pos <= $this->dataSize - 4) {
$code = self::getUInt2d($this->data, $this->pos);
switch ($code) {
case self::XLS_TYPE_BOF:
$this->readBof();
break;
case self::XLS_TYPE_PRINTGRIDLINES:
$this->readPrintGridlines();
break;
case self::XLS_TYPE_DEFAULTROWHEIGHT:
$this->readDefaultRowHeight();
break;
case self::XLS_TYPE_SHEETPR:
$this->readSheetPr();
break;
case self::XLS_TYPE_HORIZONTALPAGEBREAKS:
$this->readHorizontalPageBreaks();
break;
case self::XLS_TYPE_VERTICALPAGEBREAKS:
$this->readVerticalPageBreaks();
break;
case self::XLS_TYPE_HEADER:
$this->readHeader();
break;
case self::XLS_TYPE_FOOTER:
$this->readFooter();
break;
case self::XLS_TYPE_HCENTER:
$this->readHcenter();
break;
case self::XLS_TYPE_VCENTER:
$this->readVcenter();
break;
case self::XLS_TYPE_LEFTMARGIN:
$this->readLeftMargin();
break;
case self::XLS_TYPE_RIGHTMARGIN:
$this->readRightMargin();
break;
case self::XLS_TYPE_TOPMARGIN:
$this->readTopMargin();
break;
case self::XLS_TYPE_BOTTOMMARGIN:
$this->readBottomMargin();
break;
case self::XLS_TYPE_PAGESETUP:
$this->readPageSetup();
break;
case self::XLS_TYPE_PROTECT:
$this->readProtect();
break;
case self::XLS_TYPE_SCENPROTECT:
$this->readScenProtect();
break;
case self::XLS_TYPE_OBJECTPROTECT:
$this->readObjectProtect();
break;
case self::XLS_TYPE_PASSWORD:
$this->readPassword();
break;
case self::XLS_TYPE_DEFCOLWIDTH:
$this->readDefColWidth();
break;
case self::XLS_TYPE_COLINFO:
$this->readColInfo();
break;
case self::XLS_TYPE_DIMENSION:
$this->readDefault();
break;
case self::XLS_TYPE_ROW:
$this->readRow();
break;
case self::XLS_TYPE_DBCELL:
$this->readDefault();
break;
case self::XLS_TYPE_RK:
$this->readRk();
break;
case self::XLS_TYPE_LABELSST:
$this->readLabelSst();
break;
case self::XLS_TYPE_MULRK:
$this->readMulRk();
break;
case self::XLS_TYPE_NUMBER:
$this->readNumber();
break;
case self::XLS_TYPE_FORMULA:
$this->readFormula();
break;
case self::XLS_TYPE_SHAREDFMLA:
$this->readSharedFmla();
break;
case self::XLS_TYPE_BOOLERR:
$this->readBoolErr();
break;
case self::XLS_TYPE_MULBLANK:
$this->readMulBlank();
break;
case self::XLS_TYPE_LABEL:
$this->readLabel();
break;
case self::XLS_TYPE_BLANK:
$this->readBlank();
break;
case self::XLS_TYPE_MSODRAWING:
$this->readMsoDrawing();
break;
case self::XLS_TYPE_OBJ:
$this->readObj();
break;
case self::XLS_TYPE_WINDOW2:
$this->readWindow2();
break;
case self::XLS_TYPE_PAGELAYOUTVIEW:
$this->readPageLayoutView();
break;
case self::XLS_TYPE_SCL:
$this->readScl();
break;
case self::XLS_TYPE_PANE:
$this->readPane();
break;
case self::XLS_TYPE_SELECTION:
$this->readSelection();
break;
case self::XLS_TYPE_MERGEDCELLS:
$this->readMergedCells();
break;
case self::XLS_TYPE_HYPERLINK:
$this->readHyperLink();
break;
case self::XLS_TYPE_DATAVALIDATIONS:
$this->readDataValidations();
break;
case self::XLS_TYPE_DATAVALIDATION:
$this->readDataValidation();
break;
case self::XLS_TYPE_CFHEADER:
$cellRangeAddresses = $this->readCFHeader();
break;
case self::XLS_TYPE_CFRULE:
$this->readCFRule($cellRangeAddresses ?? []);
break;
case self::XLS_TYPE_SHEETLAYOUT:
$this->readSheetLayout();
break;
case self::XLS_TYPE_SHEETPROTECTION:
$this->readSheetProtection();
break;
case self::XLS_TYPE_RANGEPROTECTION:
$this->readRangeProtection();
break;
case self::XLS_TYPE_NOTE:
$this->readNote();
break;
case self::XLS_TYPE_TXO:
$this->readTextObject();
break;
case self::XLS_TYPE_CONTINUE:
$this->readContinue();
break;
case self::XLS_TYPE_EOF:
$this->readDefault();
break 2;
default:
$this->readDefault();
break;
}
}
// treat MSODRAWING records, sheet-level Escher
if (!$this->readDataOnly && $this->drawingData) {
$escherWorksheet = new Escher();
$reader = new Xls\Escher($escherWorksheet);
$escherWorksheet = $reader->load($this->drawingData);
// get all spContainers in one long array, so they can be mapped to OBJ records
/** @var SpContainer[] */
$allSpContainers = method_exists($escherWorksheet, 'getDgContainer') ? $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers() : [];
}
// treat OBJ records
foreach ($this->objs as $n => $obj) {
// the first shape container never has a corresponding OBJ record, hence $n + 1
if (isset($allSpContainers[$n + 1])) {
$spContainer = $allSpContainers[$n + 1];
// we skip all spContainers that are a part of a group shape since we cannot yet handle those
if ($spContainer->getNestingLevel() > 1) {
continue;
}
// calculate the width and height of the shape
/** @var int $startRow */
[$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates());
/** @var int $endRow */
[$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates());
$startOffsetX = $spContainer->getStartOffsetX();
$startOffsetY = $spContainer->getStartOffsetY();
$endOffsetX = $spContainer->getEndOffsetX();
$endOffsetY = $spContainer->getEndOffsetY();
$width = SharedXls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX);
$height = SharedXls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY);
// calculate offsetX and offsetY of the shape
$offsetX = (int) ($startOffsetX * SharedXls::sizeCol($this->phpSheet, $startColumn) / 1024);
$offsetY = (int) ($startOffsetY * SharedXls::sizeRow($this->phpSheet, $startRow) / 256);
switch ($obj['otObjType']) {
case 0x19:
// Note
if (isset($this->cellNotes[$obj['idObjID']])) {
$cellNote = $this->cellNotes[$obj['idObjID']];
if (isset($this->textObjects[$obj['idObjID']])) {
$textObject = $this->textObjects[$obj['idObjID']];
$this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject;
}
}
break;
case 0x08:
// picture
// get index to BSE entry (1-based)
$BSEindex = $spContainer->getOPT(0x0104);
// If there is no BSE Index, we will fail here and other fields are not read.
// Fix by checking here.
// TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field?
// More likely : a uncompatible picture
if (!$BSEindex) {
continue 2;
}
if ($escherWorkbook) {
$BSECollection = method_exists($escherWorkbook, 'getDggContainer') ? $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection() : [];
$BSE = $BSECollection[$BSEindex - 1];
$blipType = $BSE->getBlipType();
// need check because some blip types are not supported by Escher reader such as EMF
if ($blip = $BSE->getBlip()) {
$ih = imagecreatefromstring($blip->getData());
if ($ih !== false) {
$drawing = new MemoryDrawing();
$drawing->setImageResource($ih);
// width, height, offsetX, offsetY
$drawing->setResizeProportional(false);
$drawing->setWidth($width);
$drawing->setHeight($height);
$drawing->setOffsetX($offsetX);
$drawing->setOffsetY($offsetY);
switch ($blipType) {
case BSE::BLIPTYPE_JPEG:
$drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG);
$drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG);
break;
case BSE::BLIPTYPE_PNG:
imagealphablending($ih, false);
imagesavealpha($ih, true);
$drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG);
$drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG);
break;
}
$drawing->setWorksheet($this->phpSheet);
$drawing->setCoordinates($spContainer->getStartCoordinates());
}
}
}
break;
default:
// other object type
break;
}
}
}
// treat SHAREDFMLA records
if ($this->version == self::XLS_BIFF8) {
foreach ($this->sharedFormulaParts as $cell => $baseCell) {
/** @var int $row */
[$column, $row] = Coordinate::coordinateFromString($cell);
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
$formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell);
$this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
}
}
}
if (!empty($this->cellNotes)) {
foreach ($this->cellNotes as $note => $noteDetails) {
if (!isset($noteDetails['objTextData'])) {
if (isset($this->textObjects[$note])) {
$textObject = $this->textObjects[$note];
$noteDetails['objTextData'] = $textObject;
} else {
$noteDetails['objTextData']['text'] = '';
}
}
$cellAddress = str_replace('$', '', $noteDetails['cellRef']);
$this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text']));
}
}
}
// add the named ranges (defined names)
foreach ($this->definedname as $definedName) {
if ($definedName['isBuiltInName']) {
switch ($definedName['name']) {
case pack('C', 0x06):
// print area
// in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2
$ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
$extractedRanges = [];
foreach ($ranges as $range) {
// $range should look like one of these
// Foo!$C$7:$J$66
// Bar!$A$1:$IV$2
$explodes = Worksheet::extractSheetTitle($range, true);
$sheetName = trim($explodes[0], "'");
if (count($explodes) == 2) {
if (strpos($explodes[1], ':') === false) {
$explodes[1] = $explodes[1] . ':' . $explodes[1];
}
$extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66
}
}
if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) {
$docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2
}
break;
case pack('C', 0x07):
// print titles (repeating rows)
// Assuming BIFF8, there are 3 cases
// 1. repeating rows
// formula looks like this: Sheet!$A$1:$IV$2
// rows 1-2 repeat
// 2. repeating columns
// formula looks like this: Sheet!$A$1:$B$65536
// columns A-B repeat
// 3. both repeating rows and repeating columns
// formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2
$ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma?
foreach ($ranges as $range) {
// $range should look like this one of these
// Sheet!$A$1:$B$65536
// Sheet!$A$1:$IV$2
if (strpos($range, '!') !== false) {
$explodes = Worksheet::extractSheetTitle($range, true);
if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) {
$extractedRange = $explodes[1];
$extractedRange = str_replace('$', '', $extractedRange);
$coordinateStrings = explode(':', $extractedRange);
if (count($coordinateStrings) == 2) {
[$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]);
[$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]);
if ($firstColumn == 'A' && $lastColumn == 'IV') {
// then we have repeating rows
$docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]);
} elseif ($firstRow == 1 && $lastRow == 65536) {
// then we have repeating columns
$docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]);
}
}
}
}
}
break;
}
} else {
// Extract range
if (strpos($definedName['formula'], '!') !== false) {
$explodes = Worksheet::extractSheetTitle($definedName['formula'], true);
if (
($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) ||
($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))
) {
$extractedRange = $explodes[1];
$localOnly = ($definedName['scope'] === 0) ? false : true;
$scope = ($definedName['scope'] === 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']);
$this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope));
}
}
// Named Value
// TODO Provide support for named values
}
}
$this->data = '';
return $this->spreadsheet;
}
/**
* Read record data from stream, decrypting as required.
*
* @param string $data Data stream to read from
* @param int $pos Position to start reading from
* @param int $len Record data length
*
* @return string Record data
*/
private function readRecordData($data, $pos, $len)
{
$data = substr($data, $pos, $len);
// File not encrypted, or record before encryption start point
if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) {
return $data;
}
$recordData = '';
if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) {
$oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK);
$block = (int) floor($pos / self::REKEY_BLOCK);
$endBlock = (int) floor(($pos + $len) / self::REKEY_BLOCK);
// Spin an RC4 decryptor to the right spot. If we have a decryptor sitting
// at a point earlier in the current block, re-use it as we can save some time.
if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) {
$this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
$step = $pos % self::REKEY_BLOCK;
} else {
$step = $pos - $this->rc4Pos;
}
$this->rc4Key->RC4(str_repeat("\0", $step));
// Decrypt record data (re-keying at the end of every block)
while ($block != $endBlock) {
$step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK);
$recordData .= $this->rc4Key->RC4(substr($data, 0, $step));
$data = substr($data, $step);
$pos += $step;
$len -= $step;
++$block;
$this->rc4Key = $this->makeKey($block, $this->md5Ctxt);
}
$recordData .= $this->rc4Key->RC4(substr($data, 0, $len));
// Keep track of the position of this decryptor.
// We'll try and re-use it later if we can to speed things up
$this->rc4Pos = $pos + $len;
} elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) {
throw new Exception('XOr encryption not supported');
}
return $recordData;
}
/**
* Use OLE reader to extract the relevant data streams from the OLE file.
*
* @param string $filename
*/
private function loadOLE($filename): void
{
// OLE reader
$ole = new OLERead();
// get excel data,
$ole->read($filename);
// Get workbook data: workbook stream + sheet streams
$this->data = $ole->getStream($ole->wrkbook); // @phpstan-ignore-line
// Get summary information data
$this->summaryInformation = $ole->getStream($ole->summaryInformation);
// Get additional document summary information data
$this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation);
}
/**
* Read summary information.
*/
private function readSummaryInformation(): void
{
if (!isset($this->summaryInformation)) {
return;
}
// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
// offset: 2; size: 2;
// offset: 4; size: 2; OS version
// offset: 6; size: 2; OS indicator
// offset: 8; size: 16
// offset: 24; size: 4; section count
$secCount = self::getInt4d($this->summaryInformation, 24);
// offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
// offset: 44; size: 4
$secOffset = self::getInt4d($this->summaryInformation, 44);
// section header
// offset: $secOffset; size: 4; section length
$secLength = self::getInt4d($this->summaryInformation, $secOffset);
// offset: $secOffset+4; size: 4; property count
$countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4);
// initialize code page (used to resolve string values)
$codePage = 'CP1252';
// offset: ($secOffset+8); size: var
// loop through property decarations and properties
for ($i = 0; $i < $countProperties; ++$i) {
// offset: ($secOffset+8) + (8 * $i); size: 4; property ID
$id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i));
// Use value of property id as appropriate
// offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48)
$offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i));
$type = self::getInt4d($this->summaryInformation, $secOffset + $offset);
// initialize property value
$value = null;
// extract property value based on property type
switch ($type) {
case 0x02: // 2 byte signed integer
$value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset);
break;
case 0x03: // 4 byte signed integer
$value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
break;
case 0x13: // 4 byte unsigned integer
// not needed yet, fix later if necessary
break;
case 0x1E: // null-terminated string prepended by dword string length
$byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset);
$value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength);
$value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
$value = rtrim($value);
break;
case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
// PHP-time
$value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8));
break;
case 0x47: // Clipboard format
// not needed yet, fix later if necessary
break;
}
switch ($id) {
case 0x01: // Code Page
$codePage = CodePage::numberToName((int) $value);
break;
case 0x02: // Title
$this->spreadsheet->getProperties()->setTitle("$value");
break;
case 0x03: // Subject
$this->spreadsheet->getProperties()->setSubject("$value");
break;
case 0x04: // Author (Creator)
$this->spreadsheet->getProperties()->setCreator("$value");
break;
case 0x05: // Keywords
$this->spreadsheet->getProperties()->setKeywords("$value");
break;
case 0x06: // Comments (Description)
$this->spreadsheet->getProperties()->setDescription("$value");
break;
case 0x07: // Template
// Not supported by PhpSpreadsheet
break;
case 0x08: // Last Saved By (LastModifiedBy)
$this->spreadsheet->getProperties()->setLastModifiedBy("$value");
break;
case 0x09: // Revision
// Not supported by PhpSpreadsheet
break;
case 0x0A: // Total Editing Time
// Not supported by PhpSpreadsheet
break;
case 0x0B: // Last Printed
// Not supported by PhpSpreadsheet
break;
case 0x0C: // Created Date/Time
$this->spreadsheet->getProperties()->setCreated($value);
break;
case 0x0D: // Modified Date/Time
$this->spreadsheet->getProperties()->setModified($value);
break;
case 0x0E: // Number of Pages
// Not supported by PhpSpreadsheet
break;
case 0x0F: // Number of Words
// Not supported by PhpSpreadsheet
break;
case 0x10: // Number of Characters
// Not supported by PhpSpreadsheet
break;
case 0x11: // Thumbnail
// Not supported by PhpSpreadsheet
break;
case 0x12: // Name of creating application
// Not supported by PhpSpreadsheet
break;
case 0x13: // Security
// Not supported by PhpSpreadsheet
break;
}
}
}
/**
* Read additional document summary information.
*/
private function readDocumentSummaryInformation(): void
{
if (!isset($this->documentSummaryInformation)) {
return;
}
// offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
// offset: 2; size: 2;
// offset: 4; size: 2; OS version
// offset: 6; size: 2; OS indicator
// offset: 8; size: 16
// offset: 24; size: 4; section count
$secCount = self::getInt4d($this->documentSummaryInformation, 24);
// offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
// offset: 44; size: 4; first section offset
$secOffset = self::getInt4d($this->documentSummaryInformation, 44);
// section header
// offset: $secOffset; size: 4; section length
$secLength = self::getInt4d($this->documentSummaryInformation, $secOffset);
// offset: $secOffset+4; size: 4; property count
$countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4);
// initialize code page (used to resolve string values)
$codePage = 'CP1252';
// offset: ($secOffset+8); size: var
// loop through property decarations and properties
for ($i = 0; $i < $countProperties; ++$i) {
// offset: ($secOffset+8) + (8 * $i); size: 4; property ID
$id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i));
// Use value of property id as appropriate
// offset: 60 + 8 * $i; size: 4; offset from beginning of section (48)
$offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i));
$type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset);
// initialize property value
$value = null;
// extract property value based on property type
switch ($type) {
case 0x02: // 2 byte signed integer
$value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
break;
case 0x03: // 4 byte signed integer
$value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
break;
case 0x0B: // Boolean
$value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset);
$value = ($value == 0 ? false : true);
break;
case 0x13: // 4 byte unsigned integer
// not needed yet, fix later if necessary
break;
case 0x1E: // null-terminated string prepended by dword string length
$byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset);
$value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength);
$value = StringHelper::convertEncoding($value, 'UTF-8', $codePage);
$value = rtrim($value);
break;
case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
// PHP-Time
$value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8));
break;
case 0x47: // Clipboard format
// not needed yet, fix later if necessary
break;
}
switch ($id) {
case 0x01: // Code Page
$codePage = CodePage::numberToName((int) $value);
break;
case 0x02: // Category
$this->spreadsheet->getProperties()->setCategory("$value");
break;
case 0x03: // Presentation Target
// Not supported by PhpSpreadsheet
break;
case 0x04: // Bytes
// Not supported by PhpSpreadsheet
break;
case 0x05: // Lines
// Not supported by PhpSpreadsheet
break;
case 0x06: // Paragraphs
// Not supported by PhpSpreadsheet
break;
case 0x07: // Slides
// Not supported by PhpSpreadsheet
break;
case 0x08: // Notes
// Not supported by PhpSpreadsheet
break;
case 0x09: // Hidden Slides
// Not supported by PhpSpreadsheet
break;
case 0x0A: // MM Clips
// Not supported by PhpSpreadsheet
break;
case 0x0B: // Scale Crop
// Not supported by PhpSpreadsheet
break;
case 0x0C: // Heading Pairs
// Not supported by PhpSpreadsheet
break;
case 0x0D: // Titles of Parts
// Not supported by PhpSpreadsheet
break;
case 0x0E: // Manager
$this->spreadsheet->getProperties()->setManager("$value");
break;
case 0x0F: // Company
$this->spreadsheet->getProperties()->setCompany("$value");
break;
case 0x10: // Links up-to-date
// Not supported by PhpSpreadsheet
break;
}
}
}
/**
* Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record.
*/
private function readDefault(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
// move stream pointer to next record
$this->pos += 4 + $length;
}
/**
* The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions,
* this record stores a note (cell note). This feature was significantly enhanced in Excel 97.
*/
private function readNote(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
$cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4));
if ($this->version == self::XLS_BIFF8) {
$noteObjID = self::getUInt2d($recordData, 6);
$noteAuthor = self::readUnicodeStringLong(substr($recordData, 8));
$noteAuthor = $noteAuthor['value'];
$this->cellNotes[$noteObjID] = [
'cellRef' => $cellAddress,
'objectID' => $noteObjID,
'author' => $noteAuthor,
];
} else {
$extension = false;
if ($cellAddress == '$B$65536') {
// If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation
// note from the previous cell annotation. We're not yet handling this, so annotations longer than the
// max 2048 bytes will probably throw a wobbly.
$row = self::getUInt2d($recordData, 0);
$extension = true;
$arrayKeys = array_keys($this->phpSheet->getComments());
$cellAddress = array_pop($arrayKeys);
}
$cellAddress = str_replace('$', '', (string) $cellAddress);
$noteLength = self::getUInt2d($recordData, 4);
$noteText = trim(substr($recordData, 6));
if ($extension) {
// Concatenate this extension with the currently set comment for the cell
$comment = $this->phpSheet->getComment($cellAddress);
$commentText = $comment->getText()->getPlainText();
$comment->setText($this->parseRichText($commentText . $noteText));
} else {
// Set comment for the cell
$this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText));
// ->setAuthor($author)
}
}
}
/**
* The TEXT Object record contains the text associated with a cell annotation.
*/
private function readTextObject(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// recordData consists of an array of subrecords looking like this:
// grbit: 2 bytes; Option Flags
// rot: 2 bytes; rotation
// cchText: 2 bytes; length of the text (in the first continue record)
// cbRuns: 2 bytes; length of the formatting (in the second continue record)
// followed by the continuation records containing the actual text and formatting
$grbitOpts = self::getUInt2d($recordData, 0);
$rot = self::getUInt2d($recordData, 2);
$cchText = self::getUInt2d($recordData, 10);
$cbRuns = self::getUInt2d($recordData, 12);
$text = $this->getSplicedRecordData();
$textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1;
$textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte);
// get 1 byte
$is16Bit = ord($text['recordData'][0]);
// it is possible to use a compressed format,
// which omits the high bytes of all characters, if they are all zero
if (($is16Bit & 0x01) === 0) {
$textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1');
} else {
$textStr = $this->decodeCodepage($textStr);
}
$this->textObjects[$this->textObjRef] = [
'text' => $textStr,
'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns),
'alignment' => $grbitOpts,
'rotation' => $rot,
];
}
/**
* Read BOF.
*/
private function readBof(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = substr($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 2; size: 2; type of the following data
$substreamType = self::getUInt2d($recordData, 2);
switch ($substreamType) {
case self::XLS_WORKBOOKGLOBALS:
$version = self::getUInt2d($recordData, 0);
if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) {
throw new Exception('Cannot read this Excel file. Version is too old.');
}
$this->version = $version;
break;
case self::XLS_WORKSHEET:
// do not use this version information for anything
// it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream
break;
default:
// substream, e.g. chart
// just skip the entire substream
do {
$code = self::getUInt2d($this->data, $this->pos);
$this->readDefault();
} while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize);
break;
}
}
/**
* FILEPASS.
*
* This record is part of the File Protection Block. It
* contains information about the read/write password of the
* file. All record contents following this record will be
* encrypted.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*
* The decryption functions and objects used from here on in
* are based on the source of Spreadsheet-ParseExcel:
* https://metacpan.org/release/Spreadsheet-ParseExcel
*/
private function readFilepass(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
if ($length != 54) {
throw new Exception('Unexpected file pass record length');
}
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) {
throw new Exception('Decryption password incorrect');
}
$this->encryption = self::MS_BIFF_CRYPTO_RC4;
// Decryption required from the record after next onwards
$this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2);
}
/**
* Make an RC4 decryptor for the given block.
*
* @param int $block Block for which to create decrypto
* @param string $valContext MD5 context state
*
* @return Xls\RC4
*/
private function makeKey($block, $valContext)
{
$pwarray = str_repeat("\0", 64);
for ($i = 0; $i < 5; ++$i) {
$pwarray[$i] = $valContext[$i];
}
$pwarray[5] = chr($block & 0xff);
$pwarray[6] = chr(($block >> 8) & 0xff);
$pwarray[7] = chr(($block >> 16) & 0xff);
$pwarray[8] = chr(($block >> 24) & 0xff);
$pwarray[9] = "\x80";
$pwarray[56] = "\x48";
$md5 = new Xls\MD5();
$md5->add($pwarray);
$s = $md5->getContext();
return new Xls\RC4($s);
}
/**
* Verify RC4 file password.
*
* @param string $password Password to check
* @param string $docid Document id
* @param string $salt_data Salt data
* @param string $hashedsalt_data Hashed salt data
* @param string $valContext Set to the MD5 context of the value
*
* @return bool Success
*/
private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext)
{
$pwarray = str_repeat("\0", 64);
$iMax = strlen($password);
for ($i = 0; $i < $iMax; ++$i) {
$o = ord(substr($password, $i, 1));
$pwarray[2 * $i] = chr($o & 0xff);
$pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff);
}
$pwarray[2 * $i] = chr(0x80);
$pwarray[56] = chr(($i << 4) & 0xff);
$md5 = new Xls\MD5();
$md5->add($pwarray);
$mdContext1 = $md5->getContext();
$offset = 0;
$keyoffset = 0;
$tocopy = 5;
$md5->reset();
while ($offset != 16) {
if ((64 - $offset) < 5) {
$tocopy = 64 - $offset;
}
for ($i = 0; $i <= $tocopy; ++$i) {
$pwarray[$offset + $i] = $mdContext1[$keyoffset + $i];
}
$offset += $tocopy;
if ($offset == 64) {
$md5->add($pwarray);
$keyoffset = $tocopy;
$tocopy = 5 - $tocopy;
$offset = 0;
continue;
}
$keyoffset = 0;
$tocopy = 5;
for ($i = 0; $i < 16; ++$i) {
$pwarray[$offset + $i] = $docid[$i];
}
$offset += 16;
}
$pwarray[16] = "\x80";
for ($i = 0; $i < 47; ++$i) {
$pwarray[17 + $i] = "\0";
}
$pwarray[56] = "\x80";
$pwarray[57] = "\x0a";
$md5->add($pwarray);
$valContext = $md5->getContext();
$key = $this->makeKey(0, $valContext);
$salt = $key->RC4($salt_data);
$hashedsalt = $key->RC4($hashedsalt_data);
$salt .= "\x80" . str_repeat("\0", 47);
$salt[56] = "\x80";
$md5->reset();
$md5->add($salt);
$mdContext2 = $md5->getContext();
return $mdContext2 == $hashedsalt;
}
/**
* CODEPAGE.
*
* This record stores the text encoding used to write byte
* strings, stored as MS Windows code page identifier.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readCodepage(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; code page identifier
$codepage = self::getUInt2d($recordData, 0);
$this->codepage = CodePage::numberToName($codepage);
}
/**
* DATEMODE.
*
* This record specifies the base date for displaying date
* values. All dates are stored as count of days past this
* base date. In BIFF2-BIFF4 this record is part of the
* Calculation Settings Block. In BIFF5-BIFF8 it is
* stored in the Workbook Globals Substream.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readDateMode(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; 0 = base 1900, 1 = base 1904
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
if (ord($recordData[0]) == 1) {
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
}
}
/**
* Read a FONT record.
*/
private function readFont(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
$objFont = new Font();
// offset: 0; size: 2; height of the font (in twips = 1/20 of a point)
$size = self::getUInt2d($recordData, 0);
$objFont->setSize($size / 20);
// offset: 2; size: 2; option flags
// bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8)
// bit: 1; mask 0x0002; italic
$isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1;
if ($isItalic) {
$objFont->setItalic(true);
}
// bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8)
// bit: 3; mask 0x0008; strikethrough
$isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3;
if ($isStrike) {
$objFont->setStrikethrough(true);
}
// offset: 4; size: 2; colour index
$colorIndex = self::getUInt2d($recordData, 4);
$objFont->colorIndex = $colorIndex;
// offset: 6; size: 2; font weight
$weight = self::getUInt2d($recordData, 6);
switch ($weight) {
case 0x02BC:
$objFont->setBold(true);
break;
}
// offset: 8; size: 2; escapement type
$escapement = self::getUInt2d($recordData, 8);
CellFont::escapement($objFont, $escapement);
// offset: 10; size: 1; underline type
$underlineType = ord($recordData[10]);
CellFont::underline($objFont, $underlineType);
// offset: 11; size: 1; font family
// offset: 12; size: 1; character set
// offset: 13; size: 1; not used
// offset: 14; size: var; font name
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringShort(substr($recordData, 14));
} else {
$string = $this->readByteStringShort(substr($recordData, 14));
}
$objFont->setName($string['value']);
$this->objFonts[] = $objFont;
}
}
/**
* FORMAT.
*
* This record contains information about a number format.
* All FORMAT records occur together in a sequential list.
*
* In BIFF2-BIFF4 other records referencing a FORMAT record
* contain a zero-based index into this list. From BIFF5 on
* the FORMAT record contains the index itself that will be
* used by other records.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readFormat(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
$indexCode = self::getUInt2d($recordData, 0);
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringLong(substr($recordData, 2));
} else {
// BIFF7
$string = $this->readByteStringShort(substr($recordData, 2));
}
$formatString = $string['value'];
// Apache Open Office sets wrong case writing to xls - issue 2239
if ($formatString === 'GENERAL') {
$formatString = NumberFormat::FORMAT_GENERAL;
}
$this->formats[$indexCode] = $formatString;
}
}
/**
* XF - Extended Format.
*
* This record contains formatting information for cells, rows, columns or styles.
* According to https://support.microsoft.com/en-us/help/147732 there are always at least 15 cell style XF
* and 1 cell XF.
* Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF
* and XF record 15 is a cell XF
* We only read the first cell style XF and skip the remaining cell style XF records
* We read all cell XF records.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readXf(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
$objStyle = new Style();
if (!$this->readDataOnly) {
// offset: 0; size: 2; Index to FONT record
if (self::getUInt2d($recordData, 0) < 4) {
$fontIndex = self::getUInt2d($recordData, 0);
} else {
// this has to do with that index 4 is omitted in all BIFF versions for some strange reason
// check the OpenOffice documentation of the FONT record
$fontIndex = self::getUInt2d($recordData, 0) - 1;
}
$objStyle->setFont($this->objFonts[$fontIndex]);
// offset: 2; size: 2; Index to FORMAT record
$numberFormatIndex = self::getUInt2d($recordData, 2);
if (isset($this->formats[$numberFormatIndex])) {
// then we have user-defined format code
$numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]];
} elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') {
// then we have built-in format code
$numberFormat = ['formatCode' => $code];
} else {
// we set the general format code
$numberFormat = ['formatCode' => NumberFormat::FORMAT_GENERAL];
}
$objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']);
// offset: 4; size: 2; XF type, cell protection, and parent style XF
// bit 2-0; mask 0x0007; XF_TYPE_PROT
$xfTypeProt = self::getUInt2d($recordData, 4);
// bit 0; mask 0x01; 1 = cell is locked
$isLocked = (0x01 & $xfTypeProt) >> 0;
$objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED);
// bit 1; mask 0x02; 1 = Formula is hidden
$isHidden = (0x02 & $xfTypeProt) >> 1;
$objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED);
// bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF
$isCellStyleXf = (0x04 & $xfTypeProt) >> 2;
// offset: 6; size: 1; Alignment and text break
// bit 2-0, mask 0x07; horizontal alignment
$horAlign = (0x07 & ord($recordData[6])) >> 0;
Xls\Style\CellAlignment::horizontal($objStyle->getAlignment(), $horAlign);
// bit 3, mask 0x08; wrap text
$wrapText = (0x08 & ord($recordData[6])) >> 3;
Xls\Style\CellAlignment::wrap($objStyle->getAlignment(), $wrapText);
// bit 6-4, mask 0x70; vertical alignment
$vertAlign = (0x70 & ord($recordData[6])) >> 4;
Xls\Style\CellAlignment::vertical($objStyle->getAlignment(), $vertAlign);
if ($this->version == self::XLS_BIFF8) {
// offset: 7; size: 1; XF_ROTATION: Text rotation angle
$angle = ord($recordData[7]);
$rotation = 0;
if ($angle <= 90) {
$rotation = $angle;
} elseif ($angle <= 180) {
$rotation = 90 - $angle;
} elseif ($angle == Alignment::TEXTROTATION_STACK_EXCEL) {
$rotation = Alignment::TEXTROTATION_STACK_PHPSPREADSHEET;
}
$objStyle->getAlignment()->setTextRotation($rotation);
// offset: 8; size: 1; Indentation, shrink to cell size, and text direction
// bit: 3-0; mask: 0x0F; indent level
$indent = (0x0F & ord($recordData[8])) >> 0;
$objStyle->getAlignment()->setIndent($indent);
// bit: 4; mask: 0x10; 1 = shrink content to fit into cell
$shrinkToFit = (0x10 & ord($recordData[8])) >> 4;
switch ($shrinkToFit) {
case 0:
$objStyle->getAlignment()->setShrinkToFit(false);
break;
case 1:
$objStyle->getAlignment()->setShrinkToFit(true);
break;
}
// offset: 9; size: 1; Flags used for attribute groups
// offset: 10; size: 4; Cell border lines and background area
// bit: 3-0; mask: 0x0000000F; left style
if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) {
$objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle);
}
// bit: 7-4; mask: 0x000000F0; right style
if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) {
$objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle);
}
// bit: 11-8; mask: 0x00000F00; top style
if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) {
$objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle);
}
// bit: 15-12; mask: 0x0000F000; bottom style
if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) {
$objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle);
}
// bit: 22-16; mask: 0x007F0000; left color
$objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16;
// bit: 29-23; mask: 0x3F800000; right color
$objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23;
// bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom
$diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false;
// bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right
$diagonalUp = ((int) 0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false;
if ($diagonalUp === false) {
if ($diagonalDown == false) {
$objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE);
} else {
$objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN);
}
} elseif ($diagonalDown == false) {
$objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP);
} else {
$objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH);
}
// offset: 14; size: 4;
// bit: 6-0; mask: 0x0000007F; top color
$objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0;
// bit: 13-7; mask: 0x00003F80; bottom color
$objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7;
// bit: 20-14; mask: 0x001FC000; diagonal color
$objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14;
// bit: 24-21; mask: 0x01E00000; diagonal style
if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) {
$objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle);
}
// bit: 31-26; mask: 0xFC000000 fill pattern
if ($fillType = Xls\Style\FillPattern::lookup(((int) 0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) {
$objStyle->getFill()->setFillType($fillType);
}
// offset: 18; size: 2; pattern and background colour
// bit: 6-0; mask: 0x007F; color index for pattern color
$objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0;
// bit: 13-7; mask: 0x3F80; color index for pattern background
$objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7;
} else {
// BIFF5
// offset: 7; size: 1; Text orientation and flags
$orientationAndFlags = ord($recordData[7]);
// bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation
$xfOrientation = (0x03 & $orientationAndFlags) >> 0;
switch ($xfOrientation) {
case 0:
$objStyle->getAlignment()->setTextRotation(0);
break;
case 1:
$objStyle->getAlignment()->setTextRotation(Alignment::TEXTROTATION_STACK_PHPSPREADSHEET);
break;
case 2:
$objStyle->getAlignment()->setTextRotation(90);
break;
case 3:
$objStyle->getAlignment()->setTextRotation(-90);
break;
}
// offset: 8; size: 4; cell border lines and background area
$borderAndBackground = self::getInt4d($recordData, 8);
// bit: 6-0; mask: 0x0000007F; color index for pattern color
$objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0;
// bit: 13-7; mask: 0x00003F80; color index for pattern background
$objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7;
// bit: 21-16; mask: 0x003F0000; fill pattern
$objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16));
// bit: 24-22; mask: 0x01C00000; bottom line style
$objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22));
// bit: 31-25; mask: 0xFE000000; bottom line color
$objStyle->getBorders()->getBottom()->colorIndex = ((int) 0xFE000000 & $borderAndBackground) >> 25;
// offset: 12; size: 4; cell border lines
$borderLines = self::getInt4d($recordData, 12);
// bit: 2-0; mask: 0x00000007; top line style
$objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0));
// bit: 5-3; mask: 0x00000038; left line style
$objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3));
// bit: 8-6; mask: 0x000001C0; right line style
$objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6));
// bit: 15-9; mask: 0x0000FE00; top line color index
$objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9;
// bit: 22-16; mask: 0x007F0000; left line color index
$objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16;
// bit: 29-23; mask: 0x3F800000; right line color index
$objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23;
}
// add cellStyleXf or cellXf and update mapping
if ($isCellStyleXf) {
// we only read one style XF record which is always the first
if ($this->xfIndex == 0) {
$this->spreadsheet->addCellStyleXf($objStyle);
$this->mapCellStyleXfIndex[$this->xfIndex] = 0;
}
} else {
// we read all cell XF records
$this->spreadsheet->addCellXf($objStyle);
$this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1;
}
// update XF index for when we read next record
++$this->xfIndex;
}
}
private function readXfExt(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; 0x087D = repeated header
// offset: 2; size: 2
// offset: 4; size: 8; not used
// offset: 12; size: 2; record version
// offset: 14; size: 2; index to XF record which this record modifies
$ixfe = self::getUInt2d($recordData, 14);
// offset: 16; size: 2; not used
// offset: 18; size: 2; number of extension properties that follow
$cexts = self::getUInt2d($recordData, 18);
// start reading the actual extension data
$offset = 20;
while ($offset < $length) {
// extension type
$extType = self::getUInt2d($recordData, $offset);
// extension length
$cb = self::getUInt2d($recordData, $offset + 2);
// extension data
$extData = substr($recordData, $offset + 4, $cb);
switch ($extType) {
case 4: // fill start color
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
$fill->getStartColor()->setRGB($rgb);
$fill->startcolorIndex = null; // normal color index does not apply, discard
}
}
break;
case 5: // fill end color
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill();
$fill->getEndColor()->setRGB($rgb);
$fill->endcolorIndex = null; // normal color index does not apply, discard
}
}
break;
case 7: // border color top
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop();
$top->getColor()->setRGB($rgb);
$top->colorIndex = null; // normal color index does not apply, discard
}
}
break;
case 8: // border color bottom
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom();
$bottom->getColor()->setRGB($rgb);
$bottom->colorIndex = null; // normal color index does not apply, discard
}
}
break;
case 9: // border color left
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft();
$left->getColor()->setRGB($rgb);
$left->colorIndex = null; // normal color index does not apply, discard
}
}
break;
case 10: // border color right
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight();
$right->getColor()->setRGB($rgb);
$right->colorIndex = null; // normal color index does not apply, discard
}
}
break;
case 11: // border color diagonal
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal();
$diagonal->getColor()->setRGB($rgb);
$diagonal->colorIndex = null; // normal color index does not apply, discard
}
}
break;
case 13: // font color
$xclfType = self::getUInt2d($extData, 0); // color type
$xclrValue = substr($extData, 4, 4); // color value (value based on color type)
if ($xclfType == 2) {
$rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2]));
// modify the relevant style property
if (isset($this->mapCellXfIndex[$ixfe])) {
$font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont();
$font->getColor()->setRGB($rgb);
$font->colorIndex = null; // normal color index does not apply, discard
}
}
break;
}
$offset += $cb;
}
}
}
/**
* Read STYLE record.
*/
private function readStyle(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; index to XF record and flag for built-in style
$ixfe = self::getUInt2d($recordData, 0);
// bit: 11-0; mask 0x0FFF; index to XF record
$xfIndex = (0x0FFF & $ixfe) >> 0;
// bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style
$isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15);
if ($isBuiltIn) {
// offset: 2; size: 1; identifier for built-in style
$builtInId = ord($recordData[2]);
switch ($builtInId) {
case 0x00:
// currently, we are not using this for anything
break;
default:
break;
}
}
// user-defined; not supported by PhpSpreadsheet
}
}
/**
* Read PALETTE record.
*/
private function readPalette(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; number of following colors
$nm = self::getUInt2d($recordData, 0);
// list of RGB colors
for ($i = 0; $i < $nm; ++$i) {
$rgb = substr($recordData, 2 + 4 * $i, 4);
$this->palette[] = self::readRGB($rgb);
}
}
}
/**
* SHEET.
*
* This record is located in the Workbook Globals
* Substream and represents a sheet inside the workbook.
* One SHEET record is written for each sheet. It stores the
* sheet name and a stream offset to the BOF record of the
* respective Sheet Substream within the Workbook Stream.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readSheet(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// offset: 0; size: 4; absolute stream position of the BOF record of the sheet
// NOTE: not encrypted
$rec_offset = self::getInt4d($this->data, $this->pos + 4);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 4; size: 1; sheet state
switch (ord($recordData[4])) {
case 0x00:
$sheetState = Worksheet::SHEETSTATE_VISIBLE;
break;
case 0x01:
$sheetState = Worksheet::SHEETSTATE_HIDDEN;
break;
case 0x02:
$sheetState = Worksheet::SHEETSTATE_VERYHIDDEN;
break;
default:
$sheetState = Worksheet::SHEETSTATE_VISIBLE;
break;
}
// offset: 5; size: 1; sheet type
$sheetType = ord($recordData[5]);
// offset: 6; size: var; sheet name
$rec_name = null;
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringShort(substr($recordData, 6));
$rec_name = $string['value'];
} elseif ($this->version == self::XLS_BIFF7) {
$string = $this->readByteStringShort(substr($recordData, 6));
$rec_name = $string['value'];
}
$this->sheets[] = [
'name' => $rec_name,
'offset' => $rec_offset,
'sheetState' => $sheetState,
'sheetType' => $sheetType,
];
}
/**
* Read EXTERNALBOOK record.
*/
private function readExternalBook(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset within record data
$offset = 0;
// there are 4 types of records
if (strlen($recordData) > 4) {
// external reference
// offset: 0; size: 2; number of sheet names ($nm)
$nm = self::getUInt2d($recordData, 0);
$offset += 2;
// offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length)
$encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2));
$offset += $encodedUrlString['size'];
// offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length)
$externalSheetNames = [];
for ($i = 0; $i < $nm; ++$i) {
$externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset));
$externalSheetNames[] = $externalSheetNameString['value'];
$offset += $externalSheetNameString['size'];
}
// store the record data
$this->externalBooks[] = [
'type' => 'external',
'encodedUrl' => $encodedUrlString['value'],
'externalSheetNames' => $externalSheetNames,
];
} elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) {
// internal reference
// offset: 0; size: 2; number of sheet in this document
// offset: 2; size: 2; 0x01 0x04
$this->externalBooks[] = [
'type' => 'internal',
];
} elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) {
// add-in function
// offset: 0; size: 2; 0x0001
$this->externalBooks[] = [
'type' => 'addInFunction',
];
} elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) {
// DDE links, OLE links
// offset: 0; size: 2; 0x0000
// offset: 2; size: var; encoded source document name
$this->externalBooks[] = [
'type' => 'DDEorOLE',
];
}
}
/**
* Read EXTERNNAME record.
*/
private function readExternName(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// external sheet references provided for named cells
if ($this->version == self::XLS_BIFF8) {
// offset: 0; size: 2; options
$options = self::getUInt2d($recordData, 0);
// offset: 2; size: 2;
// offset: 4; size: 2; not used
// offset: 6; size: var
$nameString = self::readUnicodeStringShort(substr($recordData, 6));
// offset: var; size: var; formula data
$offset = 6 + $nameString['size'];
$formula = $this->getFormulaFromStructure(substr($recordData, $offset));
$this->externalNames[] = [
'name' => $nameString['value'],
'formula' => $formula,
];
}
}
/**
* Read EXTERNSHEET record.
*/
private function readExternSheet(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// external sheet references provided for named cells
if ($this->version == self::XLS_BIFF8) {
// offset: 0; size: 2; number of following ref structures
$nm = self::getUInt2d($recordData, 0);
for ($i = 0; $i < $nm; ++$i) {
$this->ref[] = [
// offset: 2 + 6 * $i; index to EXTERNALBOOK record
'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i),
// offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record
'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i),
// offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record
'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i),
];
}
}
}
/**
* DEFINEDNAME.
*
* This record is part of a Link Table. It contains the name
* and the token array of an internal defined name. Token
* arrays of defined names contain tokens with aberrant
* token classes.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readDefinedName(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8) {
// retrieves named cells
// offset: 0; size: 2; option flags
$opts = self::getUInt2d($recordData, 0);
// bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name
$isBuiltInName = (0x0020 & $opts) >> 5;
// offset: 2; size: 1; keyboard shortcut
// offset: 3; size: 1; length of the name (character count)
$nlen = ord($recordData[3]);
// offset: 4; size: 2; size of the formula data (it can happen that this is zero)
// note: there can also be additional data, this is not included in $flen
$flen = self::getUInt2d($recordData, 4);
// offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based)
$scope = self::getUInt2d($recordData, 8);
// offset: 14; size: var; Name (Unicode string without length field)
$string = self::readUnicodeString(substr($recordData, 14), $nlen);
// offset: var; size: $flen; formula data
$offset = 14 + $string['size'];
$formulaStructure = pack('v', $flen) . substr($recordData, $offset);
try {
$formula = $this->getFormulaFromStructure($formulaStructure);
} catch (PhpSpreadsheetException $e) {
$formula = '';
}
$this->definedname[] = [
'isBuiltInName' => $isBuiltInName,
'name' => $string['value'],
'formula' => $formula,
'scope' => $scope,
];
}
}
/**
* Read MSODRAWINGGROUP record.
*/
private function readMsoDrawingGroup(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
// get spliced record data
$splicedRecordData = $this->getSplicedRecordData();
$recordData = $splicedRecordData['recordData'];
$this->drawingGroupData .= $recordData;
}
/**
* SST - Shared String Table.
*
* This record contains a list of all strings used anywhere
* in the workbook. Each string occurs only once. The
* workbook uses indexes into the list to reference the
* strings.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readSst(): void
{
// offset within (spliced) record data
$pos = 0;
// Limit global SST position, further control for bad SST Length in BIFF8 data
$limitposSST = 0;
// get spliced record data
$splicedRecordData = $this->getSplicedRecordData();
$recordData = $splicedRecordData['recordData'];
$spliceOffsets = $splicedRecordData['spliceOffsets'];
// offset: 0; size: 4; total number of strings in the workbook
$pos += 4;
// offset: 4; size: 4; number of following strings ($nm)
$nm = self::getInt4d($recordData, 4);
$pos += 4;
// look up limit position
foreach ($spliceOffsets as $spliceOffset) {
// it can happen that the string is empty, therefore we need
// <= and not just <
if ($pos <= $spliceOffset) {
$limitposSST = $spliceOffset;
}
}
// loop through the Unicode strings (16-bit length)
for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) {
// number of characters in the Unicode string
$numChars = self::getUInt2d($recordData, $pos);
$pos += 2;
// option flags
$optionFlags = ord($recordData[$pos]);
++$pos;
// bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed
$isCompressed = (($optionFlags & 0x01) == 0);
// bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic
$hasAsian = (($optionFlags & 0x04) != 0);
// bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text
$hasRichText = (($optionFlags & 0x08) != 0);
$formattingRuns = 0;
if ($hasRichText) {
// number of Rich-Text formatting runs
$formattingRuns = self::getUInt2d($recordData, $pos);
$pos += 2;
}
$extendedRunLength = 0;
if ($hasAsian) {
// size of Asian phonetic setting
$extendedRunLength = self::getInt4d($recordData, $pos);
$pos += 4;
}
// expected byte length of character array if not split
$len = ($isCompressed) ? $numChars : $numChars * 2;
// look up limit position - Check it again to be sure that no error occurs when parsing SST structure
$limitpos = null;
foreach ($spliceOffsets as $spliceOffset) {
// it can happen that the string is empty, therefore we need
// <= and not just <
if ($pos <= $spliceOffset) {
$limitpos = $spliceOffset;
break;
}
}
if ($pos + $len <= $limitpos) {
// character array is not split between records
$retstr = substr($recordData, $pos, $len);
$pos += $len;
} else {
// character array is split between records
// first part of character array
$retstr = substr($recordData, $pos, $limitpos - $pos);
$bytesRead = $limitpos - $pos;
// remaining characters in Unicode string
$charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2));
$pos = $limitpos;
// keep reading the characters
while ($charsLeft > 0) {
// look up next limit position, in case the string span more than one continue record
foreach ($spliceOffsets as $spliceOffset) {
if ($pos < $spliceOffset) {
$limitpos = $spliceOffset;
break;
}
}
// repeated option flags
// OpenOffice.org documentation 5.21
$option = ord($recordData[$pos]);
++$pos;
if ($isCompressed && ($option == 0)) {
// 1st fragment compressed
// this fragment compressed
$len = min($charsLeft, $limitpos - $pos);
$retstr .= substr($recordData, $pos, $len);
$charsLeft -= $len;
$isCompressed = true;
} elseif (!$isCompressed && ($option != 0)) {
// 1st fragment uncompressed
// this fragment uncompressed
$len = min($charsLeft * 2, $limitpos - $pos);
$retstr .= substr($recordData, $pos, $len);
$charsLeft -= $len / 2;
$isCompressed = false;
} elseif (!$isCompressed && ($option == 0)) {
// 1st fragment uncompressed
// this fragment compressed
$len = min($charsLeft, $limitpos - $pos);
for ($j = 0; $j < $len; ++$j) {
$retstr .= $recordData[$pos + $j]
. chr(0);
}
$charsLeft -= $len;
$isCompressed = false;
} else {
// 1st fragment compressed
// this fragment uncompressed
$newstr = '';
$jMax = strlen($retstr);
for ($j = 0; $j < $jMax; ++$j) {
$newstr .= $retstr[$j] . chr(0);
}
$retstr = $newstr;
$len = min($charsLeft * 2, $limitpos - $pos);
$retstr .= substr($recordData, $pos, $len);
$charsLeft -= $len / 2;
$isCompressed = false;
}
$pos += $len;
}
}
// convert to UTF-8
$retstr = self::encodeUTF16($retstr, $isCompressed);
// read additional Rich-Text information, if any
$fmtRuns = [];
if ($hasRichText) {
// list of formatting runs
for ($j = 0; $j < $formattingRuns; ++$j) {
// first formatted character; zero-based
$charPos = self::getUInt2d($recordData, $pos + $j * 4);
// index to font record
$fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4);
$fmtRuns[] = [
'charPos' => $charPos,
'fontIndex' => $fontIndex,
];
}
$pos += 4 * $formattingRuns;
}
// read additional Asian phonetics information, if any
if ($hasAsian) {
// For Asian phonetic settings, we skip the extended string data
$pos += $extendedRunLength;
}
// store the shared sting
$this->sst[] = [
'value' => $retstr,
'fmtRuns' => $fmtRuns,
];
}
// getSplicedRecordData() takes care of moving current position in data stream
}
/**
* Read PRINTGRIDLINES record.
*/
private function readPrintGridlines(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
// offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines
$printGridlines = (bool) self::getUInt2d($recordData, 0);
$this->phpSheet->setPrintGridlines($printGridlines);
}
}
/**
* Read DEFAULTROWHEIGHT record.
*/
private function readDefaultRowHeight(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; option flags
// offset: 2; size: 2; default height for unused rows, (twips 1/20 point)
$height = self::getUInt2d($recordData, 2);
$this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20);
}
/**
* Read SHEETPR record.
*/
private function readSheetPr(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2
// bit: 6; mask: 0x0040; 0 = outline buttons above outline group
$isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6;
$this->phpSheet->setShowSummaryBelow((bool) $isSummaryBelow);
// bit: 7; mask: 0x0080; 0 = outline buttons left of outline group
$isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7;
$this->phpSheet->setShowSummaryRight((bool) $isSummaryRight);
// bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages
// this corresponds to radio button setting in page setup dialog in Excel
$this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8);
}
/**
* Read HORIZONTALPAGEBREAKS record.
*/
private function readHorizontalPageBreaks(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
// offset: 0; size: 2; number of the following row index structures
$nm = self::getUInt2d($recordData, 0);
// offset: 2; size: 6 * $nm; list of $nm row index structures
for ($i = 0; $i < $nm; ++$i) {
$r = self::getUInt2d($recordData, 2 + 6 * $i);
$cf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
$cl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
// not sure why two column indexes are necessary?
$this->phpSheet->setBreak([$cf + 1, $r], Worksheet::BREAK_ROW);
}
}
}
/**
* Read VERTICALPAGEBREAKS record.
*/
private function readVerticalPageBreaks(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
// offset: 0; size: 2; number of the following column index structures
$nm = self::getUInt2d($recordData, 0);
// offset: 2; size: 6 * $nm; list of $nm row index structures
for ($i = 0; $i < $nm; ++$i) {
$c = self::getUInt2d($recordData, 2 + 6 * $i);
$rf = self::getUInt2d($recordData, 2 + 6 * $i + 2);
//$rl = self::getUInt2d($recordData, 2 + 6 * $i + 4);
// not sure why two row indexes are necessary?
$this->phpSheet->setBreak([$c + 1, ($rf > 0) ? $rf : 1], Worksheet::BREAK_COLUMN);
}
}
}
/**
* Read HEADER record.
*/
private function readHeader(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: var
// realized that $recordData can be empty even when record exists
if ($recordData) {
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringLong($recordData);
} else {
$string = $this->readByteStringShort($recordData);
}
$this->phpSheet->getHeaderFooter()->setOddHeader($string['value']);
$this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']);
}
}
}
/**
* Read FOOTER record.
*/
private function readFooter(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: var
// realized that $recordData can be empty even when record exists
if ($recordData) {
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringLong($recordData);
} else {
$string = $this->readByteStringShort($recordData);
}
$this->phpSheet->getHeaderFooter()->setOddFooter($string['value']);
$this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']);
}
}
}
/**
* Read HCENTER record.
*/
private function readHcenter(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally
$isHorizontalCentered = (bool) self::getUInt2d($recordData, 0);
$this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered);
}
}
/**
* Read VCENTER record.
*/
private function readVcenter(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered
$isVerticalCentered = (bool) self::getUInt2d($recordData, 0);
$this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered);
}
}
/**
* Read LEFTMARGIN record.
*/
private function readLeftMargin(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 8
$this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData));
}
}
/**
* Read RIGHTMARGIN record.
*/
private function readRightMargin(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 8
$this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData));
}
}
/**
* Read TOPMARGIN record.
*/
private function readTopMargin(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 8
$this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData));
}
}
/**
* Read BOTTOMMARGIN record.
*/
private function readBottomMargin(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 8
$this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData));
}
}
/**
* Read PAGESETUP record.
*/
private function readPageSetup(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; paper size
$paperSize = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; scaling factor
$scale = self::getUInt2d($recordData, 2);
// offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed
$fitToWidth = self::getUInt2d($recordData, 6);
// offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed
$fitToHeight = self::getUInt2d($recordData, 8);
// offset: 10; size: 2; option flags
// bit: 0; mask: 0x0001; 0=down then over, 1=over then down
$isOverThenDown = (0x0001 & self::getUInt2d($recordData, 10));
// bit: 1; mask: 0x0002; 0=landscape, 1=portrait
$isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1;
// bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init
// when this bit is set, do not use flags for those properties
$isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2;
if (!$isNotInit) {
$this->phpSheet->getPageSetup()->setPaperSize($paperSize);
$this->phpSheet->getPageSetup()->setPageOrder(((bool) $isOverThenDown) ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER);
$this->phpSheet->getPageSetup()->setOrientation(((bool) $isPortrait) ? PageSetup::ORIENTATION_PORTRAIT : PageSetup::ORIENTATION_LANDSCAPE);
$this->phpSheet->getPageSetup()->setScale($scale, false);
$this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages);
$this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false);
$this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false);
}
// offset: 16; size: 8; header margin (IEEE 754 floating-point value)
$marginHeader = self::extractNumber(substr($recordData, 16, 8));
$this->phpSheet->getPageMargins()->setHeader($marginHeader);
// offset: 24; size: 8; footer margin (IEEE 754 floating-point value)
$marginFooter = self::extractNumber(substr($recordData, 24, 8));
$this->phpSheet->getPageMargins()->setFooter($marginFooter);
}
}
/**
* PROTECT - Sheet protection (BIFF2 through BIFF8)
* if this record is omitted, then it also means no sheet protection.
*/
private function readProtect(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2;
// bit 0, mask 0x01; 1 = sheet is protected
$bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
$this->phpSheet->getProtection()->setSheet((bool) $bool);
}
/**
* SCENPROTECT.
*/
private function readScenProtect(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2;
// bit: 0, mask 0x01; 1 = scenarios are protected
$bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
$this->phpSheet->getProtection()->setScenarios((bool) $bool);
}
/**
* OBJECTPROTECT.
*/
private function readObjectProtect(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2;
// bit: 0, mask 0x01; 1 = objects are protected
$bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0;
$this->phpSheet->getProtection()->setObjects((bool) $bool);
}
/**
* PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8).
*/
private function readPassword(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; 16-bit hash value of password
$password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password
$this->phpSheet->getProtection()->setPassword($password, true);
}
}
/**
* Read DEFCOLWIDTH record.
*/
private function readDefColWidth(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; default column width
$width = self::getUInt2d($recordData, 0);
if ($width != 8) {
$this->phpSheet->getDefaultColumnDimension()->setWidth($width);
}
}
/**
* Read COLINFO record.
*/
private function readColInfo(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; index to first column in range
$firstColumnIndex = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to last column in range
$lastColumnIndex = self::getUInt2d($recordData, 2);
// offset: 4; size: 2; width of the column in 1/256 of the width of the zero character
$width = self::getUInt2d($recordData, 4);
// offset: 6; size: 2; index to XF record for default column formatting
$xfIndex = self::getUInt2d($recordData, 6);
// offset: 8; size: 2; option flags
// bit: 0; mask: 0x0001; 1= columns are hidden
$isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0;
// bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline)
$level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8;
// bit: 12; mask: 0x1000; 1 = collapsed
$isCollapsed = (bool) ((0x1000 & self::getUInt2d($recordData, 8)) >> 12);
// offset: 10; size: 2; not used
for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) {
if ($lastColumnIndex == 255 || $lastColumnIndex == 256) {
$this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256);
break;
}
$this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256);
$this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden);
$this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level);
$this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed);
if (isset($this->mapCellXfIndex[$xfIndex])) {
$this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
}
/**
* ROW.
*
* This record contains the properties of a single row in a
* sheet. Rows and cells in a sheet are divided into blocks
* of 32 rows.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readRow(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; index of this row
$r = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to column of the first cell which is described by a cell record
// offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1
// offset: 6; size: 2;
// bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point
$height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0;
// bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height
$useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
if (!$useDefaultHeight) {
$this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
}
// offset: 8; size: 2; not used
// offset: 10; size: 2; not used in BIFF5-BIFF8
// offset: 12; size: 4; option flags and default row formatting
// bit: 2-0: mask: 0x00000007; outline level of the row
$level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0;
$this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level);
// bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed
$isCollapsed = (bool) ((0x00000010 & self::getInt4d($recordData, 12)) >> 4);
$this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed);
// bit: 5; mask: 0x00000020; 1 = row is hidden
$isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5;
$this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden);
// bit: 7; mask: 0x00000080; 1 = row has explicit format
$hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7;
// bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record
$xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16;
if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) {
$this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
/**
* Read RK record
* This record represents a cell that contains an RK value
* (encoded integer or floating-point value). If a
* floating-point value cannot be encoded to an RK value,
* a NUMBER record will be written. This record replaces the
* record INTEGER written in BIFF2.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readRk(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to column
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: 4; size: 2; index to XF record
$xfIndex = self::getUInt2d($recordData, 4);
// offset: 6; size: 4; RK value
$rknum = self::getInt4d($recordData, 6);
$numValue = self::getIEEE754($rknum);
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add style information
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
// add cell
$cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
}
}
/**
* Read LABELSST record
* This record represents a cell that contains a string. It
* replaces the LABEL record and RSTRING record used in
* BIFF2-BIFF5.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readLabelSst(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to column
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
$emptyCell = true;
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: 4; size: 2; index to XF record
$xfIndex = self::getUInt2d($recordData, 4);
// offset: 6; size: 4; index to SST record
$index = self::getInt4d($recordData, 6);
// add cell
if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) {
// then we should treat as rich text
$richText = new RichText();
$charPos = 0;
$sstCount = count($this->sst[$index]['fmtRuns']);
for ($i = 0; $i <= $sstCount; ++$i) {
if (isset($fmtRuns[$i])) {
$text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos);
$charPos = $fmtRuns[$i]['charPos'];
} else {
$text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value']));
}
if (StringHelper::countCharacters($text) > 0) {
if ($i == 0) { // first text run, no style
$richText->createText($text);
} else {
$textRun = $richText->createTextRun($text);
if (isset($fmtRuns[$i - 1])) {
if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
$fontIndex = $fmtRuns[$i - 1]['fontIndex'];
} else {
// this has to do with that index 4 is omitted in all BIFF versions for some stra nge reason
// check the OpenOffice documentation of the FONT record
$fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
}
if (array_key_exists($fontIndex, $this->objFonts) === false) {
$fontIndex = count($this->objFonts) - 1;
}
$textRun->setFont(clone $this->objFonts[$fontIndex]);
}
}
}
}
if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') {
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
$cell->setValueExplicit($richText, DataType::TYPE_STRING);
$emptyCell = false;
}
} else {
if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') {
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
$cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING);
$emptyCell = false;
}
}
if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) {
// add style information
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
/**
* Read MULRK record
* This record represents a cell range containing RK value
* cells. All cells are located in the same row.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readMulRk(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to first column
$colFirst = self::getUInt2d($recordData, 2);
// offset: var; size: 2; index to last column
$colLast = self::getUInt2d($recordData, $length - 2);
$columns = $colLast - $colFirst + 1;
// offset within record data
$offset = 4;
for ($i = 1; $i <= $columns; ++$i) {
$columnString = Coordinate::stringFromColumnIndex($colFirst + $i);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: var; size: 2; index to XF record
$xfIndex = self::getUInt2d($recordData, $offset);
// offset: var; size: 4; RK value
$numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2));
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add style
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
// add cell value
$cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
}
$offset += 6;
}
}
/**
* Read NUMBER record
* This record represents a cell that contains a
* floating-point value.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readNumber(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size 2; index to column
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset 4; size: 2; index to XF record
$xfIndex = self::getUInt2d($recordData, 4);
$numValue = self::extractNumber(substr($recordData, 6, 8));
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add cell style
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
// add cell value
$cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC);
}
}
/**
* Read FORMULA record + perhaps a following STRING record if formula result is a string
* This record contains the token array and the result of a
* formula cell.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readFormula(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; row index
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; col index
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
// offset: 20: size: variable; formula structure
$formulaStructure = substr($recordData, 20);
// offset: 14: size: 2; option flags, recalculate always, recalculate on open etc.
$options = self::getUInt2d($recordData, 14);
// bit: 0; mask: 0x0001; 1 = recalculate always
// bit: 1; mask: 0x0002; 1 = calculate on open
// bit: 2; mask: 0x0008; 1 = part of a shared formula
$isPartOfSharedFormula = (bool) (0x0008 & $options);
// WARNING:
// We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true
// the formula data may be ordinary formula data, therefore we need to check
// explicitly for the tExp token (0x01)
$isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01;
if ($isPartOfSharedFormula) {
// part of shared formula which means there will be a formula with a tExp token and nothing else
// get the base cell, grab tExp token
$baseRow = self::getUInt2d($formulaStructure, 3);
$baseCol = self::getUInt2d($formulaStructure, 5);
$this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1);
}
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
if ($isPartOfSharedFormula) {
// formula is added to this cell after the sheet has been read
$this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell;
}
// offset: 16: size: 4; not used
// offset: 4; size: 2; XF index
$xfIndex = self::getUInt2d($recordData, 4);
// offset: 6; size: 8; result of the formula
if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) {
// String formula. Result follows in appended STRING record
$dataType = DataType::TYPE_STRING;
// read possible SHAREDFMLA record
$code = self::getUInt2d($this->data, $this->pos);
if ($code == self::XLS_TYPE_SHAREDFMLA) {
$this->readSharedFmla();
}
// read STRING record
$value = $this->readString();
} elseif (
(ord($recordData[6]) == 1)
&& (ord($recordData[12]) == 255)
&& (ord($recordData[13]) == 255)
) {
// Boolean formula. Result is in +2; 0=false, 1=true
$dataType = DataType::TYPE_BOOL;
$value = (bool) ord($recordData[8]);
} elseif (
(ord($recordData[6]) == 2)
&& (ord($recordData[12]) == 255)
&& (ord($recordData[13]) == 255)
) {
// Error formula. Error code is in +2
$dataType = DataType::TYPE_ERROR;
$value = Xls\ErrorCode::lookup(ord($recordData[8]));
} elseif (
(ord($recordData[6]) == 3)
&& (ord($recordData[12]) == 255)
&& (ord($recordData[13]) == 255)
) {
// Formula result is a null string
$dataType = DataType::TYPE_NULL;
$value = '';
} else {
// forumla result is a number, first 14 bytes like _NUMBER record
$dataType = DataType::TYPE_NUMERIC;
$value = self::extractNumber(substr($recordData, 6, 8));
}
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add cell style
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
// store the formula
if (!$isPartOfSharedFormula) {
// not part of shared formula
// add cell value. If we can read formula, populate with formula, otherwise just used cached value
try {
if ($this->version != self::XLS_BIFF8) {
throw new Exception('Not BIFF8. Can only read BIFF8 formulas');
}
$formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language
$cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA);
} catch (PhpSpreadsheetException $e) {
$cell->setValueExplicit($value, $dataType);
}
} else {
if ($this->version == self::XLS_BIFF8) {
// do nothing at this point, formula id added later in the code
} else {
$cell->setValueExplicit($value, $dataType);
}
}
// store the cached calculated value
$cell->setCalculatedValue($value);
}
}
/**
* Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader,
* which usually contains relative references.
* These will be used to construct the formula in each shared formula part after the sheet is read.
*/
private function readSharedFmla(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything
$cellRange = substr($recordData, 0, 6);
$cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax
// offset: 6, size: 1; not used
// offset: 7, size: 1; number of existing FORMULA records for this shared formula
$no = ord($recordData[7]);
// offset: 8, size: var; Binary token array of the shared formula
$formula = substr($recordData, 8);
// at this point we only store the shared formula for later use
$this->sharedFormulas[$this->baseCell] = $formula;
}
/**
* Read a STRING record from current stream position and advance the stream pointer to next record
* This record is used for storing result from FORMULA record when it is a string, and
* it occurs directly after the FORMULA record.
*
* @return string The string contents as UTF-8
*/
private function readString()
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringLong($recordData);
$value = $string['value'];
} else {
$string = $this->readByteStringLong($recordData);
$value = $string['value'];
}
return $value;
}
/**
* Read BOOLERR record
* This record represents a Boolean value or error value
* cell.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readBoolErr(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; row index
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; column index
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: 4; size: 2; index to XF record
$xfIndex = self::getUInt2d($recordData, 4);
// offset: 6; size: 1; the boolean value or error value
$boolErr = ord($recordData[6]);
// offset: 7; size: 1; 0=boolean; 1=error
$isError = ord($recordData[7]);
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
switch ($isError) {
case 0: // boolean
$value = (bool) $boolErr;
// add cell value
$cell->setValueExplicit($value, DataType::TYPE_BOOL);
break;
case 1: // error type
$value = Xls\ErrorCode::lookup($boolErr);
// add cell value
$cell->setValueExplicit($value, DataType::TYPE_ERROR);
break;
}
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add cell style
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
/**
* Read MULBLANK record
* This record represents a cell range of empty cells. All
* cells are located in the same row.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readMulBlank(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to first column
$fc = self::getUInt2d($recordData, 2);
// offset: 4; size: 2 x nc; list of indexes to XF records
// add style information
if (!$this->readDataOnly && $this->readEmptyCells) {
for ($i = 0; $i < $length / 2 - 3; ++$i) {
$columnString = Coordinate::stringFromColumnIndex($fc + $i + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
$xfIndex = self::getUInt2d($recordData, 4 + 2 * $i);
if (isset($this->mapCellXfIndex[$xfIndex])) {
$this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
}
// offset: 6; size 2; index to last column (not needed)
}
/**
* Read LABEL record
* This record represents a cell that contains a string. In
* BIFF8 it is usually replaced by the LABELSST record.
* Excel still uses this record, if it copies unformatted
* text cells to the clipboard.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readLabel(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; index to row
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to column
$column = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($column + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: 4; size: 2; XF index
$xfIndex = self::getUInt2d($recordData, 4);
// add cell value
// todo: what if string is very long? continue record
if ($this->version == self::XLS_BIFF8) {
$string = self::readUnicodeStringLong(substr($recordData, 6));
$value = $string['value'];
} else {
$string = $this->readByteStringLong(substr($recordData, 6));
$value = $string['value'];
}
if ($this->readEmptyCells || trim($value) !== '') {
$cell = $this->phpSheet->getCell($columnString . ($row + 1));
$cell->setValueExplicit($value, DataType::TYPE_STRING);
if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) {
// add cell style
$cell->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
}
/**
* Read BLANK record.
*/
private function readBlank(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; row index
$row = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; col index
$col = self::getUInt2d($recordData, 2);
$columnString = Coordinate::stringFromColumnIndex($col + 1);
// Read cell?
if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) {
// offset: 4; size: 2; XF index
$xfIndex = self::getUInt2d($recordData, 4);
// add style information
if (!$this->readDataOnly && $this->readEmptyCells && isset($this->mapCellXfIndex[$xfIndex])) {
$this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]);
}
}
}
/**
* Read MSODRAWING record.
*/
private function readMsoDrawing(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
// get spliced record data
$splicedRecordData = $this->getSplicedRecordData();
$recordData = $splicedRecordData['recordData'];
$this->drawingData .= $recordData;
}
/**
* Read OBJ record.
*/
private function readObj(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly || $this->version != self::XLS_BIFF8) {
return;
}
// recordData consists of an array of subrecords looking like this:
// ft: 2 bytes; ftCmo type (0x15)
// cb: 2 bytes; size in bytes of ftCmo data
// ot: 2 bytes; Object Type
// id: 2 bytes; Object id number
// grbit: 2 bytes; Option Flags
// data: var; subrecord data
// for now, we are just interested in the second subrecord containing the object type
$ftCmoType = self::getUInt2d($recordData, 0);
$cbCmoSize = self::getUInt2d($recordData, 2);
$otObjType = self::getUInt2d($recordData, 4);
$idObjID = self::getUInt2d($recordData, 6);
$grbitOpts = self::getUInt2d($recordData, 6);
$this->objs[] = [
'ftCmoType' => $ftCmoType,
'cbCmoSize' => $cbCmoSize,
'otObjType' => $otObjType,
'idObjID' => $idObjID,
'grbitOpts' => $grbitOpts,
];
$this->textObjRef = $idObjID;
}
/**
* Read WINDOW2 record.
*/
private function readWindow2(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; option flags
$options = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; index to first visible row
$firstVisibleRow = self::getUInt2d($recordData, 2);
// offset: 4; size: 2; index to first visible colum
$firstVisibleColumn = self::getUInt2d($recordData, 4);
$zoomscaleInPageBreakPreview = 0;
$zoomscaleInNormalView = 0;
if ($this->version === self::XLS_BIFF8) {
// offset: 8; size: 2; not used
// offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%)
// offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%)
// offset: 14; size: 4; not used
if (!isset($recordData[10])) {
$zoomscaleInPageBreakPreview = 0;
} else {
$zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10);
}
if ($zoomscaleInPageBreakPreview === 0) {
$zoomscaleInPageBreakPreview = 60;
}
if (!isset($recordData[12])) {
$zoomscaleInNormalView = 0;
} else {
$zoomscaleInNormalView = self::getUInt2d($recordData, 12);
}
if ($zoomscaleInNormalView === 0) {
$zoomscaleInNormalView = 100;
}
}
// bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines
$showGridlines = (bool) ((0x0002 & $options) >> 1);
$this->phpSheet->setShowGridlines($showGridlines);
// bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers
$showRowColHeaders = (bool) ((0x0004 & $options) >> 2);
$this->phpSheet->setShowRowColHeaders($showRowColHeaders);
// bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen
$this->frozen = (bool) ((0x0008 & $options) >> 3);
// bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left
$this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6));
// bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active
$isActive = (bool) ((0x0400 & $options) >> 10);
if ($isActive) {
$this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet));
}
// bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view
$isPageBreakPreview = (bool) ((0x0800 & $options) >> 11);
//FIXME: set $firstVisibleRow and $firstVisibleColumn
if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) {
//NOTE: this setting is inferior to page layout view(Excel2007-)
$view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL;
$this->phpSheet->getSheetView()->setView($view);
if ($this->version === self::XLS_BIFF8) {
$zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView;
$this->phpSheet->getSheetView()->setZoomScale($zoomScale);
$this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView);
}
}
}
/**
* Read PLV Record(Created by Excel2007 or upper).
*/
private function readPageLayoutView(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; rt
//->ignore
$rt = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; grbitfr
//->ignore
$grbitFrt = self::getUInt2d($recordData, 2);
// offset: 4; size: 8; reserved
//->ignore
// offset: 12; size 2; zoom scale
$wScalePLV = self::getUInt2d($recordData, 12);
// offset: 14; size 2; grbit
$grbit = self::getUInt2d($recordData, 14);
// decomprise grbit
$fPageLayoutView = $grbit & 0x01;
$fRulerVisible = ($grbit >> 1) & 0x01; //no support
$fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support
if ($fPageLayoutView === 1) {
$this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT);
$this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT
}
//otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW.
}
/**
* Read SCL record.
*/
private function readScl(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// offset: 0; size: 2; numerator of the view magnification
$numerator = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; numerator of the view magnification
$denumerator = self::getUInt2d($recordData, 2);
// set the zoom scale (in percent)
$this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator);
}
/**
* Read PANE record.
*/
private function readPane(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 2; position of vertical split
$px = self::getUInt2d($recordData, 0);
// offset: 2; size: 2; position of horizontal split
$py = self::getUInt2d($recordData, 2);
// offset: 4; size: 2; top most visible row in the bottom pane
$rwTop = self::getUInt2d($recordData, 4);
// offset: 6; size: 2; first visible left column in the right pane
$colLeft = self::getUInt2d($recordData, 6);
if ($this->frozen) {
// frozen panes
$cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1);
$topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1);
$this->phpSheet->freezePane($cell, $topLeftCell);
}
// unfrozen panes; split windows; not supported by PhpSpreadsheet core
}
}
/**
* Read SELECTION record. There is one such record for each pane in the sheet.
*/
private function readSelection(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 1; pane identifier
$paneId = ord($recordData[0]);
// offset: 1; size: 2; index to row of the active cell
$r = self::getUInt2d($recordData, 1);
// offset: 3; size: 2; index to column of the active cell
$c = self::getUInt2d($recordData, 3);
// offset: 5; size: 2; index into the following cell range list to the
// entry that contains the active cell
$index = self::getUInt2d($recordData, 5);
// offset: 7; size: var; cell range address list containing all selected cell ranges
$data = substr($recordData, 7);
$cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax
$selectedCells = $cellRangeAddressList['cellRangeAddresses'][0];
// first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!)
if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) {
$selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells);
}
// first row '1' + last row '65536' indicates that full column is selected
if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) {
$selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells);
}
// first column 'A' + last column 'IV' indicates that full row is selected
if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) {
$selectedCells = (string) preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells);
}
$this->phpSheet->setSelectedCells($selectedCells);
}
}
private function includeCellRangeFiltered(string $cellRangeAddress): bool
{
$includeCellRange = true;
if ($this->getReadFilter() !== null) {
$includeCellRange = false;
$rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress);
++$rangeBoundaries[1][0];
for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) {
for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) {
if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) {
$includeCellRange = true;
break 2;
}
}
}
}
return $includeCellRange;
}
/**
* MERGEDCELLS.
*
* This record contains the addresses of merged cell ranges
* in the current sheet.
*
* -- "OpenOffice.org's Documentation of the Microsoft
* Excel File Format"
*/
private function readMergedCells(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) {
$cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData);
foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) {
if (
(strpos($cellRangeAddress, ':') !== false) &&
($this->includeCellRangeFiltered($cellRangeAddress))
) {
$this->phpSheet->mergeCells($cellRangeAddress, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
}
}
}
/**
* Read HYPERLINK record.
*/
private function readHyperLink(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if (!$this->readDataOnly) {
// offset: 0; size: 8; cell range address of all cells containing this hyperlink
try {
$cellRange = $this->readBIFF8CellRangeAddressFixed($recordData);
} catch (PhpSpreadsheetException $e) {
return;
}
// offset: 8, size: 16; GUID of StdLink
// offset: 24, size: 4; unknown value
// offset: 28, size: 4; option flags
// bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL
$isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0;
// bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL
$isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1;
// bit: 2 (and 4); mask: 0x00000014; 0 = no description
$hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2;
// bit: 3; mask: 0x00000008; 0 = no text, 1 = has text
$hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3;
// bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame
$hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7;
// bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name)
$isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8;
// offset within record data
$offset = 32;
if ($hasDesc) {
// offset: 32; size: var; character count of description text
$dl = self::getInt4d($recordData, 32);
// offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated
$desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false);
$offset += 4 + 2 * $dl;
}
if ($hasFrame) {
$fl = self::getInt4d($recordData, $offset);
$offset += 4 + 2 * $fl;
}
// detect type of hyperlink (there are 4 types)
$hyperlinkType = null;
if ($isUNC) {
$hyperlinkType = 'UNC';
} elseif (!$isFileLinkOrUrl) {
$hyperlinkType = 'workbook';
} elseif (ord($recordData[$offset]) == 0x03) {
$hyperlinkType = 'local';
} elseif (ord($recordData[$offset]) == 0xE0) {
$hyperlinkType = 'URL';
}
switch ($hyperlinkType) {
case 'URL':
// section 5.58.2: Hyperlink containing a URL
// e.g. http://example.org/index.php
// offset: var; size: 16; GUID of URL Moniker
$offset += 16;
// offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word
$us = self::getInt4d($recordData, $offset);
$offset += 4;
// offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated
$url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false);
$nullOffset = strpos($url, chr(0x00));
if ($nullOffset) {
$url = substr($url, 0, $nullOffset);
}
$url .= $hasText ? '#' : '';
$offset += $us;
break;
case 'local':
// section 5.58.3: Hyperlink to local file
// examples:
// mydoc.txt
// ../../somedoc.xls#Sheet!A1
// offset: var; size: 16; GUI of File Moniker
$offset += 16;
// offset: var; size: 2; directory up-level count.
$upLevelCount = self::getUInt2d($recordData, $offset);
$offset += 2;
// offset: var; size: 4; character count of the shortened file path and name, including trailing zero word
$sl = self::getInt4d($recordData, $offset);
$offset += 4;
// offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string)
$shortenedFilePath = substr($recordData, $offset, $sl);
$shortenedFilePath = self::encodeUTF16($shortenedFilePath, true);
$shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero
$offset += $sl;
// offset: var; size: 24; unknown sequence
$offset += 24;
// extended file path
// offset: var; size: 4; size of the following file link field including string lenth mark
$sz = self::getInt4d($recordData, $offset);
$offset += 4;
// only present if $sz > 0
if ($sz > 0) {
// offset: var; size: 4; size of the character array of the extended file path and name
$xl = self::getInt4d($recordData, $offset);
$offset += 4;
// offset: var; size 2; unknown
$offset += 2;
// offset: var; size $xl; character array of the extended file path and name.
$extendedFilePath = substr($recordData, $offset, $xl);
$extendedFilePath = self::encodeUTF16($extendedFilePath, false);
$offset += $xl;
}
// construct the path
$url = str_repeat('..\\', $upLevelCount);
$url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available
$url .= $hasText ? '#' : '';
break;
case 'UNC':
// section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path
// todo: implement
return;
case 'workbook':
// section 5.58.5: Hyperlink to the Current Workbook
// e.g. Sheet2!B1:C2, stored in text mark field
$url = 'sheet://';
break;
default:
return;
}
if ($hasText) {
// offset: var; size: 4; character count of text mark including trailing zero word
$tl = self::getInt4d($recordData, $offset);
$offset += 4;
// offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated
$text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false);
$url .= $text;
}
// apply the hyperlink to all the relevant cells
foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) {
$this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url);
}
}
}
/**
* Read DATAVALIDATIONS record.
*/
private function readDataValidations(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
}
/**
* Read DATAVALIDATION record.
*/
private function readDataValidation(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 4; Options
$options = self::getInt4d($recordData, 0);
// bit: 0-3; mask: 0x0000000F; type
$type = (0x0000000F & $options) >> 0;
$type = Xls\DataValidationHelper::type($type);
// bit: 4-6; mask: 0x00000070; error type
$errorStyle = (0x00000070 & $options) >> 4;
$errorStyle = Xls\DataValidationHelper::errorStyle($errorStyle);
// bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list)
// I have only seen cases where this is 1
$explicitFormula = (0x00000080 & $options) >> 7;
// bit: 8; mask: 0x00000100; 1= empty cells allowed
$allowBlank = (0x00000100 & $options) >> 8;
// bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity
$suppressDropDown = (0x00000200 & $options) >> 9;
// bit: 18; mask: 0x00040000; 1= show prompt box if cell selected
$showInputMessage = (0x00040000 & $options) >> 18;
// bit: 19; mask: 0x00080000; 1= show error box if invalid values entered
$showErrorMessage = (0x00080000 & $options) >> 19;
// bit: 20-23; mask: 0x00F00000; condition operator
$operator = (0x00F00000 & $options) >> 20;
$operator = Xls\DataValidationHelper::operator($operator);
if ($type === null || $errorStyle === null || $operator === null) {
return;
}
// offset: 4; size: var; title of the prompt box
$offset = 4;
$string = self::readUnicodeStringLong(substr($recordData, $offset));
$promptTitle = $string['value'] !== chr(0) ? $string['value'] : '';
$offset += $string['size'];
// offset: var; size: var; title of the error box
$string = self::readUnicodeStringLong(substr($recordData, $offset));
$errorTitle = $string['value'] !== chr(0) ? $string['value'] : '';
$offset += $string['size'];
// offset: var; size: var; text of the prompt box
$string = self::readUnicodeStringLong(substr($recordData, $offset));
$prompt = $string['value'] !== chr(0) ? $string['value'] : '';
$offset += $string['size'];
// offset: var; size: var; text of the error box
$string = self::readUnicodeStringLong(substr($recordData, $offset));
$error = $string['value'] !== chr(0) ? $string['value'] : '';
$offset += $string['size'];
// offset: var; size: 2; size of the formula data for the first condition
$sz1 = self::getUInt2d($recordData, $offset);
$offset += 2;
// offset: var; size: 2; not used
$offset += 2;
// offset: var; size: $sz1; formula data for first condition (without size field)
$formula1 = substr($recordData, $offset, $sz1);
$formula1 = pack('v', $sz1) . $formula1; // prepend the length
try {
$formula1 = $this->getFormulaFromStructure($formula1);
// in list type validity, null characters are used as item separators
if ($type == DataValidation::TYPE_LIST) {
$formula1 = str_replace(chr(0), ',', $formula1);
}
} catch (PhpSpreadsheetException $e) {
return;
}
$offset += $sz1;
// offset: var; size: 2; size of the formula data for the first condition
$sz2 = self::getUInt2d($recordData, $offset);
$offset += 2;
// offset: var; size: 2; not used
$offset += 2;
// offset: var; size: $sz2; formula data for second condition (without size field)
$formula2 = substr($recordData, $offset, $sz2);
$formula2 = pack('v', $sz2) . $formula2; // prepend the length
try {
$formula2 = $this->getFormulaFromStructure($formula2);
} catch (PhpSpreadsheetException $e) {
return;
}
$offset += $sz2;
// offset: var; size: var; cell range address list with
$cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset));
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
foreach ($cellRangeAddresses as $cellRange) {
$stRange = $this->phpSheet->shrinkRangeToFit($cellRange);
foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) {
$objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation();
$objValidation->setType($type);
$objValidation->setErrorStyle($errorStyle);
$objValidation->setAllowBlank((bool) $allowBlank);
$objValidation->setShowInputMessage((bool) $showInputMessage);
$objValidation->setShowErrorMessage((bool) $showErrorMessage);
$objValidation->setShowDropDown(!$suppressDropDown);
$objValidation->setOperator($operator);
$objValidation->setErrorTitle($errorTitle);
$objValidation->setError($error);
$objValidation->setPromptTitle($promptTitle);
$objValidation->setPrompt($prompt);
$objValidation->setFormula1($formula1);
$objValidation->setFormula2($formula2);
}
}
}
/**
* Read SHEETLAYOUT record. Stores sheet tab color information.
*/
private function readSheetLayout(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// local pointer in record data
$offset = 0;
if (!$this->readDataOnly) {
// offset: 0; size: 2; repeated record identifier 0x0862
// offset: 2; size: 10; not used
// offset: 12; size: 4; size of record data
// Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?)
$sz = self::getInt4d($recordData, 12);
switch ($sz) {
case 0x14:
// offset: 16; size: 2; color index for sheet tab
$colorIndex = self::getUInt2d($recordData, 16);
$color = Xls\Color::map($colorIndex, $this->palette, $this->version);
$this->phpSheet->getTabColor()->setRGB($color['rgb']);
break;
case 0x28:
// TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007
return;
}
}
}
/**
* Read SHEETPROTECTION record (FEATHEADR).
*/
private function readSheetProtection(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2; repeated record header
// offset: 2; size: 2; FRT cell reference flag (=0 currently)
// offset: 4; size: 8; Currently not used and set to 0
// offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag)
$isf = self::getUInt2d($recordData, 12);
if ($isf != 2) {
return;
}
// offset: 14; size: 1; =1 since this is a feat header
// offset: 15; size: 4; size of rgbHdrSData
// rgbHdrSData, assume "Enhanced Protection"
// offset: 19; size: 2; option flags
$options = self::getUInt2d($recordData, 19);
// bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects
// Note - do not negate $bool
$bool = (0x0001 & $options) >> 0;
$this->phpSheet->getProtection()->setObjects((bool) $bool);
// bit: 1; mask 0x0002; edit scenarios
// Note - do not negate $bool
$bool = (0x0002 & $options) >> 1;
$this->phpSheet->getProtection()->setScenarios((bool) $bool);
// bit: 2; mask 0x0004; format cells
$bool = (0x0004 & $options) >> 2;
$this->phpSheet->getProtection()->setFormatCells(!$bool);
// bit: 3; mask 0x0008; format columns
$bool = (0x0008 & $options) >> 3;
$this->phpSheet->getProtection()->setFormatColumns(!$bool);
// bit: 4; mask 0x0010; format rows
$bool = (0x0010 & $options) >> 4;
$this->phpSheet->getProtection()->setFormatRows(!$bool);
// bit: 5; mask 0x0020; insert columns
$bool = (0x0020 & $options) >> 5;
$this->phpSheet->getProtection()->setInsertColumns(!$bool);
// bit: 6; mask 0x0040; insert rows
$bool = (0x0040 & $options) >> 6;
$this->phpSheet->getProtection()->setInsertRows(!$bool);
// bit: 7; mask 0x0080; insert hyperlinks
$bool = (0x0080 & $options) >> 7;
$this->phpSheet->getProtection()->setInsertHyperlinks(!$bool);
// bit: 8; mask 0x0100; delete columns
$bool = (0x0100 & $options) >> 8;
$this->phpSheet->getProtection()->setDeleteColumns(!$bool);
// bit: 9; mask 0x0200; delete rows
$bool = (0x0200 & $options) >> 9;
$this->phpSheet->getProtection()->setDeleteRows(!$bool);
// bit: 10; mask 0x0400; select locked cells
// Note that this is opposite of most of above.
$bool = (0x0400 & $options) >> 10;
$this->phpSheet->getProtection()->setSelectLockedCells((bool) $bool);
// bit: 11; mask 0x0800; sort cell range
$bool = (0x0800 & $options) >> 11;
$this->phpSheet->getProtection()->setSort(!$bool);
// bit: 12; mask 0x1000; auto filter
$bool = (0x1000 & $options) >> 12;
$this->phpSheet->getProtection()->setAutoFilter(!$bool);
// bit: 13; mask 0x2000; pivot tables
$bool = (0x2000 & $options) >> 13;
$this->phpSheet->getProtection()->setPivotTables(!$bool);
// bit: 14; mask 0x4000; select unlocked cells
// Note that this is opposite of most of above.
$bool = (0x4000 & $options) >> 14;
$this->phpSheet->getProtection()->setSelectUnlockedCells((bool) $bool);
// offset: 21; size: 2; not used
}
/**
* Read RANGEPROTECTION record
* Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification,
* where it is referred to as FEAT record.
*/
private function readRangeProtection(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer to next record
$this->pos += 4 + $length;
// local pointer in record data
$offset = 0;
if (!$this->readDataOnly) {
$offset += 12;
// offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag
$isf = self::getUInt2d($recordData, 12);
if ($isf != 2) {
// we only read FEAT records of type 2
return;
}
$offset += 2;
$offset += 5;
// offset: 19; size: 2; count of ref ranges this feature is on
$cref = self::getUInt2d($recordData, 19);
$offset += 2;
$offset += 6;
// offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record)
$cellRanges = [];
for ($i = 0; $i < $cref; ++$i) {
try {
$cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8));
} catch (PhpSpreadsheetException $e) {
return;
}
$cellRanges[] = $cellRange;
$offset += 8;
}
// offset: var; size: var; variable length of feature specific data
$rgbFeat = substr($recordData, $offset);
$offset += 4;
// offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit)
$wPassword = self::getInt4d($recordData, $offset);
$offset += 4;
// Apply range protection to sheet
if ($cellRanges) {
$this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true);
}
}
}
/**
* Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record
* When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented.
* In this case, we must treat the CONTINUE record as a MSODRAWING record.
*/
private function readContinue(): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// check if we are reading drawing data
// this is in case a free CONTINUE record occurs in other circumstances we are unaware of
if ($this->drawingData == '') {
// move stream pointer to next record
$this->pos += 4 + $length;
return;
}
// check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data
if ($length < 4) {
// move stream pointer to next record
$this->pos += 4 + $length;
return;
}
// dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record
// look inside CONTINUE record to see if it looks like a part of an Escher stream
// we know that Escher stream may be split at least at
// 0xF003 MsofbtSpgrContainer
// 0xF004 MsofbtSpContainer
// 0xF00D MsofbtClientTextbox
$validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more
$splitPoint = self::getUInt2d($recordData, 2);
if (in_array($splitPoint, $validSplitPoints)) {
// get spliced record data (and move pointer to next record)
$splicedRecordData = $this->getSplicedRecordData();
$this->drawingData .= $splicedRecordData['recordData'];
return;
}
// move stream pointer to next record
$this->pos += 4 + $length;
}
/**
* Reads a record from current position in data stream and continues reading data as long as CONTINUE
* records are found. Splices the record data pieces and returns the combined string as if record data
* is in one piece.
* Moves to next current position in data stream to start of next record different from a CONtINUE record.
*
* @return array
*/
private function getSplicedRecordData()
{
$data = '';
$spliceOffsets = [];
$i = 0;
$spliceOffsets[0] = 0;
do {
++$i;
// offset: 0; size: 2; identifier
$identifier = self::getUInt2d($this->data, $this->pos);
// offset: 2; size: 2; length
$length = self::getUInt2d($this->data, $this->pos + 2);
$data .= $this->readRecordData($this->data, $this->pos + 4, $length);
$spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length;
$this->pos += 4 + $length;
$nextIdentifier = self::getUInt2d($this->data, $this->pos);
} while ($nextIdentifier == self::XLS_TYPE_CONTINUE);
return [
'recordData' => $data,
'spliceOffsets' => $spliceOffsets,
];
}
/**
* Convert formula structure into human readable Excel formula like 'A3+A5*5'.
*
* @param string $formulaStructure The complete binary data for the formula
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
*
* @return string Human readable formula
*/
private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1')
{
// offset: 0; size: 2; size of the following formula data
$sz = self::getUInt2d($formulaStructure, 0);
// offset: 2; size: sz
$formulaData = substr($formulaStructure, 2, $sz);
// offset: 2 + sz; size: variable (optional)
if (strlen($formulaStructure) > 2 + $sz) {
$additionalData = substr($formulaStructure, 2 + $sz);
} else {
$additionalData = '';
}
return $this->getFormulaFromData($formulaData, $additionalData, $baseCell);
}
/**
* Take formula data and additional data for formula and return human readable formula.
*
* @param string $formulaData The binary data for the formula itself
* @param string $additionalData Additional binary data going with the formula
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
*
* @return string Human readable formula
*/
private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1')
{
// start parsing the formula data
$tokens = [];
while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) {
$tokens[] = $token;
$formulaData = substr($formulaData, $token['size']);
}
$formulaString = $this->createFormulaFromTokens($tokens, $additionalData);
return $formulaString;
}
/**
* Take array of tokens together with additional data for formula and return human readable formula.
*
* @param array $tokens
* @param string $additionalData Additional binary data going with the formula
*
* @return string Human readable formula
*/
private function createFormulaFromTokens($tokens, $additionalData)
{
// empty formula?
if (empty($tokens)) {
return '';
}
$formulaStrings = [];
foreach ($tokens as $token) {
// initialize spaces
$space0 = $space0 ?? ''; // spaces before next token, not tParen
$space1 = $space1 ?? ''; // carriage returns before next token, not tParen
$space2 = $space2 ?? ''; // spaces before opening parenthesis
$space3 = $space3 ?? ''; // carriage returns before opening parenthesis
$space4 = $space4 ?? ''; // spaces before closing parenthesis
$space5 = $space5 ?? ''; // carriage returns before closing parenthesis
switch ($token['name']) {
case 'tAdd': // addition
case 'tConcat': // addition
case 'tDiv': // division
case 'tEQ': // equality
case 'tGE': // greater than or equal
case 'tGT': // greater than
case 'tIsect': // intersection
case 'tLE': // less than or equal
case 'tList': // less than or equal
case 'tLT': // less than
case 'tMul': // multiplication
case 'tNE': // multiplication
case 'tPower': // power
case 'tRange': // range
case 'tSub': // subtraction
$op2 = array_pop($formulaStrings);
$op1 = array_pop($formulaStrings);
$formulaStrings[] = "$op1$space1$space0{$token['data']}$op2";
unset($space0, $space1);
break;
case 'tUplus': // unary plus
case 'tUminus': // unary minus
$op = array_pop($formulaStrings);
$formulaStrings[] = "$space1$space0{$token['data']}$op";
unset($space0, $space1);
break;
case 'tPercent': // percent sign
$op = array_pop($formulaStrings);
$formulaStrings[] = "$op$space1$space0{$token['data']}";
unset($space0, $space1);
break;
case 'tAttrVolatile': // indicates volatile function
case 'tAttrIf':
case 'tAttrSkip':
case 'tAttrChoose':
// token is only important for Excel formula evaluator
// do nothing
break;
case 'tAttrSpace': // space / carriage return
// space will be used when next token arrives, do not alter formulaString stack
switch ($token['data']['spacetype']) {
case 'type0':
$space0 = str_repeat(' ', $token['data']['spacecount']);
break;
case 'type1':
$space1 = str_repeat("\n", $token['data']['spacecount']);
break;
case 'type2':
$space2 = str_repeat(' ', $token['data']['spacecount']);
break;
case 'type3':
$space3 = str_repeat("\n", $token['data']['spacecount']);
break;
case 'type4':
$space4 = str_repeat(' ', $token['data']['spacecount']);
break;
case 'type5':
$space5 = str_repeat("\n", $token['data']['spacecount']);
break;
}
break;
case 'tAttrSum': // SUM function with one parameter
$op = array_pop($formulaStrings);
$formulaStrings[] = "{$space1}{$space0}SUM($op)";
unset($space0, $space1);
break;
case 'tFunc': // function with fixed number of arguments
case 'tFuncV': // function with variable number of arguments
if ($token['data']['function'] != '') {
// normal function
$ops = []; // array of operators
for ($i = 0; $i < $token['data']['args']; ++$i) {
$ops[] = array_pop($formulaStrings);
}
$ops = array_reverse($ops);
$formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')';
unset($space0, $space1);
} else {
// add-in function
$ops = []; // array of operators
for ($i = 0; $i < $token['data']['args'] - 1; ++$i) {
$ops[] = array_pop($formulaStrings);
}
$ops = array_reverse($ops);
$function = array_pop($formulaStrings);
$formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')';
unset($space0, $space1);
}
break;
case 'tParen': // parenthesis
$expression = array_pop($formulaStrings);
$formulaStrings[] = "$space3$space2($expression$space5$space4)";
unset($space2, $space3, $space4, $space5);
break;
case 'tArray': // array constant
$constantArray = self::readBIFF8ConstantArray($additionalData);
$formulaStrings[] = $space1 . $space0 . $constantArray['value'];
$additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data
unset($space0, $space1);
break;
case 'tMemArea':
// bite off chunk of additional data
$cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData);
$additionalData = substr($additionalData, $cellRangeAddressList['size']);
$formulaStrings[] = "$space1$space0{$token['data']}";
unset($space0, $space1);
break;
case 'tArea': // cell range address
case 'tBool': // boolean
case 'tErr': // error code
case 'tInt': // integer
case 'tMemErr':
case 'tMemFunc':
case 'tMissArg':
case 'tName':
case 'tNameX':
case 'tNum': // number
case 'tRef': // single cell reference
case 'tRef3d': // 3d cell reference
case 'tArea3d': // 3d cell range reference
case 'tRefN':
case 'tAreaN':
case 'tStr': // string
$formulaStrings[] = "$space1$space0{$token['data']}";
unset($space0, $space1);
break;
}
}
$formulaString = $formulaStrings[0];
return $formulaString;
}
/**
* Fetch next token from binary formula data.
*
* @param string $formulaData Formula data
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
*
* @return array
*/
private function getNextToken($formulaData, $baseCell = 'A1')
{
// offset: 0; size: 1; token id
$id = ord($formulaData[0]); // token id
$name = false; // initialize token name
switch ($id) {
case 0x03:
$name = 'tAdd';
$size = 1;
$data = '+';
break;
case 0x04:
$name = 'tSub';
$size = 1;
$data = '-';
break;
case 0x05:
$name = 'tMul';
$size = 1;
$data = '*';
break;
case 0x06:
$name = 'tDiv';
$size = 1;
$data = '/';
break;
case 0x07:
$name = 'tPower';
$size = 1;
$data = '^';
break;
case 0x08:
$name = 'tConcat';
$size = 1;
$data = '&';
break;
case 0x09:
$name = 'tLT';
$size = 1;
$data = '<';
break;
case 0x0A:
$name = 'tLE';
$size = 1;
$data = '<=';
break;
case 0x0B:
$name = 'tEQ';
$size = 1;
$data = '=';
break;
case 0x0C:
$name = 'tGE';
$size = 1;
$data = '>=';
break;
case 0x0D:
$name = 'tGT';
$size = 1;
$data = '>';
break;
case 0x0E:
$name = 'tNE';
$size = 1;
$data = '<>';
break;
case 0x0F:
$name = 'tIsect';
$size = 1;
$data = ' ';
break;
case 0x10:
$name = 'tList';
$size = 1;
$data = ',';
break;
case 0x11:
$name = 'tRange';
$size = 1;
$data = ':';
break;
case 0x12:
$name = 'tUplus';
$size = 1;
$data = '+';
break;
case 0x13:
$name = 'tUminus';
$size = 1;
$data = '-';
break;
case 0x14:
$name = 'tPercent';
$size = 1;
$data = '%';
break;
case 0x15: // parenthesis
$name = 'tParen';
$size = 1;
$data = null;
break;
case 0x16: // missing argument
$name = 'tMissArg';
$size = 1;
$data = '';
break;
case 0x17: // string
$name = 'tStr';
// offset: 1; size: var; Unicode string, 8-bit string length
$string = self::readUnicodeStringShort(substr($formulaData, 1));
$size = 1 + $string['size'];
$data = self::UTF8toExcelDoubleQuoted($string['value']);
break;
case 0x19: // Special attribute
// offset: 1; size: 1; attribute type flags:
switch (ord($formulaData[1])) {
case 0x01:
$name = 'tAttrVolatile';
$size = 4;
$data = null;
break;
case 0x02:
$name = 'tAttrIf';
$size = 4;
$data = null;
break;
case 0x04:
$name = 'tAttrChoose';
// offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1)
$nc = self::getUInt2d($formulaData, 2);
// offset: 4; size: 2 * $nc
// offset: 4 + 2 * $nc; size: 2
$size = 2 * $nc + 6;
$data = null;
break;
case 0x08:
$name = 'tAttrSkip';
$size = 4;
$data = null;
break;
case 0x10:
$name = 'tAttrSum';
$size = 4;
$data = null;
break;
case 0x40:
case 0x41:
$name = 'tAttrSpace';
$size = 4;
// offset: 2; size: 2; space type and position
switch (ord($formulaData[2])) {
case 0x00:
$spacetype = 'type0';
break;
case 0x01:
$spacetype = 'type1';
break;
case 0x02:
$spacetype = 'type2';
break;
case 0x03:
$spacetype = 'type3';
break;
case 0x04:
$spacetype = 'type4';
break;
case 0x05:
$spacetype = 'type5';
break;
default:
throw new Exception('Unrecognized space type in tAttrSpace token');
}
// offset: 3; size: 1; number of inserted spaces/carriage returns
$spacecount = ord($formulaData[3]);
$data = ['spacetype' => $spacetype, 'spacecount' => $spacecount];
break;
default:
throw new Exception('Unrecognized attribute flag in tAttr token');
}
break;
case 0x1C: // error code
// offset: 1; size: 1; error code
$name = 'tErr';
$size = 2;
$data = Xls\ErrorCode::lookup(ord($formulaData[1]));
break;
case 0x1D: // boolean
// offset: 1; size: 1; 0 = false, 1 = true;
$name = 'tBool';
$size = 2;
$data = ord($formulaData[1]) ? 'TRUE' : 'FALSE';
break;
case 0x1E: // integer
// offset: 1; size: 2; unsigned 16-bit integer
$name = 'tInt';
$size = 3;
$data = self::getUInt2d($formulaData, 1);
break;
case 0x1F: // number
// offset: 1; size: 8;
$name = 'tNum';
$size = 9;
$data = self::extractNumber(substr($formulaData, 1));
$data = str_replace(',', '.', (string) $data); // in case non-English locale
break;
case 0x20: // array constant
case 0x40:
case 0x60:
// offset: 1; size: 7; not used
$name = 'tArray';
$size = 8;
$data = null;
break;
case 0x21: // function with fixed number of arguments
case 0x41:
case 0x61:
$name = 'tFunc';
$size = 3;
// offset: 1; size: 2; index to built-in sheet function
switch (self::getUInt2d($formulaData, 1)) {
case 2:
$function = 'ISNA';
$args = 1;
break;
case 3:
$function = 'ISERROR';
$args = 1;
break;
case 10:
$function = 'NA';
$args = 0;
break;
case 15:
$function = 'SIN';
$args = 1;
break;
case 16:
$function = 'COS';
$args = 1;
break;
case 17:
$function = 'TAN';
$args = 1;
break;
case 18:
$function = 'ATAN';
$args = 1;
break;
case 19:
$function = 'PI';
$args = 0;
break;
case 20:
$function = 'SQRT';
$args = 1;
break;
case 21:
$function = 'EXP';
$args = 1;
break;
case 22:
$function = 'LN';
$args = 1;
break;
case 23:
$function = 'LOG10';
$args = 1;
break;
case 24:
$function = 'ABS';
$args = 1;
break;
case 25:
$function = 'INT';
$args = 1;
break;
case 26:
$function = 'SIGN';
$args = 1;
break;
case 27:
$function = 'ROUND';
$args = 2;
break;
case 30:
$function = 'REPT';
$args = 2;
break;
case 31:
$function = 'MID';
$args = 3;
break;
case 32:
$function = 'LEN';
$args = 1;
break;
case 33:
$function = 'VALUE';
$args = 1;
break;
case 34:
$function = 'TRUE';
$args = 0;
break;
case 35:
$function = 'FALSE';
$args = 0;
break;
case 38:
$function = 'NOT';
$args = 1;
break;
case 39:
$function = 'MOD';
$args = 2;
break;
case 40:
$function = 'DCOUNT';
$args = 3;
break;
case 41:
$function = 'DSUM';
$args = 3;
break;
case 42:
$function = 'DAVERAGE';
$args = 3;
break;
case 43:
$function = 'DMIN';
$args = 3;
break;
case 44:
$function = 'DMAX';
$args = 3;
break;
case 45:
$function = 'DSTDEV';
$args = 3;
break;
case 48:
$function = 'TEXT';
$args = 2;
break;
case 61:
$function = 'MIRR';
$args = 3;
break;
case 63:
$function = 'RAND';
$args = 0;
break;
case 65:
$function = 'DATE';
$args = 3;
break;
case 66:
$function = 'TIME';
$args = 3;
break;
case 67:
$function = 'DAY';
$args = 1;
break;
case 68:
$function = 'MONTH';
$args = 1;
break;
case 69:
$function = 'YEAR';
$args = 1;
break;
case 71:
$function = 'HOUR';
$args = 1;
break;
case 72:
$function = 'MINUTE';
$args = 1;
break;
case 73:
$function = 'SECOND';
$args = 1;
break;
case 74:
$function = 'NOW';
$args = 0;
break;
case 75:
$function = 'AREAS';
$args = 1;
break;
case 76:
$function = 'ROWS';
$args = 1;
break;
case 77:
$function = 'COLUMNS';
$args = 1;
break;
case 83:
$function = 'TRANSPOSE';
$args = 1;
break;
case 86:
$function = 'TYPE';
$args = 1;
break;
case 97:
$function = 'ATAN2';
$args = 2;
break;
case 98:
$function = 'ASIN';
$args = 1;
break;
case 99:
$function = 'ACOS';
$args = 1;
break;
case 105:
$function = 'ISREF';
$args = 1;
break;
case 111:
$function = 'CHAR';
$args = 1;
break;
case 112:
$function = 'LOWER';
$args = 1;
break;
case 113:
$function = 'UPPER';
$args = 1;
break;
case 114:
$function = 'PROPER';
$args = 1;
break;
case 117:
$function = 'EXACT';
$args = 2;
break;
case 118:
$function = 'TRIM';
$args = 1;
break;
case 119:
$function = 'REPLACE';
$args = 4;
break;
case 121:
$function = 'CODE';
$args = 1;
break;
case 126:
$function = 'ISERR';
$args = 1;
break;
case 127:
$function = 'ISTEXT';
$args = 1;
break;
case 128:
$function = 'ISNUMBER';
$args = 1;
break;
case 129:
$function = 'ISBLANK';
$args = 1;
break;
case 130:
$function = 'T';
$args = 1;
break;
case 131:
$function = 'N';
$args = 1;
break;
case 140:
$function = 'DATEVALUE';
$args = 1;
break;
case 141:
$function = 'TIMEVALUE';
$args = 1;
break;
case 142:
$function = 'SLN';
$args = 3;
break;
case 143:
$function = 'SYD';
$args = 4;
break;
case 162:
$function = 'CLEAN';
$args = 1;
break;
case 163:
$function = 'MDETERM';
$args = 1;
break;
case 164:
$function = 'MINVERSE';
$args = 1;
break;
case 165:
$function = 'MMULT';
$args = 2;
break;
case 184:
$function = 'FACT';
$args = 1;
break;
case 189:
$function = 'DPRODUCT';
$args = 3;
break;
case 190:
$function = 'ISNONTEXT';
$args = 1;
break;
case 195:
$function = 'DSTDEVP';
$args = 3;
break;
case 196:
$function = 'DVARP';
$args = 3;
break;
case 198:
$function = 'ISLOGICAL';
$args = 1;
break;
case 199:
$function = 'DCOUNTA';
$args = 3;
break;
case 207:
$function = 'REPLACEB';
$args = 4;
break;
case 210:
$function = 'MIDB';
$args = 3;
break;
case 211:
$function = 'LENB';
$args = 1;
break;
case 212:
$function = 'ROUNDUP';
$args = 2;
break;
case 213:
$function = 'ROUNDDOWN';
$args = 2;
break;
case 214:
$function = 'ASC';
$args = 1;
break;
case 215:
$function = 'DBCS';
$args = 1;
break;
case 221:
$function = 'TODAY';
$args = 0;
break;
case 229:
$function = 'SINH';
$args = 1;
break;
case 230:
$function = 'COSH';
$args = 1;
break;
case 231:
$function = 'TANH';
$args = 1;
break;
case 232:
$function = 'ASINH';
$args = 1;
break;
case 233:
$function = 'ACOSH';
$args = 1;
break;
case 234:
$function = 'ATANH';
$args = 1;
break;
case 235:
$function = 'DGET';
$args = 3;
break;
case 244:
$function = 'INFO';
$args = 1;
break;
case 252:
$function = 'FREQUENCY';
$args = 2;
break;
case 261:
$function = 'ERROR.TYPE';
$args = 1;
break;
case 271:
$function = 'GAMMALN';
$args = 1;
break;
case 273:
$function = 'BINOMDIST';
$args = 4;
break;
case 274:
$function = 'CHIDIST';
$args = 2;
break;
case 275:
$function = 'CHIINV';
$args = 2;
break;
case 276:
$function = 'COMBIN';
$args = 2;
break;
case 277:
$function = 'CONFIDENCE';
$args = 3;
break;
case 278:
$function = 'CRITBINOM';
$args = 3;
break;
case 279:
$function = 'EVEN';
$args = 1;
break;
case 280:
$function = 'EXPONDIST';
$args = 3;
break;
case 281:
$function = 'FDIST';
$args = 3;
break;
case 282:
$function = 'FINV';
$args = 3;
break;
case 283:
$function = 'FISHER';
$args = 1;
break;
case 284:
$function = 'FISHERINV';
$args = 1;
break;
case 285:
$function = 'FLOOR';
$args = 2;
break;
case 286:
$function = 'GAMMADIST';
$args = 4;
break;
case 287:
$function = 'GAMMAINV';
$args = 3;
break;
case 288:
$function = 'CEILING';
$args = 2;
break;
case 289:
$function = 'HYPGEOMDIST';
$args = 4;
break;
case 290:
$function = 'LOGNORMDIST';
$args = 3;
break;
case 291:
$function = 'LOGINV';
$args = 3;
break;
case 292:
$function = 'NEGBINOMDIST';
$args = 3;
break;
case 293:
$function = 'NORMDIST';
$args = 4;
break;
case 294:
$function = 'NORMSDIST';
$args = 1;
break;
case 295:
$function = 'NORMINV';
$args = 3;
break;
case 296:
$function = 'NORMSINV';
$args = 1;
break;
case 297:
$function = 'STANDARDIZE';
$args = 3;
break;
case 298:
$function = 'ODD';
$args = 1;
break;
case 299:
$function = 'PERMUT';
$args = 2;
break;
case 300:
$function = 'POISSON';
$args = 3;
break;
case 301:
$function = 'TDIST';
$args = 3;
break;
case 302:
$function = 'WEIBULL';
$args = 4;
break;
case 303:
$function = 'SUMXMY2';
$args = 2;
break;
case 304:
$function = 'SUMX2MY2';
$args = 2;
break;
case 305:
$function = 'SUMX2PY2';
$args = 2;
break;
case 306:
$function = 'CHITEST';
$args = 2;
break;
case 307:
$function = 'CORREL';
$args = 2;
break;
case 308:
$function = 'COVAR';
$args = 2;
break;
case 309:
$function = 'FORECAST';
$args = 3;
break;
case 310:
$function = 'FTEST';
$args = 2;
break;
case 311:
$function = 'INTERCEPT';
$args = 2;
break;
case 312:
$function = 'PEARSON';
$args = 2;
break;
case 313:
$function = 'RSQ';
$args = 2;
break;
case 314:
$function = 'STEYX';
$args = 2;
break;
case 315:
$function = 'SLOPE';
$args = 2;
break;
case 316:
$function = 'TTEST';
$args = 4;
break;
case 325:
$function = 'LARGE';
$args = 2;
break;
case 326:
$function = 'SMALL';
$args = 2;
break;
case 327:
$function = 'QUARTILE';
$args = 2;
break;
case 328:
$function = 'PERCENTILE';
$args = 2;
break;
case 331:
$function = 'TRIMMEAN';
$args = 2;
break;
case 332:
$function = 'TINV';
$args = 2;
break;
case 337:
$function = 'POWER';
$args = 2;
break;
case 342:
$function = 'RADIANS';
$args = 1;
break;
case 343:
$function = 'DEGREES';
$args = 1;
break;
case 346:
$function = 'COUNTIF';
$args = 2;
break;
case 347:
$function = 'COUNTBLANK';
$args = 1;
break;
case 350:
$function = 'ISPMT';
$args = 4;
break;
case 351:
$function = 'DATEDIF';
$args = 3;
break;
case 352:
$function = 'DATESTRING';
$args = 1;
break;
case 353:
$function = 'NUMBERSTRING';
$args = 2;
break;
case 360:
$function = 'PHONETIC';
$args = 1;
break;
case 368:
$function = 'BAHTTEXT';
$args = 1;
break;
default:
throw new Exception('Unrecognized function in formula');
}
$data = ['function' => $function, 'args' => $args];
break;
case 0x22: // function with variable number of arguments
case 0x42:
case 0x62:
$name = 'tFuncV';
$size = 4;
// offset: 1; size: 1; number of arguments
$args = ord($formulaData[1]);
// offset: 2: size: 2; index to built-in sheet function
$index = self::getUInt2d($formulaData, 2);
switch ($index) {
case 0:
$function = 'COUNT';
break;
case 1:
$function = 'IF';
break;
case 4:
$function = 'SUM';
break;
case 5:
$function = 'AVERAGE';
break;
case 6:
$function = 'MIN';
break;
case 7:
$function = 'MAX';
break;
case 8:
$function = 'ROW';
break;
case 9:
$function = 'COLUMN';
break;
case 11:
$function = 'NPV';
break;
case 12:
$function = 'STDEV';
break;
case 13:
$function = 'DOLLAR';
break;
case 14:
$function = 'FIXED';
break;
case 28:
$function = 'LOOKUP';
break;
case 29:
$function = 'INDEX';
break;
case 36:
$function = 'AND';
break;
case 37:
$function = 'OR';
break;
case 46:
$function = 'VAR';
break;
case 49:
$function = 'LINEST';
break;
case 50:
$function = 'TREND';
break;
case 51:
$function = 'LOGEST';
break;
case 52:
$function = 'GROWTH';
break;
case 56:
$function = 'PV';
break;
case 57:
$function = 'FV';
break;
case 58:
$function = 'NPER';
break;
case 59:
$function = 'PMT';
break;
case 60:
$function = 'RATE';
break;
case 62:
$function = 'IRR';
break;
case 64:
$function = 'MATCH';
break;
case 70:
$function = 'WEEKDAY';
break;
case 78:
$function = 'OFFSET';
break;
case 82:
$function = 'SEARCH';
break;
case 100:
$function = 'CHOOSE';
break;
case 101:
$function = 'HLOOKUP';
break;
case 102:
$function = 'VLOOKUP';
break;
case 109:
$function = 'LOG';
break;
case 115:
$function = 'LEFT';
break;
case 116:
$function = 'RIGHT';
break;
case 120:
$function = 'SUBSTITUTE';
break;
case 124:
$function = 'FIND';
break;
case 125:
$function = 'CELL';
break;
case 144:
$function = 'DDB';
break;
case 148:
$function = 'INDIRECT';
break;
case 167:
$function = 'IPMT';
break;
case 168:
$function = 'PPMT';
break;
case 169:
$function = 'COUNTA';
break;
case 183:
$function = 'PRODUCT';
break;
case 193:
$function = 'STDEVP';
break;
case 194:
$function = 'VARP';
break;
case 197:
$function = 'TRUNC';
break;
case 204:
$function = 'USDOLLAR';
break;
case 205:
$function = 'FINDB';
break;
case 206:
$function = 'SEARCHB';
break;
case 208:
$function = 'LEFTB';
break;
case 209:
$function = 'RIGHTB';
break;
case 216:
$function = 'RANK';
break;
case 219:
$function = 'ADDRESS';
break;
case 220:
$function = 'DAYS360';
break;
case 222:
$function = 'VDB';
break;
case 227:
$function = 'MEDIAN';
break;
case 228:
$function = 'SUMPRODUCT';
break;
case 247:
$function = 'DB';
break;
case 255:
$function = '';
break;
case 269:
$function = 'AVEDEV';
break;
case 270:
$function = 'BETADIST';
break;
case 272:
$function = 'BETAINV';
break;
case 317:
$function = 'PROB';
break;
case 318:
$function = 'DEVSQ';
break;
case 319:
$function = 'GEOMEAN';
break;
case 320:
$function = 'HARMEAN';
break;
case 321:
$function = 'SUMSQ';
break;
case 322:
$function = 'KURT';
break;
case 323:
$function = 'SKEW';
break;
case 324:
$function = 'ZTEST';
break;
case 329:
$function = 'PERCENTRANK';
break;
case 330:
$function = 'MODE';
break;
case 336:
$function = 'CONCATENATE';
break;
case 344:
$function = 'SUBTOTAL';
break;
case 345:
$function = 'SUMIF';
break;
case 354:
$function = 'ROMAN';
break;
case 358:
$function = 'GETPIVOTDATA';
break;
case 359:
$function = 'HYPERLINK';
break;
case 361:
$function = 'AVERAGEA';
break;
case 362:
$function = 'MAXA';
break;
case 363:
$function = 'MINA';
break;
case 364:
$function = 'STDEVPA';
break;
case 365:
$function = 'VARPA';
break;
case 366:
$function = 'STDEVA';
break;
case 367:
$function = 'VARA';
break;
default:
throw new Exception('Unrecognized function in formula');
}
$data = ['function' => $function, 'args' => $args];
break;
case 0x23: // index to defined name
case 0x43:
case 0x63:
$name = 'tName';
$size = 5;
// offset: 1; size: 2; one-based index to definedname record
$definedNameIndex = self::getUInt2d($formulaData, 1) - 1;
// offset: 2; size: 2; not used
$data = $this->definedname[$definedNameIndex]['name'] ?? '';
break;
case 0x24: // single cell reference e.g. A5
case 0x44:
case 0x64:
$name = 'tRef';
$size = 5;
$data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4));
break;
case 0x25: // cell range reference to cells in the same sheet (2d)
case 0x45:
case 0x65:
$name = 'tArea';
$size = 9;
$data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8));
break;
case 0x26: // Constant reference sub-expression
case 0x46:
case 0x66:
$name = 'tMemArea';
// offset: 1; size: 4; not used
// offset: 5; size: 2; size of the following subexpression
$subSize = self::getUInt2d($formulaData, 5);
$size = 7 + $subSize;
$data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
break;
case 0x27: // Deleted constant reference sub-expression
case 0x47:
case 0x67:
$name = 'tMemErr';
// offset: 1; size: 4; not used
// offset: 5; size: 2; size of the following subexpression
$subSize = self::getUInt2d($formulaData, 5);
$size = 7 + $subSize;
$data = $this->getFormulaFromData(substr($formulaData, 7, $subSize));
break;
case 0x29: // Variable reference sub-expression
case 0x49:
case 0x69:
$name = 'tMemFunc';
// offset: 1; size: 2; size of the following sub-expression
$subSize = self::getUInt2d($formulaData, 1);
$size = 3 + $subSize;
$data = $this->getFormulaFromData(substr($formulaData, 3, $subSize));
break;
case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places
case 0x4C:
case 0x6C:
$name = 'tRefN';
$size = 5;
$data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell);
break;
case 0x2D: // Relative 2d range reference
case 0x4D:
case 0x6D:
$name = 'tAreaN';
$size = 9;
$data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell);
break;
case 0x39: // External name
case 0x59:
case 0x79:
$name = 'tNameX';
$size = 7;
// offset: 1; size: 2; index to REF entry in EXTERNSHEET record
// offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record
$index = self::getUInt2d($formulaData, 3);
// assume index is to EXTERNNAME record
$data = $this->externalNames[$index - 1]['name'] ?? '';
// offset: 5; size: 2; not used
break;
case 0x3A: // 3d reference to cell
case 0x5A:
case 0x7A:
$name = 'tRef3d';
$size = 7;
try {
// offset: 1; size: 2; index to REF entry
$sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
// offset: 3; size: 4; cell address
$cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4));
$data = "$sheetRange!$cellAddress";
} catch (PhpSpreadsheetException $e) {
// deleted sheet reference
$data = '#REF!';
}
break;
case 0x3B: // 3d reference to cell range
case 0x5B:
case 0x7B:
$name = 'tArea3d';
$size = 11;
try {
// offset: 1; size: 2; index to REF entry
$sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1));
// offset: 3; size: 8; cell address
$cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8));
$data = "$sheetRange!$cellRangeAddress";
} catch (PhpSpreadsheetException $e) {
// deleted sheet reference
$data = '#REF!';
}
break;
// Unknown cases // don't know how to deal with
default:
throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula');
}
return [
'id' => $id,
'name' => $name,
'size' => $size,
'data' => $data,
];
}
/**
* Reads a cell address in BIFF8 e.g. 'A2' or '$A$2'
* section 3.3.4.
*
* @param string $cellAddressStructure
*
* @return string
*/
private function readBIFF8CellAddress($cellAddressStructure)
{
// offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
$row = self::getUInt2d($cellAddressStructure, 0) + 1;
// offset: 2; size: 2; index to column or column offset + relative flags
// bit: 7-0; mask 0x00FF; column index
$column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1);
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
$column = '$' . $column;
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
$row = '$' . $row;
}
return $column . $row;
}
/**
* Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column
* to indicate offsets from a base cell
* section 3.3.4.
*
* @param string $cellAddressStructure
* @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas
*
* @return string
*/
private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1')
{
[$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell);
$baseCol = Coordinate::columnIndexFromString($baseCol) - 1;
$baseRow = (int) $baseRow;
// offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767))
$rowIndex = self::getUInt2d($cellAddressStructure, 0);
$row = self::getUInt2d($cellAddressStructure, 0) + 1;
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) {
// offset: 2; size: 2; index to column or column offset + relative flags
// bit: 7-0; mask 0x00FF; column index
$colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2);
$column = Coordinate::stringFromColumnIndex($colIndex + 1);
$column = '$' . $column;
} else {
// offset: 2; size: 2; index to column or column offset + relative flags
// bit: 7-0; mask 0x00FF; column index
$relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2);
$colIndex = $baseCol + $relativeColIndex;
$colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256;
$colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256;
$column = Coordinate::stringFromColumnIndex($colIndex + 1);
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) {
$row = '$' . $row;
} else {
$rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536;
$row = $baseRow + $rowIndex;
}
return $column . $row;
}
/**
* Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1'
* always fixed range
* section 2.5.14.
*
* @param string $subData
*
* @return string
*/
private function readBIFF5CellRangeAddressFixed($subData)
{
// offset: 0; size: 2; index to first row
$fr = self::getUInt2d($subData, 0) + 1;
// offset: 2; size: 2; index to last row
$lr = self::getUInt2d($subData, 2) + 1;
// offset: 4; size: 1; index to first column
$fc = ord($subData[4]);
// offset: 5; size: 1; index to last column
$lc = ord($subData[5]);
// check values
if ($fr > $lr || $fc > $lc) {
throw new Exception('Not a cell range address');
}
// column index to letter
$fc = Coordinate::stringFromColumnIndex($fc + 1);
$lc = Coordinate::stringFromColumnIndex($lc + 1);
if ($fr == $lr && $fc == $lc) {
return "$fc$fr";
}
return "$fc$fr:$lc$lr";
}
/**
* Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1'
* always fixed range
* section 2.5.14.
*
* @param string $subData
*
* @return string
*/
private function readBIFF8CellRangeAddressFixed($subData)
{
// offset: 0; size: 2; index to first row
$fr = self::getUInt2d($subData, 0) + 1;
// offset: 2; size: 2; index to last row
$lr = self::getUInt2d($subData, 2) + 1;
// offset: 4; size: 2; index to first column
$fc = self::getUInt2d($subData, 4);
// offset: 6; size: 2; index to last column
$lc = self::getUInt2d($subData, 6);
// check values
if ($fr > $lr || $fc > $lc) {
throw new Exception('Not a cell range address');
}
// column index to letter
$fc = Coordinate::stringFromColumnIndex($fc + 1);
$lc = Coordinate::stringFromColumnIndex($lc + 1);
if ($fr == $lr && $fc == $lc) {
return "$fc$fr";
}
return "$fc$fr:$lc$lr";
}
/**
* Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6'
* there are flags indicating whether column/row index is relative
* section 3.3.4.
*
* @param string $subData
*
* @return string
*/
private function readBIFF8CellRangeAddress($subData)
{
// todo: if cell range is just a single cell, should this funciton
// not just return e.g. 'A1' and not 'A1:A1' ?
// offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767))
$fr = self::getUInt2d($subData, 0) + 1;
// offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767))
$lr = self::getUInt2d($subData, 2) + 1;
// offset: 4; size: 2; index to first column or column offset + relative flags
// bit: 7-0; mask 0x00FF; column index
$fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1);
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($subData, 4))) {
$fc = '$' . $fc;
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($subData, 4))) {
$fr = '$' . $fr;
}
// offset: 6; size: 2; index to last column or column offset + relative flags
// bit: 7-0; mask 0x00FF; column index
$lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1);
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($subData, 6))) {
$lc = '$' . $lc;
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($subData, 6))) {
$lr = '$' . $lr;
}
return "$fc$fr:$lc$lr";
}
/**
* Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column
* to indicate offsets from a base cell
* section 3.3.4.
*
* @param string $subData
* @param string $baseCell Base cell
*
* @return string Cell range address
*/
private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1')
{
[$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell);
$baseCol = $baseCol - 1;
// TODO: if cell range is just a single cell, should this funciton
// not just return e.g. 'A1' and not 'A1:A1' ?
// offset: 0; size: 2; first row
$frIndex = self::getUInt2d($subData, 0); // adjust below
// offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767)
$lrIndex = self::getUInt2d($subData, 2); // adjust below
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($subData, 4))) {
// absolute column index
// offset: 4; size: 2; first column with relative/absolute flags
// bit: 7-0; mask 0x00FF; column index
$fcIndex = 0x00FF & self::getUInt2d($subData, 4);
$fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
$fc = '$' . $fc;
} else {
// column offset
// offset: 4; size: 2; first column with relative/absolute flags
// bit: 7-0; mask 0x00FF; column index
$relativeFcIndex = 0x00FF & self::getInt2d($subData, 4);
$fcIndex = $baseCol + $relativeFcIndex;
$fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256;
$fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256;
$fc = Coordinate::stringFromColumnIndex($fcIndex + 1);
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($subData, 4))) {
// absolute row index
$fr = $frIndex + 1;
$fr = '$' . $fr;
} else {
// row offset
$frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536;
$fr = $baseRow + $frIndex;
}
// bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index)
if (!(0x4000 & self::getUInt2d($subData, 6))) {
// absolute column index
// offset: 6; size: 2; last column with relative/absolute flags
// bit: 7-0; mask 0x00FF; column index
$lcIndex = 0x00FF & self::getUInt2d($subData, 6);
$lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
$lc = '$' . $lc;
} else {
// column offset
// offset: 4; size: 2; first column with relative/absolute flags
// bit: 7-0; mask 0x00FF; column index
$relativeLcIndex = 0x00FF & self::getInt2d($subData, 4);
$lcIndex = $baseCol + $relativeLcIndex;
$lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256;
$lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256;
$lc = Coordinate::stringFromColumnIndex($lcIndex + 1);
}
// bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index)
if (!(0x8000 & self::getUInt2d($subData, 6))) {
// absolute row index
$lr = $lrIndex + 1;
$lr = '$' . $lr;
} else {
// row offset
$lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536;
$lr = $baseRow + $lrIndex;
}
return "$fc$fr:$lc$lr";
}
/**
* Read BIFF8 cell range address list
* section 2.5.15.
*
* @param string $subData
*
* @return array
*/
private function readBIFF8CellRangeAddressList($subData)
{
$cellRangeAddresses = [];
// offset: 0; size: 2; number of the following cell range addresses
$nm = self::getUInt2d($subData, 0);
$offset = 2;
// offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses
for ($i = 0; $i < $nm; ++$i) {
$cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8));
$offset += 8;
}
return [
'size' => 2 + 8 * $nm,
'cellRangeAddresses' => $cellRangeAddresses,
];
}
/**
* Read BIFF5 cell range address list
* section 2.5.15.
*
* @param string $subData
*
* @return array
*/
private function readBIFF5CellRangeAddressList($subData)
{
$cellRangeAddresses = [];
// offset: 0; size: 2; number of the following cell range addresses
$nm = self::getUInt2d($subData, 0);
$offset = 2;
// offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses
for ($i = 0; $i < $nm; ++$i) {
$cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6));
$offset += 6;
}
return [
'size' => 2 + 6 * $nm,
'cellRangeAddresses' => $cellRangeAddresses,
];
}
/**
* Get a sheet range like Sheet1:Sheet3 from REF index
* Note: If there is only one sheet in the range, one gets e.g Sheet1
* It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets,
* in which case an Exception is thrown.
*
* @param int $index
*
* @return false|string
*/
private function readSheetRangeByRefIndex($index)
{
if (isset($this->ref[$index])) {
$type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type'];
switch ($type) {
case 'internal':
// check if we have a deleted 3d reference
if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) {
throw new Exception('Deleted sheet reference');
}
// we have normal sheet range (collapsed or uncollapsed)
$firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name'];
$lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name'];
if ($firstSheetName == $lastSheetName) {
// collapsed sheet range
$sheetRange = $firstSheetName;
} else {
$sheetRange = "$firstSheetName:$lastSheetName";
}
// escape the single-quotes
$sheetRange = str_replace("'", "''", $sheetRange);
// if there are special characters, we need to enclose the range in single-quotes
// todo: check if we have identified the whole set of special characters
// it seems that the following characters are not accepted for sheet names
// and we may assume that they are not present: []*/:\?
if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) {
$sheetRange = "'$sheetRange'";
}
return $sheetRange;
default:
// TODO: external sheet support
throw new Exception('Xls reader only supports internal sheets in formulas');
}
}
return false;
}
/**
* read BIFF8 constant value array from array data
* returns e.g. ['value' => '{1,2;3,4}', 'size' => 40]
* section 2.5.8.
*
* @param string $arrayData
*
* @return array
*/
private static function readBIFF8ConstantArray($arrayData)
{
// offset: 0; size: 1; number of columns decreased by 1
$nc = ord($arrayData[0]);
// offset: 1; size: 2; number of rows decreased by 1
$nr = self::getUInt2d($arrayData, 1);
$size = 3; // initialize
$arrayData = substr($arrayData, 3);
// offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values
$matrixChunks = [];
for ($r = 1; $r <= $nr + 1; ++$r) {
$items = [];
for ($c = 1; $c <= $nc + 1; ++$c) {
$constant = self::readBIFF8Constant($arrayData);
$items[] = $constant['value'];
$arrayData = substr($arrayData, $constant['size']);
$size += $constant['size'];
}
$matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"'
}
$matrix = '{' . implode(';', $matrixChunks) . '}';
return [
'value' => $matrix,
'size' => $size,
];
}
/**
* read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value'
* section 2.5.7
* returns e.g. ['value' => '5', 'size' => 9].
*
* @param string $valueData
*
* @return array
*/
private static function readBIFF8Constant($valueData)
{
// offset: 0; size: 1; identifier for type of constant
$identifier = ord($valueData[0]);
switch ($identifier) {
case 0x00: // empty constant (what is this?)
$value = '';
$size = 9;
break;
case 0x01: // number
// offset: 1; size: 8; IEEE 754 floating-point value
$value = self::extractNumber(substr($valueData, 1, 8));
$size = 9;
break;
case 0x02: // string value
// offset: 1; size: var; Unicode string, 16-bit string length
$string = self::readUnicodeStringLong(substr($valueData, 1));
$value = '"' . $string['value'] . '"';
$size = 1 + $string['size'];
break;
case 0x04: // boolean
// offset: 1; size: 1; 0 = FALSE, 1 = TRUE
if (ord($valueData[1])) {
$value = 'TRUE';
} else {
$value = 'FALSE';
}
$size = 9;
break;
case 0x10: // error code
// offset: 1; size: 1; error code
$value = Xls\ErrorCode::lookup(ord($valueData[1]));
$size = 9;
break;
default:
throw new PhpSpreadsheetException('Unsupported BIFF8 constant');
}
return [
'value' => $value,
'size' => $size,
];
}
/**
* Extract RGB color
* OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4.
*
* @param string $rgb Encoded RGB value (4 bytes)
*
* @return array
*/
private static function readRGB($rgb)
{
// offset: 0; size 1; Red component
$r = ord($rgb[0]);
// offset: 1; size: 1; Green component
$g = ord($rgb[1]);
// offset: 2; size: 1; Blue component
$b = ord($rgb[2]);
// HEX notation, e.g. 'FF00FC'
$rgb = sprintf('%02X%02X%02X', $r, $g, $b);
return ['rgb' => $rgb];
}
/**
* Read byte string (8-bit string length)
* OpenOffice documentation: 2.5.2.
*
* @param string $subData
*
* @return array
*/
private function readByteStringShort($subData)
{
// offset: 0; size: 1; length of the string (character count)
$ln = ord($subData[0]);
// offset: 1: size: var; character array (8-bit characters)
$value = $this->decodeCodepage(substr($subData, 1, $ln));
return [
'value' => $value,
'size' => 1 + $ln, // size in bytes of data structure
];
}
/**
* Read byte string (16-bit string length)
* OpenOffice documentation: 2.5.2.
*
* @param string $subData
*
* @return array
*/
private function readByteStringLong($subData)
{
// offset: 0; size: 2; length of the string (character count)
$ln = self::getUInt2d($subData, 0);
// offset: 2: size: var; character array (8-bit characters)
$value = $this->decodeCodepage(substr($subData, 2));
//return $string;
return [
'value' => $value,
'size' => 2 + $ln, // size in bytes of data structure
];
}
/**
* Extracts an Excel Unicode short string (8-bit string length)
* OpenOffice documentation: 2.5.3
* function will automatically find out where the Unicode string ends.
*
* @param string $subData
*
* @return array
*/
private static function readUnicodeStringShort($subData)
{
$value = '';
// offset: 0: size: 1; length of the string (character count)
$characterCount = ord($subData[0]);
$string = self::readUnicodeString(substr($subData, 1), $characterCount);
// add 1 for the string length
++$string['size'];
return $string;
}
/**
* Extracts an Excel Unicode long string (16-bit string length)
* OpenOffice documentation: 2.5.3
* this function is under construction, needs to support rich text, and Asian phonetic settings.
*
* @param string $subData
*
* @return array
*/
private static function readUnicodeStringLong($subData)
{
$value = '';
// offset: 0: size: 2; length of the string (character count)
$characterCount = self::getUInt2d($subData, 0);
$string = self::readUnicodeString(substr($subData, 2), $characterCount);
// add 2 for the string length
$string['size'] += 2;
return $string;
}
/**
* Read Unicode string with no string length field, but with known character count
* this function is under construction, needs to support rich text, and Asian phonetic settings
* OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3.
*
* @param string $subData
* @param int $characterCount
*
* @return array
*/
private static function readUnicodeString($subData, $characterCount)
{
$value = '';
// offset: 0: size: 1; option flags
// bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit)
$isCompressed = !((0x01 & ord($subData[0])) >> 0);
// bit: 2; mask: 0x04; Asian phonetic settings
$hasAsian = (0x04) & ord($subData[0]) >> 2;
// bit: 3; mask: 0x08; Rich-Text settings
$hasRichText = (0x08) & ord($subData[0]) >> 3;
// offset: 1: size: var; character array
// this offset assumes richtext and Asian phonetic settings are off which is generally wrong
// needs to be fixed
$value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed);
return [
'value' => $value,
'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags
];
}
/**
* Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas.
* Example: hello"world --> "hello""world".
*
* @param string $value UTF-8 encoded string
*
* @return string
*/
private static function UTF8toExcelDoubleQuoted($value)
{
return '"' . str_replace('"', '""', $value) . '"';
}
/**
* Reads first 8 bytes of a string and return IEEE 754 float.
*
* @param string $data Binary string that is at least 8 bytes long
*
* @return float
*/
private static function extractNumber($data)
{
$rknumhigh = self::getInt4d($data, 4);
$rknumlow = self::getInt4d($data, 0);
$sign = ($rknumhigh & (int) 0x80000000) >> 31;
$exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023;
$mantissa = (0x100000 | ($rknumhigh & 0x000fffff));
$mantissalow1 = ($rknumlow & (int) 0x80000000) >> 31;
$mantissalow2 = ($rknumlow & 0x7fffffff);
$value = $mantissa / 2 ** (20 - $exp);
if ($mantissalow1 != 0) {
$value += 1 / 2 ** (21 - $exp);
}
$value += $mantissalow2 / 2 ** (52 - $exp);
if ($sign) {
$value *= -1;
}
return $value;
}
/**
* @param int $rknum
*
* @return float
*/
private static function getIEEE754($rknum)
{
if (($rknum & 0x02) != 0) {
$value = $rknum >> 2;
} else {
// changes by mmp, info on IEEE754 encoding from
// research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html
// The RK format calls for using only the most significant 30 bits
// of the 64 bit floating point value. The other 34 bits are assumed
// to be 0 so we use the upper 30 bits of $rknum as follows...
$sign = ($rknum & (int) 0x80000000) >> 31;
$exp = ($rknum & 0x7ff00000) >> 20;
$mantissa = (0x100000 | ($rknum & 0x000ffffc));
$value = $mantissa / 2 ** (20 - ($exp - 1023));
if ($sign) {
$value = -1 * $value;
}
//end of changes by mmp
}
if (($rknum & 0x01) != 0) {
$value /= 100;
}
return $value;
}
/**
* Get UTF-8 string from (compressed or uncompressed) UTF-16 string.
*
* @param string $string
* @param bool $compressed
*
* @return string
*/
private static function encodeUTF16($string, $compressed = false)
{
if ($compressed) {
$string = self::uncompressByteString($string);
}
return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE');
}
/**
* Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8.
*
* @param string $string
*
* @return string
*/
private static function uncompressByteString($string)
{
$uncompressedString = '';
$strLen = strlen($string);
for ($i = 0; $i < $strLen; ++$i) {
$uncompressedString .= $string[$i] . "\0";
}
return $uncompressedString;
}
/**
* Convert string to UTF-8. Only used for BIFF5.
*
* @param string $string
*
* @return string
*/
private function decodeCodepage($string)
{
return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage);
}
/**
* Read 16-bit unsigned integer.
*
* @param string $data
* @param int $pos
*
* @return int
*/
public static function getUInt2d($data, $pos)
{
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8);
}
/**
* Read 16-bit signed integer.
*
* @param string $data
* @param int $pos
*
* @return int
*/
public static function getInt2d($data, $pos)
{
return unpack('s', $data[$pos] . $data[$pos + 1])[1]; // @phpstan-ignore-line
}
/**
* Read 32-bit signed integer.
*
* @param string $data
* @param int $pos
*
* @return int
*/
public static function getInt4d($data, $pos)
{
// FIX: represent numbers correctly on 64-bit system
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
// Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
$_or_24 = ord($data[$pos + 3]);
if ($_or_24 >= 128) {
// negative number
$_ord_24 = -abs((256 - $_or_24) << 24);
} else {
$_ord_24 = ($_or_24 & 127) << 24;
}
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
}
private function parseRichText(string $is): RichText
{
$value = new RichText();
$value->createText($is);
return $value;
}
/**
* Phpstan 1.4.4 complains that this property is never read.
* So, we might be able to get rid of it altogether.
* For now, however, this function makes it readable,
* which satisfies Phpstan.
*
* @codeCoverageIgnore
*/
public function getMapCellStyleXfIndex(): array
{
return $this->mapCellStyleXfIndex;
}
private function readCFHeader(): array
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return [];
}
// offset: 0; size: 2; Rule Count
// $ruleCount = self::getUInt2d($recordData, 0);
// offset: var; size: var; cell range address list with
$cellRangeAddressList = ($this->version == self::XLS_BIFF8)
? $this->readBIFF8CellRangeAddressList(substr($recordData, 12))
: $this->readBIFF5CellRangeAddressList(substr($recordData, 12));
$cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses'];
return $cellRangeAddresses;
}
private function readCFRule(array $cellRangeAddresses): void
{
$length = self::getUInt2d($this->data, $this->pos + 2);
$recordData = $this->readRecordData($this->data, $this->pos + 4, $length);
// move stream pointer forward to next record
$this->pos += 4 + $length;
if ($this->readDataOnly) {
return;
}
// offset: 0; size: 2; Options
$cfRule = self::getUInt2d($recordData, 0);
// bit: 8-15; mask: 0x00FF; type
$type = (0x00FF & $cfRule) >> 0;
$type = ConditionalFormatting::type($type);
// bit: 0-7; mask: 0xFF00; type
$operator = (0xFF00 & $cfRule) >> 8;
$operator = ConditionalFormatting::operator($operator);
if ($type === null || $operator === null) {
return;
}
// offset: 2; size: 2; Size1
$size1 = self::getUInt2d($recordData, 2);
// offset: 4; size: 2; Size2
$size2 = self::getUInt2d($recordData, 4);
// offset: 6; size: 4; Options
$options = self::getInt4d($recordData, 6);
$style = new Style(false, true); // non-supervisor, conditional
$this->getCFStyleOptions($options, $style);
$hasFontRecord = (bool) ((0x04000000 & $options) >> 26);
$hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27);
$hasBorderRecord = (bool) ((0x10000000 & $options) >> 28);
$hasFillRecord = (bool) ((0x20000000 & $options) >> 29);
$hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30);
$offset = 12;
if ($hasFontRecord === true) {
$fontStyle = substr($recordData, $offset, 118);
$this->getCFFontStyle($fontStyle, $style);
$offset += 118;
}
if ($hasAlignmentRecord === true) {
$alignmentStyle = substr($recordData, $offset, 8);
$this->getCFAlignmentStyle($alignmentStyle, $style);
$offset += 8;
}
if ($hasBorderRecord === true) {
$borderStyle = substr($recordData, $offset, 8);
$this->getCFBorderStyle($borderStyle, $style);
$offset += 8;
}
if ($hasFillRecord === true) {
$fillStyle = substr($recordData, $offset, 4);
$this->getCFFillStyle($fillStyle, $style);
$offset += 4;
}
if ($hasProtectionRecord === true) {
$protectionStyle = substr($recordData, $offset, 4);
$this->getCFProtectionStyle($protectionStyle, $style);
$offset += 2;
}
$formula1 = $formula2 = null;
if ($size1 > 0) {
$formula1 = $this->readCFFormula($recordData, $offset, $size1);
if ($formula1 === null) {
return;
}
$offset += $size1;
}
if ($size2 > 0) {
$formula2 = $this->readCFFormula($recordData, $offset, $size2);
if ($formula2 === null) {
return;
}
$offset += $size2;
}
$this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style);
}
private function getCFStyleOptions(int $options, Style $style): void
{
}
private function getCFFontStyle(string $options, Style $style): void
{
$fontSize = self::getInt4d($options, 64);
if ($fontSize !== -1) {
$style->getFont()->setSize($fontSize / 20); // Convert twips to points
}
$bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold
$style->getFont()->setBold($bold);
$color = self::getInt4d($options, 80);
if ($color !== -1) {
$style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']);
}
}
private function getCFAlignmentStyle(string $options, Style $style): void
{
}
private function getCFBorderStyle(string $options, Style $style): void
{
}
private function getCFFillStyle(string $options, Style $style): void
{
$fillPattern = self::getUInt2d($options, 0);
// bit: 10-15; mask: 0xFC00; type
$fillPattern = (0xFC00 & $fillPattern) >> 10;
$fillPattern = FillPattern::lookup($fillPattern);
$fillPattern = $fillPattern === Fill::FILL_NONE ? Fill::FILL_SOLID : $fillPattern;
if ($fillPattern !== Fill::FILL_NONE) {
$style->getFill()->setFillType($fillPattern);
$fillColors = self::getUInt2d($options, 2);
// bit: 0-6; mask: 0x007F; type
$color1 = (0x007F & $fillColors) >> 0;
$style->getFill()->getStartColor()->setRGB(Xls\Color::map($color1, $this->palette, $this->version)['rgb']);
// bit: 7-13; mask: 0x3F80; type
$color2 = (0x3F80 & $fillColors) >> 7;
$style->getFill()->getEndColor()->setRGB(Xls\Color::map($color2, $this->palette, $this->version)['rgb']);
}
}
private function getCFProtectionStyle(string $options, Style $style): void
{
}
/**
* @return null|float|int|string
*/
private function readCFFormula(string $recordData, int $offset, int $size)
{
try {
$formula = substr($recordData, $offset, $size);
$formula = pack('v', $size) . $formula; // prepend the length
$formula = $this->getFormulaFromStructure($formula);
if (is_numeric($formula)) {
return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula;
}
return $formula;
} catch (PhpSpreadsheetException $e) {
}
return null;
}
/**
* @param null|float|int|string $formula1
* @param null|float|int|string $formula2
*/
private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void
{
foreach ($cellRanges as $cellRange) {
$conditional = new Conditional();
$conditional->setConditionType($type);
$conditional->setOperatorType($operator);
if ($formula1 !== null) {
$conditional->addCondition($formula1);
}
if ($formula2 !== null) {
$conditional->addCondition($formula2);
}
$conditional->setStyle($style);
$conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles();
$conditionalStyles[] = $conditional;
$this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php 0000644 00000045412 15243417764 0015762 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Csv extends BaseReader
{
const DEFAULT_FALLBACK_ENCODING = 'CP1252';
const GUESS_ENCODING = 'guess';
const UTF8_BOM = "\xEF\xBB\xBF";
const UTF8_BOM_LEN = 3;
const UTF16BE_BOM = "\xfe\xff";
const UTF16BE_BOM_LEN = 2;
const UTF16BE_LF = "\x00\x0a";
const UTF16LE_BOM = "\xff\xfe";
const UTF16LE_BOM_LEN = 2;
const UTF16LE_LF = "\x0a\x00";
const UTF32BE_BOM = "\x00\x00\xfe\xff";
const UTF32BE_BOM_LEN = 4;
const UTF32BE_LF = "\x00\x00\x00\x0a";
const UTF32LE_BOM = "\xff\xfe\x00\x00";
const UTF32LE_BOM_LEN = 4;
const UTF32LE_LF = "\x0a\x00\x00\x00";
/**
* Input encoding.
*
* @var string
*/
private $inputEncoding = 'UTF-8';
/**
* Fallback encoding if guess strikes out.
*
* @var string
*/
private $fallbackEncoding = self::DEFAULT_FALLBACK_ENCODING;
/**
* Delimiter.
*
* @var ?string
*/
private $delimiter;
/**
* Enclosure.
*
* @var string
*/
private $enclosure = '"';
/**
* Sheet index to read.
*
* @var int
*/
private $sheetIndex = 0;
/**
* Load rows contiguously.
*
* @var bool
*/
private $contiguous = false;
/**
* The character that can escape the enclosure.
*
* @var string
*/
private $escapeCharacter = '\\';
/**
* Callback for setting defaults in construction.
*
* @var ?callable
*/
private static $constructorCallback;
/**
* Attempt autodetect line endings (deprecated after PHP8.1)?
*
* @var bool
*/
private $testAutodetect = true;
/**
* @var bool
*/
protected $castFormattedNumberToNumeric = false;
/**
* @var bool
*/
protected $preserveNumericFormatting = false;
/** @var bool */
private $preserveNullString = false;
/**
* Create a new CSV Reader instance.
*/
public function __construct()
{
parent::__construct();
$callback = self::$constructorCallback;
if ($callback !== null) {
$callback($this);
}
}
/**
* Set a callback to change the defaults.
*
* The callback must accept the Csv Reader object as the first parameter,
* and it should return void.
*/
public static function setConstructorCallback(?callable $callback): void
{
self::$constructorCallback = $callback;
}
public static function getConstructorCallback(): ?callable
{
return self::$constructorCallback;
}
public function setInputEncoding(string $encoding): self
{
$this->inputEncoding = $encoding;
return $this;
}
public function getInputEncoding(): string
{
return $this->inputEncoding;
}
public function setFallbackEncoding(string $fallbackEncoding): self
{
$this->fallbackEncoding = $fallbackEncoding;
return $this;
}
public function getFallbackEncoding(): string
{
return $this->fallbackEncoding;
}
/**
* Move filepointer past any BOM marker.
*/
protected function skipBOM(): void
{
rewind($this->fileHandle);
if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) {
rewind($this->fileHandle);
}
}
/**
* Identify any separator that is explicitly set in the file.
*/
protected function checkSeparator(): void
{
$line = fgets($this->fileHandle);
if ($line === false) {
return;
}
if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
$this->delimiter = substr($line, 4, 1);
return;
}
$this->skipBOM();
}
/**
* Infer the separator if it isn't explicitly set in the file or specified by the user.
*/
protected function inferSeparator(): void
{
if ($this->delimiter !== null) {
return;
}
$inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure);
// If number of lines is 0, nothing to infer : fall back to the default
if ($inferenceEngine->linesCounted() === 0) {
$this->delimiter = $inferenceEngine->getDefaultDelimiter();
$this->skipBOM();
return;
}
$this->delimiter = $inferenceEngine->infer();
// If no delimiter could be detected, fall back to the default
if ($this->delimiter === null) {
$this->delimiter = $inferenceEngine->getDefaultDelimiter();
}
$this->skipBOM();
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*/
public function listWorksheetInfo(string $filename): array
{
// Open file
$this->openFileOrMemory($filename);
$fileHandle = $this->fileHandle;
// Skip BOM, if any
$this->skipBOM();
$this->checkSeparator();
$this->inferSeparator();
$worksheetInfo = [];
$worksheetInfo[0]['worksheetName'] = 'Worksheet';
$worksheetInfo[0]['lastColumnLetter'] = 'A';
$worksheetInfo[0]['lastColumnIndex'] = 0;
$worksheetInfo[0]['totalRows'] = 0;
$worksheetInfo[0]['totalColumns'] = 0;
// Loop through each line of the file in turn
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
while (is_array($rowData)) {
++$worksheetInfo[0]['totalRows'];
$worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
}
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
$worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
// Close file
fclose($fileHandle);
return $worksheetInfo;
}
/**
* Loads Spreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
/**
* Loads Spreadsheet from string.
*/
public function loadSpreadsheetFromString(string $contents): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true);
}
private function openFileOrMemory(string $filename): void
{
// Open file
$fhandle = $this->canRead($filename);
if (!$fhandle) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
}
if ($this->inputEncoding === self::GUESS_ENCODING) {
$this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
}
$this->openFile($filename);
if ($this->inputEncoding !== 'UTF-8') {
fclose($this->fileHandle);
$entireFile = file_get_contents($filename);
$fileHandle = fopen('php://memory', 'r+b');
if ($fileHandle !== false && $entireFile !== false) {
$this->fileHandle = $fileHandle;
$data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
fwrite($this->fileHandle, $data);
$this->skipBOM();
}
}
}
public function setTestAutoDetect(bool $value): self
{
$this->testAutodetect = $value;
return $this;
}
private function setAutoDetect(?string $value): ?string
{
$retVal = null;
if ($value !== null && $this->testAutodetect) {
$retVal2 = @ini_set('auto_detect_line_endings', $value);
if (is_string($retVal2)) {
$retVal = $retVal2;
}
}
return $retVal;
}
public function castFormattedNumberToNumeric(
bool $castFormattedNumberToNumeric,
bool $preserveNumericFormatting = false
): void {
$this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric;
$this->preserveNumericFormatting = $preserveNumericFormatting;
}
/**
* Open data uri for reading.
*/
private function openDataUri(string $filename): void
{
$fileHandle = fopen($filename, 'rb');
if ($fileHandle === false) {
// @codeCoverageIgnoreStart
throw new ReaderException('Could not open file ' . $filename . ' for reading.');
// @codeCoverageIgnoreEnd
}
$this->fileHandle = $fileHandle;
}
/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*/
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
{
return $this->loadStringOrFile($filename, $spreadsheet, false);
}
/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*/
private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
{
// Deprecated in Php8.1
$iniset = $this->setAutoDetect('1');
// Open file
if ($dataUri) {
$this->openDataUri($filename);
} else {
$this->openFileOrMemory($filename);
}
$fileHandle = $this->fileHandle;
// Skip BOM, if any
$this->skipBOM();
$this->checkSeparator();
$this->inferSeparator();
// Create new PhpSpreadsheet object
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
// Set our starting row based on whether we're in contiguous mode or not
$currentRow = 1;
$outRow = 0;
// Loop through each line of the file in turn
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
$valueBinder = Cell::getValueBinder();
$preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion();
while (is_array($rowData)) {
$noOutputYet = true;
$columnLetter = 'A';
foreach ($rowData as $rowDatum) {
$this->convertBoolean($rowDatum, $preserveBooleanString);
$numberFormatMask = $this->convertFormattedNumber($rowDatum);
if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) {
if ($this->contiguous) {
if ($noOutputYet) {
$noOutputYet = false;
++$outRow;
}
} else {
$outRow = $currentRow;
}
// Set basic styling for the value (Note that this could be overloaded by styling in a value binder)
$sheet->getCell($columnLetter . $outRow)->getStyle()
->getNumberFormat()
->setFormatCode($numberFormatMask);
// Set cell value
$sheet->getCell($columnLetter . $outRow)->setValue($rowDatum);
}
++$columnLetter;
}
$rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
++$currentRow;
}
// Close file
fclose($fileHandle);
$this->setAutoDetect($iniset);
// Return
return $spreadsheet;
}
/**
* Convert string true/false to boolean, and null to null-string.
*
* @param mixed $rowDatum
*/
private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void
{
if (is_string($rowDatum) && !$preserveBooleanString) {
if (strcasecmp(Calculation::getTRUE(), $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) {
$rowDatum = true;
} elseif (strcasecmp(Calculation::getFALSE(), $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) {
$rowDatum = false;
}
} else {
$rowDatum = $rowDatum ?? '';
}
}
/**
* Convert numeric strings to int or float values.
*
* @param mixed $rowDatum
*/
private function convertFormattedNumber(&$rowDatum): string
{
$numberFormatMask = NumberFormat::FORMAT_GENERAL;
if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) {
$numeric = str_replace(
[StringHelper::getThousandsSeparator(), StringHelper::getDecimalSeparator()],
['', '.'],
$rowDatum
);
if (is_numeric($numeric)) {
$decimalPos = strpos($rowDatum, StringHelper::getDecimalSeparator());
if ($this->preserveNumericFormatting === true) {
$numberFormatMask = (strpos($rowDatum, StringHelper::getThousandsSeparator()) !== false)
? '#,##0' : '0';
if ($decimalPos !== false) {
$decimals = strlen($rowDatum) - $decimalPos - 1;
$numberFormatMask .= '.' . str_repeat('0', min($decimals, 6));
}
}
$rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric;
}
}
return $numberFormatMask;
}
public function getDelimiter(): ?string
{
return $this->delimiter;
}
public function setDelimiter(?string $delimiter): self
{
$this->delimiter = $delimiter;
return $this;
}
public function getEnclosure(): string
{
return $this->enclosure;
}
public function setEnclosure(string $enclosure): self
{
if ($enclosure == '') {
$enclosure = '"';
}
$this->enclosure = $enclosure;
return $this;
}
public function getSheetIndex(): int
{
return $this->sheetIndex;
}
public function setSheetIndex(int $indexValue): self
{
$this->sheetIndex = $indexValue;
return $this;
}
public function setContiguous(bool $contiguous): self
{
$this->contiguous = $contiguous;
return $this;
}
public function getContiguous(): bool
{
return $this->contiguous;
}
public function setEscapeCharacter(string $escapeCharacter): self
{
$this->escapeCharacter = $escapeCharacter;
return $this;
}
public function getEscapeCharacter(): string
{
return $this->escapeCharacter;
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
// Check if file exists
try {
$this->openFile($filename);
} catch (ReaderException $e) {
return false;
}
fclose($this->fileHandle);
// Trust file extension if any
$extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION));
if (in_array($extension, ['csv', 'tsv'])) {
return true;
}
// Attempt to guess mimetype
$type = mime_content_type($filename);
$supportedTypes = [
'application/csv',
'text/csv',
'text/plain',
'inode/x-empty',
];
return in_array($type, $supportedTypes, true);
}
private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void
{
if ($encoding === '') {
$pos = strpos($contents, $compare);
if ($pos !== false && $pos % strlen($compare) === 0) {
$encoding = $setEncoding;
}
}
}
private static function guessEncodingNoBom(string $filename): string
{
$encoding = '';
$contents = file_get_contents($filename);
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE');
self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE');
self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE');
self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE');
if ($encoding === '' && preg_match('//u', $contents) === 1) {
$encoding = 'UTF-8';
}
return $encoding;
}
private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void
{
if ($encoding === '') {
if ($compare === substr($first4, 0, strlen($compare))) {
$encoding = $setEncoding;
}
}
}
private static function guessEncodingBom(string $filename): string
{
$encoding = '';
$first4 = file_get_contents($filename, false, null, 0, 4);
if ($first4 !== false) {
self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8');
self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE');
self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE');
self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE');
self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE');
}
return $encoding;
}
public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
{
$encoding = self::guessEncodingBom($filename);
if ($encoding === '') {
$encoding = self::guessEncodingNoBom($filename);
}
return ($encoding === '') ? $dflt : $encoding;
}
public function setPreserveNullString(bool $value): self
{
$this->preserveNullString = $value;
return $this;
}
public function getPreserveNullString(): bool
{
return $this->preserveNullString;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php 0000644 00000000772 15243417764 0017755 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use DOMElement;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
abstract class BaseLoader
{
/**
* @var Spreadsheet
*/
protected $spreadsheet;
/**
* @var string
*/
protected $tableNs;
public function __construct(Spreadsheet $spreadsheet, string $tableNs)
{
$this->spreadsheet = $spreadsheet;
$this->tableNs = $tableNs;
}
abstract public function read(DOMElement $workbookData): void;
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php 0000644 00000005353 15243417764 0020276 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use DOMElement;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DefinedNames extends BaseLoader
{
public function read(DOMElement $workbookData): void
{
$this->readDefinedRanges($workbookData);
$this->readDefinedExpressions($workbookData);
}
/**
* Read any Named Ranges that are defined in this spreadsheet.
*/
protected function readDefinedRanges(DOMElement $workbookData): void
{
$namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range');
foreach ($namedRanges as $definedNameElement) {
$definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
$range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address');
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
$range = FormulaTranslator::convertToExcelAddressValue($range);
$this->addDefinedName($baseAddress, $definedName, $range);
}
}
/**
* Read any Named Formulae that are defined in this spreadsheet.
*/
protected function readDefinedExpressions(DOMElement $workbookData): void
{
$namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression');
foreach ($namedExpressions as $definedNameElement) {
$definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name');
$baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address');
$expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression');
$baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress);
$expression = substr($expression, strpos($expression, ':=') + 1);
$expression = FormulaTranslator::convertToExcelFormulaValue($expression);
$this->addDefinedName($baseAddress, $definedName, $expression);
}
}
/**
* Assess scope and store the Defined Name.
*/
private function addDefinedName(string $baseAddress, string $definedName, string $value): void
{
[$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true);
$worksheet = $this->spreadsheet->getSheetByName($sheetReference);
// Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
if ($worksheet !== null) {
$this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value));
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php 0000644 00000017623 15243417764 0020354 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use DOMDocument;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PageSettings
{
/**
* @var string
*/
private $officeNs;
/**
* @var string
*/
private $stylesNs;
/**
* @var string
*/
private $stylesFo;
/**
* @var string
*/
private $tableNs;
/**
* @var string[]
*/
private $tableStylesCrossReference = [];
/** @var array */
private $pageLayoutStyles = [];
/**
* @var string[]
*/
private $masterStylesCrossReference = [];
/**
* @var string[]
*/
private $masterPrintStylesCrossReference = [];
public function __construct(DOMDocument $styleDom)
{
$this->setDomNameSpaces($styleDom);
$this->readPageSettingStyles($styleDom);
$this->readStyleMasterLookup($styleDom);
}
private function setDomNameSpaces(DOMDocument $styleDom): void
{
$this->officeNs = $styleDom->lookupNamespaceUri('office');
$this->stylesNs = $styleDom->lookupNamespaceUri('style');
$this->stylesFo = $styleDom->lookupNamespaceUri('fo');
$this->tableNs = $styleDom->lookupNamespaceUri('table');
}
private function readPageSettingStyles(DOMDocument $styleDom): void
{
$item0 = $styleDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')->item(0);
$styles = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'page-layout');
foreach ($styles as $styleSet) {
$styleName = $styleSet->getAttributeNS($this->stylesNs, 'name');
$pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0];
$styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation');
$styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to');
$stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order');
$centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering');
$marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left');
$marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right');
$marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top');
$marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom');
$header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0];
$headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
$marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
$footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0];
$footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
$marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
$this->pageLayoutStyles[$styleName] = (object) [
'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT,
'scale' => $styleScale ?: 100,
'printOrder' => $stylePrintOrder,
'horizontalCentered' => $centered === 'horizontal' || $centered === 'both',
'verticalCentered' => $centered === 'vertical' || $centered === 'both',
// margin size is already stored in inches, so no UOM conversion is required
'marginLeft' => (float) ($marginLeft ?? 0.7),
'marginRight' => (float) ($marginRight ?? 0.7),
'marginTop' => (float) ($marginTop ?? 0.3),
'marginBottom' => (float) ($marginBottom ?? 0.3),
'marginHeader' => (float) ($marginHeader ?? 0.45),
'marginFooter' => (float) ($marginFooter ?? 0.45),
];
}
}
private function readStyleMasterLookup(DOMDocument $styleDom): void
{
$item0 = $styleDom->getElementsByTagNameNS($this->officeNs, 'master-styles')->item(0);
$styleMasterLookup = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'master-page');
foreach ($styleMasterLookup as $styleMasterSet) {
$styleMasterName = $styleMasterSet->getAttributeNS($this->stylesNs, 'name');
$pageLayoutName = $styleMasterSet->getAttributeNS($this->stylesNs, 'page-layout-name');
$this->masterPrintStylesCrossReference[$styleMasterName] = $pageLayoutName;
}
}
public function readStyleCrossReferences(DOMDocument $contentDom): void
{
$item0 = $contentDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')->item(0);
$styleXReferences = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($this->stylesNs, 'style');
foreach ($styleXReferences as $styleXreferenceSet) {
$styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name');
$stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name');
$styleFamilyName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'family');
if (!empty($styleFamilyName) && $styleFamilyName === 'table') {
$styleVisibility = 'true';
foreach ($styleXreferenceSet->getElementsByTagNameNS($this->stylesNs, 'table-properties') as $tableProperties) {
$styleVisibility = $tableProperties->getAttributeNS($this->tableNs, 'display');
}
$this->tableStylesCrossReference[$styleXRefName] = $styleVisibility;
}
if (!empty($stylePageLayoutName)) {
$this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName;
}
}
}
public function setVisibilityForWorksheet(Worksheet $worksheet, string $styleName): void
{
if (!array_key_exists($styleName, $this->tableStylesCrossReference)) {
return;
}
$worksheet->setSheetState(
$this->tableStylesCrossReference[$styleName] === 'false'
? Worksheet::SHEETSTATE_HIDDEN
: Worksheet::SHEETSTATE_VISIBLE
);
}
public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void
{
if (!array_key_exists($styleName, $this->masterStylesCrossReference)) {
return;
}
$masterStyleName = $this->masterStylesCrossReference[$styleName];
if (!array_key_exists($masterStyleName, $this->masterPrintStylesCrossReference)) {
return;
}
$printSettingsIndex = $this->masterPrintStylesCrossReference[$masterStyleName];
if (!array_key_exists($printSettingsIndex, $this->pageLayoutStyles)) {
return;
}
$printSettings = $this->pageLayoutStyles[$printSettingsIndex];
$worksheet->getPageSetup()
->setOrientation($printSettings->orientation ?? PageSetup::ORIENTATION_DEFAULT)
->setPageOrder($printSettings->printOrder === 'ltr' ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER)
->setScale((int) trim($printSettings->scale, '%'))
->setHorizontalCentered($printSettings->horizontalCentered)
->setVerticalCentered($printSettings->verticalCentered);
$worksheet->getPageMargins()
->setLeft($printSettings->marginLeft)
->setRight($printSettings->marginRight)
->setTop($printSettings->marginTop)
->setBottom($printSettings->marginBottom)
->setHeader($printSettings->marginHeader)
->setFooter($printSettings->marginFooter);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php 0000644 00000007120 15243417764 0021425 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
class FormulaTranslator
{
public static function convertToExcelAddressValue(string $openOfficeAddress): string
{
$excelAddress = $openOfficeAddress;
// Cell range 3-d reference
// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
// and assume that the second worksheet reference is the same as the first
$excelAddress = (string) preg_replace(
[
'/\$?([^\.]+)\.([^\.]+):\$?([^\.]+)\.([^\.]+)/miu',
'/\$?([^\.]+)\.([^\.]+):\.([^\.]+)/miu', // Cell range reference in another sheet
'/\$?([^\.]+)\.([^\.]+)/miu', // Cell reference in another sheet
'/\.([^\.]+):\.([^\.]+)/miu', // Cell range reference
'/\.([^\.]+)/miu', // Simple cell reference
],
[
'$1!$2:$4',
'$1!$2:$3',
'$1!$2',
'$1:$2',
'$1',
],
$excelAddress
);
return $excelAddress;
}
public static function convertToExcelFormulaValue(string $openOfficeFormula): string
{
$temp = explode(Calculation::FORMULA_STRING_QUOTE, $openOfficeFormula);
$tKey = false;
$inMatrixBracesLevel = 0;
$inFunctionBracesLevel = 0;
foreach ($temp as &$value) {
// @var string $value
// Only replace in alternate array entries (i.e. non-quoted blocks)
// so that conversion isn't done in string values
$tKey = $tKey === false;
if ($tKey) {
$value = (string) preg_replace(
[
'/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference in another sheet
'/\[\$?([^\.]+)\.([^\.]+)\]/miu', // Cell reference in another sheet
'/\[\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference
'/\[\.([^\.]+)\]/miu', // Simple cell reference
],
[
'$1!$2:$3',
'$1!$2',
'$1:$2',
'$1',
],
$value
);
// Convert references to defined names/formulae
$value = str_replace('$$', '', $value);
// Convert ODS function argument separators to Excel function argument separators
$value = Calculation::translateSeparator(';', ',', $value, $inFunctionBracesLevel);
// Convert ODS matrix separators to Excel matrix separators
$value = Calculation::translateSeparator(
';',
',',
$value,
$inMatrixBracesLevel,
Calculation::FORMULA_OPEN_MATRIX_BRACE,
Calculation::FORMULA_CLOSE_MATRIX_BRACE
);
$value = Calculation::translateSeparator(
'|',
';',
$value,
$inMatrixBracesLevel,
Calculation::FORMULA_OPEN_MATRIX_BRACE,
Calculation::FORMULA_CLOSE_MATRIX_BRACE
);
$value = (string) preg_replace('/COM\.MICROSOFT\./ui', '', $value);
}
}
// Then rebuild the formula string
$excelFormula = implode('"', $temp);
return $excelFormula;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php 0000644 00000002474 15243417764 0020033 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use DOMElement;
use DOMNode;
class AutoFilter extends BaseLoader
{
public function read(DOMElement $workbookData): void
{
$this->readAutoFilters($workbookData);
}
protected function readAutoFilters(DOMElement $workbookData): void
{
$databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges');
foreach ($databases as $autofilters) {
foreach ($autofilters->childNodes as $autofilter) {
$autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address');
if ($autofilterRange !== null) {
$baseAddress = FormulaTranslator::convertToExcelAddressValue($autofilterRange);
$this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress);
}
}
}
}
protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string
{
if ($node !== null && $node->attributes !== null) {
$attribute = $node->attributes->getNamedItemNS(
$this->tableNs,
$attributeName
);
if ($attribute !== null) {
return $attribute->nodeValue;
}
}
return null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php 0000644 00000011410 15243417764 0020077 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Ods;
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
class Properties
{
/** @var Spreadsheet */
private $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
public function load(SimpleXMLElement $xml, array $namespacesMeta): void
{
$docProps = $this->spreadsheet->getProperties();
$officeProperty = $xml->children($namespacesMeta['office']);
foreach ($officeProperty as $officePropertyData) {
if (isset($namespacesMeta['dc'])) {
/** @scrutinizer ignore-call */
$officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']);
$this->setCoreProperties($docProps, $officePropertiesDC);
}
$officePropertyMeta = null;
if (isset($namespacesMeta['dc'])) {
/** @scrutinizer ignore-call */
$officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']);
}
$officePropertyMeta = $officePropertyMeta ?? [];
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
$this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps);
}
}
}
private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElement $officePropertyDC): void
{
foreach ($officePropertyDC as $propertyName => $propertyValue) {
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'title':
$docProps->setTitle($propertyValue);
break;
case 'subject':
$docProps->setSubject($propertyValue);
break;
case 'creator':
$docProps->setCreator($propertyValue);
$docProps->setLastModifiedBy($propertyValue);
break;
case 'date':
$docProps->setModified($propertyValue);
break;
case 'description':
$docProps->setDescription($propertyValue);
break;
}
}
}
private function setMetaProperties(
array $namespacesMeta,
SimpleXMLElement $propertyValue,
string $propertyName,
DocumentProperties $docProps
): void {
$propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']);
$propertyValue = (string) $propertyValue;
switch ($propertyName) {
case 'initial-creator':
$docProps->setCreator($propertyValue);
break;
case 'keyword':
$docProps->setKeywords($propertyValue);
break;
case 'creation-date':
$docProps->setCreated($propertyValue);
break;
case 'user-defined':
$this->setUserDefinedProperty($propertyValueAttributes, $propertyValue, $docProps);
break;
}
}
/**
* @param mixed $propertyValueAttributes
* @param mixed $propertyValue
*/
private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps): void
{
$propertyValueName = '';
$propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
foreach ($propertyValueAttributes as $key => $value) {
if ($key == 'name') {
$propertyValueName = (string) $value;
} elseif ($key == 'value-type') {
switch ($value) {
case 'date':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'date');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_DATE;
break;
case 'boolean':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'bool');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
break;
case 'float':
$propertyValue = DocumentProperties::convertProperty($propertyValue, 'r4');
$propertyValueType = DocumentProperties::PROPERTY_TYPE_FLOAT;
break;
default:
$propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING;
}
}
}
$docProps->setCustomProperty($propertyValueName, $propertyValue, $propertyValueType);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php 0000644 00000010775 15243417764 0017704 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Csv;
class Delimiter
{
protected const POTENTIAL_DELIMETERS = [',', ';', "\t", '|', ':', ' ', '~'];
/** @var resource */
protected $fileHandle;
/** @var string */
protected $escapeCharacter;
/** @var string */
protected $enclosure;
/** @var array */
protected $counts = [];
/** @var int */
protected $numberLines = 0;
/** @var ?string */
protected $delimiter;
/**
* @param resource $fileHandle
*/
public function __construct($fileHandle, string $escapeCharacter, string $enclosure)
{
$this->fileHandle = $fileHandle;
$this->escapeCharacter = $escapeCharacter;
$this->enclosure = $enclosure;
$this->countPotentialDelimiters();
}
public function getDefaultDelimiter(): string
{
return self::POTENTIAL_DELIMETERS[0];
}
public function linesCounted(): int
{
return $this->numberLines;
}
protected function countPotentialDelimiters(): void
{
$this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []);
$delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS);
// Count how many times each of the potential delimiters appears in each line
$this->numberLines = 0;
while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) {
$this->countDelimiterValues($line, $delimiterKeys);
}
}
protected function countDelimiterValues(string $line, array $delimiterKeys): void
{
$splitString = str_split($line, 1);
if (is_array($splitString)) {
$distribution = array_count_values($splitString);
$countLine = array_intersect_key($distribution, $delimiterKeys);
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
$this->counts[$delimiter][] = $countLine[$delimiter] ?? 0;
}
}
}
public function infer(): ?string
{
// Calculate the mean square deviations for each delimiter
// (ignoring delimiters that haven't been found consistently)
$meanSquareDeviations = [];
$middleIdx = floor(($this->numberLines - 1) / 2);
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
$series = $this->counts[$delimiter];
sort($series);
$median = ($this->numberLines % 2)
? $series[$middleIdx]
: ($series[$middleIdx] + $series[$middleIdx + 1]) / 2;
if ($median === 0) {
continue;
}
$meanSquareDeviations[$delimiter] = array_reduce(
$series,
function ($sum, $value) use ($median) {
return $sum + ($value - $median) ** 2;
}
) / count($series);
}
// ... and pick the delimiter with the smallest mean square deviation
// (in case of ties, the order in potentialDelimiters is respected)
$min = INF;
foreach (self::POTENTIAL_DELIMETERS as $delimiter) {
if (!isset($meanSquareDeviations[$delimiter])) {
continue;
}
if ($meanSquareDeviations[$delimiter] < $min) {
$min = $meanSquareDeviations[$delimiter];
$this->delimiter = $delimiter;
}
}
return $this->delimiter;
}
/**
* Get the next full line from the file.
*
* @return false|string
*/
public function getNextLine()
{
$line = '';
$enclosure = ($this->escapeCharacter === '' ? ''
: ('(?<!' . preg_quote($this->escapeCharacter, '/') . ')'))
. preg_quote($this->enclosure, '/');
do {
// Get the next line in the file
$newLine = fgets($this->fileHandle);
// Return false if there is no next line
if ($newLine === false) {
return false;
}
// Add the new line to the line passed in
$line = $line . $newLine;
// Drop everything that is enclosed to avoid counting false positives in enclosures
$line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line);
// See if we have any enclosures left in the line
// if we still have an enclosure then we need to read the next line as well
} while (preg_match('/(' . $enclosure . ')/', $line) > 0);
return ($line !== '') ? $line : false;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php 0000644 00000120041 15243417764 0016123 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMText;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Throwable;
class Html extends BaseReader
{
/**
* Sample size to read to determine if it's HTML or not.
*/
const TEST_SAMPLE_SIZE = 2048;
/**
* Input encoding.
*
* @var string
*/
protected $inputEncoding = 'ANSI';
/**
* Sheet index to read.
*
* @var int
*/
protected $sheetIndex = 0;
/**
* Formats.
*
* @var array
*/
protected $formats = [
'h1' => [
'font' => [
'bold' => true,
'size' => 24,
],
], // Bold, 24pt
'h2' => [
'font' => [
'bold' => true,
'size' => 18,
],
], // Bold, 18pt
'h3' => [
'font' => [
'bold' => true,
'size' => 13.5,
],
], // Bold, 13.5pt
'h4' => [
'font' => [
'bold' => true,
'size' => 12,
],
], // Bold, 12pt
'h5' => [
'font' => [
'bold' => true,
'size' => 10,
],
], // Bold, 10pt
'h6' => [
'font' => [
'bold' => true,
'size' => 7.5,
],
], // Bold, 7.5pt
'a' => [
'font' => [
'underline' => true,
'color' => [
'argb' => Color::COLOR_BLUE,
],
],
], // Blue underlined
'hr' => [
'borders' => [
'bottom' => [
'borderStyle' => Border::BORDER_THIN,
'color' => [
Color::COLOR_BLACK,
],
],
],
], // Bottom border
'strong' => [
'font' => [
'bold' => true,
],
], // Bold
'b' => [
'font' => [
'bold' => true,
],
], // Bold
'i' => [
'font' => [
'italic' => true,
],
], // Italic
'em' => [
'font' => [
'italic' => true,
],
], // Italic
];
/** @var array */
protected $rowspan = [];
/**
* Create a new HTML Reader instance.
*/
public function __construct()
{
parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this);
}
/**
* Validate that the current file is an HTML file.
*/
public function canRead(string $filename): bool
{
// Check if file exists
try {
$this->openFile($filename);
} catch (Exception $e) {
return false;
}
$beginning = $this->readBeginning();
$startWithTag = self::startsWithTag($beginning);
$containsTags = self::containsTags($beginning);
$endsWithTag = self::endsWithTag($this->readEnding());
fclose($this->fileHandle);
return $startWithTag && $containsTags && $endsWithTag;
}
private function readBeginning(): string
{
fseek($this->fileHandle, 0);
return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
}
private function readEnding(): string
{
$meta = stream_get_meta_data($this->fileHandle);
$filename = $meta['uri'];
$size = (int) filesize($filename);
if ($size === 0) {
return '';
}
$blockSize = self::TEST_SAMPLE_SIZE;
if ($size < $blockSize) {
$blockSize = $size;
}
fseek($this->fileHandle, $size - $blockSize);
return (string) fread($this->fileHandle, $blockSize);
}
private static function startsWithTag(string $data): bool
{
return '<' === substr(trim($data), 0, 1);
}
private static function endsWithTag(string $data): bool
{
return '>' === substr(trim($data), -1, 1);
}
private static function containsTags(string $data): bool
{
return strlen($data) !== strlen(strip_tags($data));
}
/**
* Loads Spreadsheet from file.
*/
public function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
/**
* Set input encoding.
*
* @param string $inputEncoding Input encoding, eg: 'ANSI'
*
* @return $this
*
* @codeCoverageIgnore
*
* @deprecated no use is made of this property
*/
public function setInputEncoding($inputEncoding)
{
$this->inputEncoding = $inputEncoding;
return $this;
}
/**
* Get input encoding.
*
* @return string
*
* @codeCoverageIgnore
*
* @deprecated no use is made of this property
*/
public function getInputEncoding()
{
return $this->inputEncoding;
}
// Data Array used for testing only, should write to Spreadsheet object on completion of tests
/** @var array */
protected $dataArray = [];
/** @var int */
protected $tableLevel = 0;
/** @var array */
protected $nestedColumn = ['A'];
protected function setTableStartColumn(string $column): string
{
if ($this->tableLevel == 0) {
$column = 'A';
}
++$this->tableLevel;
$this->nestedColumn[$this->tableLevel] = $column;
return $this->nestedColumn[$this->tableLevel];
}
protected function getTableStartColumn(): string
{
return $this->nestedColumn[$this->tableLevel];
}
protected function releaseTableStartColumn(): string
{
--$this->tableLevel;
return array_pop($this->nestedColumn);
}
/**
* Flush cell.
*
* @param string $column
* @param int|string $row
* @param mixed $cellContent
*/
protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent, array $attributeArray): void
{
if (is_string($cellContent)) {
// Simple String content
if (trim($cellContent) > '') {
// Only actually write it if there's content in the string
// Write to worksheet to be done here...
// ... we return the cell, so we can mess about with styles more easily
// Set cell value explicitly if there is data-type attribute
if (isset($attributeArray['data-type'])) {
$datatype = $attributeArray['data-type'];
if (in_array($datatype, [DataType::TYPE_STRING, DataType::TYPE_STRING2, DataType::TYPE_INLINE])) {
//Prevent to Excel treat string with beginning equal sign or convert big numbers to scientific number
if (substr($cellContent, 0, 1) === '=') {
$sheet->getCell($column . $row)
->getStyle()
->setQuotePrefix(true);
}
}
//catching the Exception and ignoring the invalid data types
try {
$sheet->setCellValueExplicit($column . $row, $cellContent, $attributeArray['data-type']);
} catch (\PhpOffice\PhpSpreadsheet\Exception $exception) {
$sheet->setCellValue($column . $row, $cellContent);
}
} else {
$sheet->setCellValue($column . $row, $cellContent);
}
$this->dataArray[$row][$column] = $cellContent;
}
} else {
// We have a Rich Text run
// TODO
$this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent;
}
$cellContent = (string) '';
}
private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
{
$attributeArray = [];
foreach ($child->attributes as $attribute) {
$attributeArray[$attribute->name] = $attribute->value;
}
if ($child->nodeName === 'body') {
$row = 1;
$column = 'A';
$cellContent = '';
$this->tableLevel = 0;
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
$this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'title') {
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
$sheet->setTitle($cellContent, true, true);
$cellContent = '';
} else {
$this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) {
if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
$sheet->getComment($column . $row)
->getText()
->createTextRun($child->textContent);
} else {
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
}
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
}
} else {
$this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'hr') {
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
}
++$row;
}
// fall through to br
$this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'br' || $child->nodeName === 'hr') {
if ($this->tableLevel > 0) {
// If we're inside a table, replace with a \n and set the cell to wrap
$cellContent .= "\n";
$sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
} else {
// Otherwise flush our existing content and move the row cursor on
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
}
} else {
$this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'a') {
foreach ($attributeArray as $attributeName => $attributeValue) {
switch ($attributeName) {
case 'href':
$sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue);
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
}
break;
case 'class':
if ($attributeValue === 'comment-indicator') {
break; // Ignore - it's just a red square.
}
}
}
// no idea why this should be needed
//$cellContent .= ' ';
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
$this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if (in_array((string) $child->nodeName, self::H1_ETC, true)) {
if ($this->tableLevel > 0) {
// If we're inside a table, replace with a \n
$cellContent .= $cellContent ? "\n" : '';
$sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
if ($cellContent > '') {
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
}
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
}
++$row;
$column = 'A';
}
} else {
$this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'li') {
if ($this->tableLevel > 0) {
// If we're inside a table, replace with a \n
$cellContent .= $cellContent ? "\n" : '';
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
if ($cellContent > '') {
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
}
++$row;
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$column = 'A';
}
} else {
$this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'img') {
$this->insertImage($sheet, $column, $row, $attributeArray);
} else {
$this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private string $currentColumn = 'A';
private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'table') {
$this->currentColumn = 'A';
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$column = $this->setTableStartColumn($column);
if ($this->tableLevel > 1 && $row > 1) {
--$row;
}
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
$column = $this->releaseTableStartColumn();
if ($this->tableLevel > 1) {
++$column;
} else {
++$row;
}
} else {
$this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'col') {
$this->applyInlineStyle($sheet, -1, $this->currentColumn, $attributeArray);
++$this->currentColumn;
} elseif ($child->nodeName === 'tr') {
$column = $this->getTableStartColumn();
$cellContent = '';
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
if (isset($attributeArray['height'])) {
$sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
}
++$row;
} else {
$this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName !== 'td' && $child->nodeName !== 'th') {
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
$this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray);
}
}
private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void
{
if (isset($attributeArray['bgcolor'])) {
$sheet->getStyle("$column$row")->applyFromArray(
[
'fill' => [
'fillType' => Fill::FILL_SOLID,
'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])],
],
]
);
}
}
private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void
{
if (isset($attributeArray['width'])) {
$sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width());
}
}
private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void
{
if (isset($attributeArray['height'])) {
$sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height());
}
}
private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
{
if (isset($attributeArray['align'])) {
$sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
}
}
private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
{
if (isset($attributeArray['valign'])) {
$sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
}
}
private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void
{
if (isset($attributeArray['data-format'])) {
$sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
}
}
private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
while (isset($this->rowspan[$column . $row])) {
++$column;
}
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
// apply inline style
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
$this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$this->processDomElementBgcolor($sheet, $row, $column, $attributeArray);
$this->processDomElementWidth($sheet, $column, $attributeArray);
$this->processDomElementHeight($sheet, $row, $attributeArray);
$this->processDomElementAlign($sheet, $row, $column, $attributeArray);
$this->processDomElementVAlign($sheet, $row, $column, $attributeArray);
$this->processDomElementDataFormat($sheet, $row, $column, $attributeArray);
if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
//create merging rowspan and colspan
$columnTo = $column;
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
$this->rowspan[$value] = true;
}
$sheet->mergeCells($range);
$column = $columnTo;
} elseif (isset($attributeArray['rowspan'])) {
//create merging rowspan
$range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
$this->rowspan[$value] = true;
}
$sheet->mergeCells($range);
} elseif (isset($attributeArray['colspan'])) {
//create merging colspan
$columnTo = $column;
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$sheet->mergeCells($column . $row . ':' . $columnTo . $row);
$column = $columnTo;
}
++$column;
}
protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void
{
foreach ($element->childNodes as $child) {
if ($child instanceof DOMText) {
$domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? ''));
if (is_string($cellContent)) {
// simply append the text if the cell content is a plain text string
$cellContent .= $domText;
}
// but if we have a rich text run instead, we need to append it correctly
// TODO
} elseif ($child instanceof DOMElement) {
$this->processDomElementBody($sheet, $row, $column, $cellContent, $child);
}
}
}
/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*
* @param string $filename
*
* @return Spreadsheet
*/
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
{
// Validate
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an Invalid HTML file.');
}
// Create a new DOM object
$dom = new DOMDocument();
// Reload the HTML file into the DOM object
try {
$convert = $this->getSecurityScannerOrThrow()->scanFile($filename);
$lowend = "\u{80}";
$highend = "\u{10ffff}";
$regexp = "/[$lowend-$highend]/u";
/** @var callable */
$callback = [self::class, 'replaceNonAscii'];
$convert = preg_replace_callback($regexp, $callback, $convert);
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) {
$loaded = false;
}
if ($loaded === false) {
throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
}
self::loadProperties($dom, $spreadsheet);
return $this->loadDocument($dom, $spreadsheet);
}
private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void
{
$properties = $spreadsheet->getProperties();
foreach ($dom->getElementsByTagName('meta') as $meta) {
$metaContent = (string) $meta->getAttribute('content');
if ($metaContent !== '') {
$metaName = (string) $meta->getAttribute('name');
switch ($metaName) {
case 'author':
$properties->setCreator($metaContent);
break;
case 'category':
$properties->setCategory($metaContent);
break;
case 'company':
$properties->setCompany($metaContent);
break;
case 'created':
$properties->setCreated($metaContent);
break;
case 'description':
$properties->setDescription($metaContent);
break;
case 'keywords':
$properties->setKeywords($metaContent);
break;
case 'lastModifiedBy':
$properties->setLastModifiedBy($metaContent);
break;
case 'manager':
$properties->setManager($metaContent);
break;
case 'modified':
$properties->setModified($metaContent);
break;
case 'subject':
$properties->setSubject($metaContent);
break;
case 'title':
$properties->setTitle($metaContent);
break;
default:
if (preg_match('/^custom[.](bool|date|float|int|string)[.](.+)$/', $metaName, $matches) === 1) {
switch ($matches[1]) {
case 'bool':
$properties->setCustomProperty($matches[2], (bool) $metaContent, Properties::PROPERTY_TYPE_BOOLEAN);
break;
case 'float':
$properties->setCustomProperty($matches[2], (float) $metaContent, Properties::PROPERTY_TYPE_FLOAT);
break;
case 'int':
$properties->setCustomProperty($matches[2], (int) $metaContent, Properties::PROPERTY_TYPE_INTEGER);
break;
case 'date':
$properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_DATE);
break;
default: // string
$properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_STRING);
}
}
}
}
}
if (!empty($dom->baseURI)) {
$properties->setHyperlinkBase($dom->baseURI);
}
}
private static function replaceNonAscii(array $matches): string
{
return '&#' . mb_ord($matches[0], 'UTF-8') . ';';
}
/**
* Spreadsheet from content.
*
* @param string $content
*/
public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet
{
// Create a new DOM object
$dom = new DOMDocument();
// Reload the HTML file into the DOM object
try {
$convert = $this->getSecurityScannerOrThrow()->scan($content);
$lowend = "\u{80}";
$highend = "\u{10ffff}";
$regexp = "/[$lowend-$highend]/u";
/** @var callable */
$callback = [self::class, 'replaceNonAscii'];
$convert = preg_replace_callback($regexp, $callback, $convert);
$loaded = ($convert === null) ? false : $dom->loadHTML($convert);
} catch (Throwable $e) {
$loaded = false;
}
if ($loaded === false) {
throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
}
$spreadsheet = $spreadsheet ?? new Spreadsheet();
self::loadProperties($dom, $spreadsheet);
return $this->loadDocument($dom, $spreadsheet);
}
/**
* Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
*/
private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
{
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
// Discard white space
$document->preserveWhiteSpace = false;
$row = 0;
$column = 'A';
$content = '';
$this->rowspan = [];
$this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
// Return
return $spreadsheet;
}
/**
* Get sheet index.
*
* @return int
*/
public function getSheetIndex()
{
return $this->sheetIndex;
}
/**
* Set sheet index.
*
* @param int $sheetIndex Sheet index
*
* @return $this
*/
public function setSheetIndex($sheetIndex)
{
$this->sheetIndex = $sheetIndex;
return $this;
}
/**
* Apply inline css inline style.
*
* NOTES :
* Currently only intended for td & th element,
* and only takes 'background-color' and 'color'; property with HEX color
*
* TODO :
* - Implement to other propertie, such as border
*
* @param int $row
* @param string $column
* @param array $attributeArray
*/
private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void
{
if (!isset($attributeArray['style'])) {
return;
}
if ($row <= 0 || $column === '') {
$cellStyle = new Style();
} elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
$columnTo = $column;
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
$cellStyle = $sheet->getStyle($range);
} elseif (isset($attributeArray['rowspan'])) {
$range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
$cellStyle = $sheet->getStyle($range);
} elseif (isset($attributeArray['colspan'])) {
$columnTo = $column;
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
}
$range = $column . $row . ':' . $columnTo . $row;
$cellStyle = $sheet->getStyle($range);
} else {
$cellStyle = $sheet->getStyle($column . $row);
}
// add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
$styles = explode(';', $attributeArray['style']);
foreach ($styles as $st) {
$value = explode(':', $st);
$styleName = isset($value[0]) ? trim($value[0]) : null;
$styleValue = isset($value[1]) ? trim($value[1]) : null;
$styleValueString = (string) $styleValue;
if (!$styleName) {
continue;
}
switch ($styleName) {
case 'background':
case 'background-color':
$styleColor = $this->getStyleColor($styleValueString);
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
break;
case 'color':
$styleColor = $this->getStyleColor($styleValueString);
if (!$styleColor) {
continue 2;
}
$cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
break;
case 'border':
$this->setBorderStyle($cellStyle, $styleValueString, 'allBorders');
break;
case 'border-top':
$this->setBorderStyle($cellStyle, $styleValueString, 'top');
break;
case 'border-bottom':
$this->setBorderStyle($cellStyle, $styleValueString, 'bottom');
break;
case 'border-left':
$this->setBorderStyle($cellStyle, $styleValueString, 'left');
break;
case 'border-right':
$this->setBorderStyle($cellStyle, $styleValueString, 'right');
break;
case 'font-size':
$cellStyle->getFont()->setSize(
(float) $styleValue
);
break;
case 'font-weight':
if ($styleValue === 'bold' || $styleValue >= 500) {
$cellStyle->getFont()->setBold(true);
}
break;
case 'font-style':
if ($styleValue === 'italic') {
$cellStyle->getFont()->setItalic(true);
}
break;
case 'font-family':
$cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString));
break;
case 'text-decoration':
switch ($styleValue) {
case 'underline':
$cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
break;
case 'line-through':
$cellStyle->getFont()->setStrikethrough(true);
break;
}
break;
case 'text-align':
$cellStyle->getAlignment()->setHorizontal($styleValueString);
break;
case 'vertical-align':
$cellStyle->getAlignment()->setVertical($styleValueString);
break;
case 'width':
if ($column !== '') {
$sheet->getColumnDimension($column)->setWidth(
(new CssDimension($styleValue ?? ''))->width()
);
}
break;
case 'height':
if ($row > 0) {
$sheet->getRowDimension($row)->setRowHeight(
(new CssDimension($styleValue ?? ''))->height()
);
}
break;
case 'word-wrap':
$cellStyle->getAlignment()->setWrapText(
$styleValue === 'break-word'
);
break;
case 'text-indent':
$cellStyle->getAlignment()->setIndent(
(int) str_replace(['px'], '', $styleValueString)
);
break;
}
}
}
/**
* Check if has #, so we can get clean hex.
*
* @param mixed $value
*
* @return null|string
*/
public function getStyleColor($value)
{
$value = (string) $value;
if (strpos($value, '#') === 0) {
return substr($value, 1);
}
return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value);
}
/**
* @param string $column
* @param int $row
*/
private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void
{
if (!isset($attributes['src'])) {
return;
}
$src = urldecode($attributes['src']);
$width = isset($attributes['width']) ? (float) $attributes['width'] : null;
$height = isset($attributes['height']) ? (float) $attributes['height'] : null;
$name = $attributes['alt'] ?? null;
$drawing = new Drawing();
$drawing->setPath($src);
$drawing->setWorksheet($sheet);
$drawing->setCoordinates($column . $row);
$drawing->setOffsetX(0);
$drawing->setOffsetY(10);
$drawing->setResizeProportional(true);
if ($name) {
$drawing->setName($name);
}
if ($width) {
$drawing->setWidth((int) $width);
}
if ($height) {
$drawing->setHeight((int) $height);
}
$sheet->getColumnDimension($column)->setWidth(
$drawing->getWidth() / 6
);
$sheet->getRowDimension($row)->setRowHeight(
$drawing->getHeight() * 0.9
);
}
private const BORDER_MAPPINGS = [
'dash-dot' => Border::BORDER_DASHDOT,
'dash-dot-dot' => Border::BORDER_DASHDOTDOT,
'dashed' => Border::BORDER_DASHED,
'dotted' => Border::BORDER_DOTTED,
'double' => Border::BORDER_DOUBLE,
'hair' => Border::BORDER_HAIR,
'medium' => Border::BORDER_MEDIUM,
'medium-dashed' => Border::BORDER_MEDIUMDASHED,
'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT,
'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT,
'none' => Border::BORDER_NONE,
'slant-dash-dot' => Border::BORDER_SLANTDASHDOT,
'solid' => Border::BORDER_THIN,
'thick' => Border::BORDER_THICK,
];
public static function getBorderMappings(): array
{
return self::BORDER_MAPPINGS;
}
/**
* Map html border style to PhpSpreadsheet border style.
*
* @param string $style
*
* @return null|string
*/
public function getBorderStyle($style)
{
return self::BORDER_MAPPINGS[$style] ?? null;
}
/**
* @param string $styleValue
* @param string $type
*/
private function setBorderStyle(Style $cellStyle, $styleValue, $type): void
{
if (trim($styleValue) === Border::BORDER_NONE) {
$borderStyle = Border::BORDER_NONE;
$color = null;
} else {
$borderArray = explode(' ', $styleValue);
$borderCount = count($borderArray);
if ($borderCount >= 3) {
$borderStyle = $borderArray[1];
$color = $borderArray[2];
} else {
$borderStyle = $borderArray[0];
$color = $borderArray[1] ?? null;
}
}
$cellStyle->applyFromArray([
'borders' => [
$type => [
'borderStyle' => $this->getBorderStyle($borderStyle),
'color' => ['rgb' => $this->getStyleColor($color)],
],
],
]);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php 0000644 00000403663 15243417764 0016173 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SharedFormula;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\TableReader;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\WorkbookView;
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\Drawing;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\Font;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Style;
use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
use Throwable;
use XMLReader;
use ZipArchive;
class Xlsx extends BaseReader
{
const INITIAL_FILE = '_rels/.rels';
/**
* ReferenceHelper instance.
*
* @var ReferenceHelper
*/
private $referenceHelper;
/**
* @var ZipArchive
*/
private $zip;
/** @var Styles */
private $styleReader;
/**
* @var array
*/
private $sharedFormulae = [];
/**
* Create a new Xlsx Reader instance.
*/
public function __construct()
{
parent::__construct();
$this->referenceHelper = ReferenceHelper::getInstance();
$this->securityScanner = XmlScanner::getInstance($this);
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
if (!File::testFileNoThrow($filename, self::INITIAL_FILE)) {
return false;
}
$result = false;
$this->zip = $zip = new ZipArchive();
if ($zip->open($filename) === true) {
[$workbookBasename] = $this->getWorkbookBaseName();
$result = !empty($workbookBasename);
$zip->close();
}
return $result;
}
/**
* @param mixed $value
*/
public static function testSimpleXml($value): SimpleXMLElement
{
return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
}
public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement
{
return self::testSimpleXml($value === null ? $value : $value->attributes($ns));
}
// Phpstan thinks, correctly, that xpath can return false.
// Scrutinizer thinks it can't.
// Sigh.
private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array
{
return self::falseToArray($sxml->xpath($path));
}
/**
* @param mixed $value
*/
public static function falseToArray($value): array
{
return is_array($value) ? $value : [];
}
private function loadZip(string $filename, string $ns = '', bool $replaceUnclosedBr = false): SimpleXMLElement
{
$contents = $this->getFromZipArchive($this->zip, $filename);
if ($replaceUnclosedBr) {
$contents = str_replace('<br>', '<br/>', $contents);
}
$rels = @simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($contents),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions(),
$ns
);
return self::testSimpleXml($rels);
}
// This function is just to identify cases where I'm not sure
// why empty namespace is required.
private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement
{
$contents = $this->getFromZipArchive($this->zip, $filename);
$rels = simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($contents),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions(),
($ns === '' ? $ns : '')
);
return self::testSimpleXml($rels);
}
private const REL_TO_MAIN = [
Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN,
Namespaces::THUMBNAIL => '',
];
private const REL_TO_DRAWING = [
Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING,
];
private const REL_TO_CHART = [
Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART,
];
/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
* @param string $filename
*
* @return array
*/
public function listWorksheetNames($filename)
{
File::assertFile($filename, self::INITIAL_FILE);
$worksheetNames = [];
$this->zip = $zip = new ZipArchive();
$zip->open($filename);
// The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
$rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
foreach ($rels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relType = (string) $rel['Type'];
$mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
if ($mainNS !== '') {
$xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS);
if ($xmlWorkbook->sheets) {
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
// Check if sheet should be skipped
$worksheetNames[] = (string) self::getAttributes($eleSheet)['name'];
}
}
}
}
$zip->close();
return $worksheetNames;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
File::assertFile($filename, self::INITIAL_FILE);
$worksheetInfo = [];
$this->zip = $zip = new ZipArchive();
$zip->open($filename);
$rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
foreach ($rels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relType = (string) $rel['Type'];
$mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
if ($mainNS !== '') {
$relTarget = (string) $rel['Target'];
$dir = dirname($relTarget);
$namespace = dirname($relType);
$relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
$worksheets = [];
foreach ($relsWorkbook->Relationship as $elex) {
$ele = self::getAttributes($elex);
if (
((string) $ele['Type'] === "$namespace/worksheet") ||
((string) $ele['Type'] === "$namespace/chartsheet")
) {
$worksheets[(string) $ele['Id']] = $ele['Target'];
}
}
$xmlWorkbook = $this->loadZip($relTarget, $mainNS);
if ($xmlWorkbook->sheets) {
$dir = dirname($relTarget);
/** @var SimpleXMLElement $eleSheet */
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
$tmpInfo = [
'worksheetName' => (string) self::getAttributes($eleSheet)['name'],
'lastColumnLetter' => 'A',
'lastColumnIndex' => 0,
'totalRows' => 0,
'totalColumns' => 0,
];
$fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')];
$fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet";
$xml = new XMLReader();
$xml->xml(
$this->getSecurityScannerOrThrow()->scan(
$this->getFromZipArchive($this->zip, $fileWorksheetPath)
),
null,
Settings::getLibXmlLoaderOptions()
);
$xml->setParserProperty(2, true);
$currCells = 0;
while ($xml->read()) {
if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {
$row = $xml->getAttribute('r');
$tmpInfo['totalRows'] = $row;
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
$currCells = 0;
} elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {
$cell = $xml->getAttribute('r');
$currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1);
}
}
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
$xml->close();
$tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
$worksheetInfo[] = $tmpInfo;
}
}
}
}
$zip->close();
return $worksheetInfo;
}
private static function castToBoolean(SimpleXMLElement $c): bool
{
$value = isset($c->v) ? (string) $c->v : null;
if ($value == '0') {
return false;
} elseif ($value == '1') {
return true;
}
return (bool) $c->v;
}
private static function castToError(?SimpleXMLElement $c): ?string
{
return isset($c, $c->v) ? (string) $c->v : null;
}
private static function castToString(?SimpleXMLElement $c): ?string
{
return isset($c, $c->v) ? (string) $c->v : null;
}
/**
* @param mixed $value
* @param mixed $calculatedValue
*/
private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void
{
if ($c === null) {
return;
}
$attr = $c->f->attributes();
$cellDataType = DataType::TYPE_FORMULA;
$value = "={$c->f}";
$calculatedValue = self::$castBaseType($c);
// Shared formula?
if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') {
$instance = (string) $attr['si'];
if (!isset($this->sharedFormulae[(string) $attr['si']])) {
$this->sharedFormulae[$instance] = new SharedFormula($r, $value);
} elseif ($updateSharedCells === true) {
// It's only worth the overhead of adjusting the shared formula for this cell if we're actually loading
// the cell, which may not be the case if we're using a read filter.
$master = Coordinate::indexesFromString($this->sharedFormulae[$instance]->master());
$current = Coordinate::indexesFromString($r);
$difference = [0, 0];
$difference[0] = $current[0] - $master[0];
$difference[1] = $current[1] - $master[1];
$value = $this->referenceHelper->updateFormulaReferences($this->sharedFormulae[$instance]->formula(), 'A1', $difference[0], $difference[1]);
}
}
}
/**
* @param string $fileName
*/
private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool
{
// Root-relative paths
if (strpos($fileName, '//') !== false) {
$fileName = substr($fileName, strpos($fileName, '//') + 1);
}
$fileName = File::realpath($fileName);
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
// so we need to load case-insensitively from the zip file
// Apache POI fixes
$contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE);
if ($contents === false) {
$contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE);
}
return $contents !== false;
}
/**
* @param string $fileName
*
* @return string
*/
private function getFromZipArchive(ZipArchive $archive, $fileName = '')
{
// Root-relative paths
if (strpos($fileName, '//') !== false) {
$fileName = substr($fileName, strpos($fileName, '//') + 1);
}
// Relative paths generated by dirname($filename) when $filename
// has no path (i.e.files in root of the zip archive)
$fileName = (string) preg_replace('/^\.\//', '', $fileName);
$fileName = File::realpath($fileName);
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
// so we need to load case-insensitively from the zip file
$contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
// Apache POI fixes
if ($contents === false) {
$contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
}
// Has the file been saved with Windoze directory separators rather than unix?
if ($contents === false) {
$contents = $archive->getFromName(str_replace('/', '\\', $fileName), 0, ZipArchive::FL_NOCASE);
}
return ($contents === false) ? '' : $contents;
}
/**
* Loads Spreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
File::assertFile($filename, self::INITIAL_FILE);
// Initialisations
$excel = new Spreadsheet();
$excel->removeSheetByIndex(0);
$addingFirstCellStyleXf = true;
$addingFirstCellXf = true;
$unparsedLoadedData = [];
$this->zip = $zip = new ZipArchive();
$zip->open($filename);
// Read the theme first, because we need the colour scheme when reading the styles
[$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName();
$drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
$chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART;
$wbRels = $this->loadZip("xl/_rels/{$workbookBasename}.rels", Namespaces::RELATIONSHIPS);
$theme = null;
$this->styleReader = new Styles();
foreach ($wbRels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target'];
if (substr($relTarget, 0, 4) === '/xl/') {
$relTarget = substr($relTarget, 4);
}
switch ($rel['Type']) {
case "$xmlNamespaceBase/theme":
$themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
$themeOrderAdditional = count($themeOrderArray);
$xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS);
$xmlThemeName = self::getAttributes($xmlTheme);
$xmlTheme = $xmlTheme->children($drawingNS);
$themeName = (string) $xmlThemeName['name'];
$colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme);
$colourSchemeName = (string) $colourScheme['name'];
$excel->getTheme()->setThemeColorName($colourSchemeName);
$colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS);
$themeColours = [];
foreach ($colourScheme as $k => $xmlColour) {
$themePos = array_search($k, $themeOrderArray);
if ($themePos === false) {
$themePos = $themeOrderAdditional++;
}
if (isset($xmlColour->sysClr)) {
$xmlColourData = self::getAttributes($xmlColour->sysClr);
$themeColours[$themePos] = (string) $xmlColourData['lastClr'];
$excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']);
} elseif (isset($xmlColour->srgbClr)) {
$xmlColourData = self::getAttributes($xmlColour->srgbClr);
$themeColours[$themePos] = (string) $xmlColourData['val'];
$excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']);
}
}
$theme = new Theme($themeName, $colourSchemeName, $themeColours);
$this->styleReader->setTheme($theme);
$fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme);
$fontSchemeName = (string) $fontScheme['name'];
$excel->getTheme()->setThemeFontName($fontSchemeName);
$majorFonts = [];
$minorFonts = [];
$fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS);
$majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? '';
$majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? '';
$majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? '';
$minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? '';
$minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? '';
$minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? '';
foreach ($fontScheme->majorFont->font as $xmlFont) {
$fontAttributes = self::getAttributes($xmlFont);
$script = (string) ($fontAttributes['script'] ?? '');
if (!empty($script)) {
$majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
}
}
foreach ($fontScheme->minorFont->font as $xmlFont) {
$fontAttributes = self::getAttributes($xmlFont);
$script = (string) ($fontAttributes['script'] ?? '');
if (!empty($script)) {
$minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
}
}
$excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts);
$excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts);
break;
}
}
$rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
$propertyReader = new PropertyReader($this->getSecurityScannerOrThrow(), $excel->getProperties());
$chartDetails = [];
foreach ($rels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target'];
// issue 3553
if ($relTarget[0] === '/') {
$relTarget = substr($relTarget, 1);
}
$relType = (string) $rel['Type'];
$mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
switch ($relType) {
case Namespaces::CORE_PROPERTIES:
$propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget));
break;
case "$xmlNamespaceBase/extended-properties":
$propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget));
break;
case "$xmlNamespaceBase/custom-properties":
$propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget));
break;
//Ribbon
case Namespaces::EXTENSIBILITY:
$customUI = $relTarget;
if ($customUI) {
$this->readRibbon($excel, $customUI, $zip);
}
break;
case "$xmlNamespaceBase/officeDocument":
$dir = dirname($relTarget);
// Do not specify namespace in next stmt - do it in Xpath
$relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
$relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS);
$worksheets = [];
$macros = $customUI = null;
foreach ($relsWorkbook->Relationship as $elex) {
$ele = self::getAttributes($elex);
switch ($ele['Type']) {
case Namespaces::WORKSHEET:
case Namespaces::PURL_WORKSHEET:
$worksheets[(string) $ele['Id']] = $ele['Target'];
break;
case Namespaces::CHARTSHEET:
if ($this->includeCharts === true) {
$worksheets[(string) $ele['Id']] = $ele['Target'];
}
break;
// a vbaProject ? (: some macros)
case Namespaces::VBA:
$macros = $ele['Target'];
break;
}
}
if ($macros !== null) {
$macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin
if ($macrosCode !== false) {
$excel->setMacrosCode($macrosCode);
$excel->setHasMacros(true);
//short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
$Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
if ($Certificate !== false) {
$excel->setMacrosCertificate($Certificate);
}
}
}
$relType = "rel:Relationship[@Type='"
. "$xmlNamespaceBase/styles"
. "']";
$xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType));
if ($xpath === null) {
$xmlStyles = self::testSimpleXml(null);
} else {
$xmlStyles = $this->loadZip("$dir/$xpath[Target]", $mainNS);
}
$palette = self::extractPalette($xmlStyles);
$this->styleReader->setWorkbookPalette($palette);
$fills = self::extractStyles($xmlStyles, 'fills', 'fill');
$fonts = self::extractStyles($xmlStyles, 'fonts', 'font');
$borders = self::extractStyles($xmlStyles, 'borders', 'border');
$xfTags = self::extractStyles($xmlStyles, 'cellXfs', 'xf');
$cellXfTags = self::extractStyles($xmlStyles, 'cellStyleXfs', 'xf');
$styles = [];
$cellStyles = [];
$numFmts = null;
if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) {
$numFmts = $xmlStyles->numFmts[0];
}
if (isset($numFmts) && ($numFmts !== null)) {
$numFmts->registerXPathNamespace('sml', $mainNS);
}
$this->styleReader->setNamespace($mainNS);
if (!$this->readDataOnly/* && $xmlStyles*/) {
foreach ($xfTags as $xfTag) {
$xf = self::getAttributes($xfTag);
$numFmt = null;
if ($xf['numFmtId']) {
if (isset($numFmts)) {
$tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
if (isset($tmpNumFmt['formatCode'])) {
$numFmt = (string) $tmpNumFmt['formatCode'];
}
}
// We shouldn't override any of the built-in MS Excel values (values below id 164)
// But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used
// So we make allowance for them rather than lose formatting masks
if (
$numFmt === null &&
(int) $xf['numFmtId'] < 164 &&
NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== ''
) {
$numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
}
}
$quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
$style = (object) [
'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL,
'font' => $fonts[(int) ($xf['fontId'])],
'fill' => $fills[(int) ($xf['fillId'])],
'border' => $borders[(int) ($xf['borderId'])],
'alignment' => $xfTag->alignment,
'protection' => $xfTag->protection,
'quotePrefix' => $quotePrefix,
];
$styles[] = $style;
// add style to cellXf collection
$objStyle = new Style();
$this->styleReader->readStyle($objStyle, $style);
if ($addingFirstCellXf) {
$excel->removeCellXfByIndex(0); // remove the default style
$addingFirstCellXf = false;
}
$excel->addCellXf($objStyle);
}
foreach ($cellXfTags as $xfTag) {
$xf = self::getAttributes($xfTag);
$numFmt = NumberFormat::FORMAT_GENERAL;
if ($numFmts && $xf['numFmtId']) {
$tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
if (isset($tmpNumFmt['formatCode'])) {
$numFmt = (string) $tmpNumFmt['formatCode'];
} elseif ((int) $xf['numFmtId'] < 165) {
$numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
}
}
$quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
$cellStyle = (object) [
'numFmt' => $numFmt,
'font' => $fonts[(int) ($xf['fontId'])],
'fill' => $fills[((int) $xf['fillId'])],
'border' => $borders[(int) ($xf['borderId'])],
'alignment' => $xfTag->alignment,
'protection' => $xfTag->protection,
'quotePrefix' => $quotePrefix,
];
$cellStyles[] = $cellStyle;
// add style to cellStyleXf collection
$objStyle = new Style();
$this->styleReader->readStyle($objStyle, $cellStyle);
if ($addingFirstCellStyleXf) {
$excel->removeCellStyleXfByIndex(0); // remove the default style
$addingFirstCellStyleXf = false;
}
$excel->addCellStyleXf($objStyle);
}
}
$this->styleReader->setStyleXml($xmlStyles);
$this->styleReader->setNamespace($mainNS);
$this->styleReader->setStyleBaseData($theme, $styles, $cellStyles);
$dxfs = $this->styleReader->dxfs($this->readDataOnly);
$styles = $this->styleReader->styles();
// Read content after setting the styles
$sharedStrings = [];
$relType = "rel:Relationship[@Type='"
//. Namespaces::SHARED_STRINGS
. "$xmlNamespaceBase/sharedStrings"
. "']";
$xpath = self::getArrayItem($relsWorkbook->xpath($relType));
if ($xpath) {
$xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
if (isset($xmlStrings->si)) {
foreach ($xmlStrings->si as $val) {
if (isset($val->t)) {
$sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
} elseif (isset($val->r)) {
$sharedStrings[] = $this->parseRichText($val);
}
}
}
}
$xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS);
$xmlWorkbookNS = $this->loadZip($relTarget, $mainNS);
// Set base date
if ($xmlWorkbookNS->workbookPr) {
Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
$attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr);
if (isset($attrs1904['date1904'])) {
if (self::boolean((string) $attrs1904['date1904'])) {
Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
}
}
}
// Set protection
$this->readProtection($excel, $xmlWorkbook);
$sheetId = 0; // keep track of new sheet id in final workbook
$oldSheetId = -1; // keep track of old sheet id in final workbook
$countSkippedSheets = 0; // keep track of number of skipped sheets
$mapSheetId = []; // mapping of sheet ids from old to new
$charts = $chartDetails = [];
if ($xmlWorkbookNS->sheets) {
/** @var SimpleXMLElement $eleSheet */
foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
$eleSheetAttr = self::getAttributes($eleSheet);
++$oldSheetId;
// Check if sheet should be skipped
if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) {
++$countSkippedSheets;
$mapSheetId[$oldSheetId] = null;
continue;
}
$sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id');
if (isset($worksheets[$sheetReferenceId]) === false) {
++$countSkippedSheets;
$mapSheetId[$oldSheetId] = null;
continue;
}
// Map old sheet id in original workbook to new sheet id.
// They will differ if loadSheetsOnly() is being used
$mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
// Load sheet
$docSheet = $excel->createSheet();
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
// references in formula cells... during the load, all formulae should be correct,
// and we're simply bringing the worksheet name in line with the formula, not the
// reverse
$docSheet->setTitle((string) $eleSheetAttr['name'], false, false);
$fileWorksheet = (string) $worksheets[$sheetReferenceId];
$xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
$xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
// Shared Formula table is unique to each Worksheet, so we need to reset it here
$this->sharedFormulae = [];
if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') {
$docSheet->setSheetState((string) $eleSheetAttr['state']);
}
if ($xmlSheetNS) {
$xmlSheetMain = $xmlSheetNS->children($mainNS);
// Setting Conditional Styles adjusts selected cells, so we need to execute this
// before reading the sheet view data to get the actual selected cells
if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) {
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
}
if (!$this->readDataOnly && $xmlSheet->extLst) {
(new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader);
}
if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) {
$sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet);
$sheetViews->load();
}
$sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheetNS);
$sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader);
(new ColumnAndRowAttributes($docSheet, $xmlSheetNS))
->load($this->getReadFilter(), $this->getReadDataOnly());
}
if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) {
$cIndex = 1; // Cell Start from 1
foreach ($xmlSheetNS->sheetData->row as $row) {
$rowIndex = 1;
foreach ($row->c as $c) {
$cAttr = self::getAttributes($c);
$r = (string) $cAttr['r'];
if ($r == '') {
$r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
}
$cellDataType = (string) $cAttr['t'];
$value = null;
$calculatedValue = null;
// Read cell?
if ($this->getReadFilter() !== null) {
$coordinates = Coordinate::coordinateFromString($r);
if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
// Normally, just testing for the f attribute should identify this cell as containing a formula
// that we need to read, even though it is outside of the filter range, in case it is a shared formula.
// But in some cases, this attribute isn't set; so we need to delve a level deeper and look at
// whether or not the cell has a child formula element that is shared.
if (isset($cAttr->f) || (isset($c->f, $c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared')) {
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError', false);
}
++$rowIndex;
continue;
}
}
// Read cell!
switch ($cellDataType) {
case 's':
if ((string) $c->v != '') {
$value = $sharedStrings[(int) ($c->v)];
if ($value instanceof RichText) {
$value = clone $value;
}
} else {
$value = '';
}
break;
case 'b':
if (!isset($c->f)) {
if (isset($c->v)) {
$value = self::castToBoolean($c);
} else {
$value = null;
$cellDataType = DATATYPE::TYPE_NULL;
}
} else {
// Formula
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean');
if (isset($c->f['t'])) {
$att = $c->f;
$docSheet->getCell($r)->setFormulaAttributes($att);
}
}
break;
case 'inlineStr':
if (isset($c->f)) {
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
} else {
$value = $this->parseRichText($c->is);
}
break;
case 'e':
if (!isset($c->f)) {
$value = self::castToError($c);
} else {
// Formula
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
}
break;
default:
if (!isset($c->f)) {
$value = self::castToString($c);
} else {
// Formula
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
if (isset($c->f['t'])) {
$attributes = $c->f['t'];
$docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
}
}
break;
}
// read empty cells or the cells are not empty
if ($this->readEmptyCells || ($value !== null && $value !== '')) {
// Rich text?
if ($value instanceof RichText && $this->readDataOnly) {
$value = $value->getPlainText();
}
$cell = $docSheet->getCell($r);
// Assign value
if ($cellDataType != '') {
// it is possible, that datatype is numeric but with an empty string, which result in an error
if ($cellDataType === DataType::TYPE_NUMERIC && ($value === '' || $value === null)) {
$cellDataType = DataType::TYPE_NULL;
}
if ($cellDataType !== DataType::TYPE_NULL) {
$cell->setValueExplicit($value, $cellDataType);
}
} else {
$cell->setValue($value);
}
if ($calculatedValue !== null) {
$cell->setCalculatedValue($calculatedValue);
}
// Style information?
if ($cAttr['s'] && !$this->readDataOnly) {
// no style index means 0, it seems
$cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ?
(int) ($cAttr['s']) : 0);
// issue 3495
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
$cell->getStyle()->setQuotePrefix(false);
}
}
}
++$rowIndex;
}
++$cIndex;
}
}
if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) {
foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) {
$ignoredError = self::testSimpleXml($ignoredErrorx);
$this->processIgnoredErrors($ignoredError, $docSheet);
}
}
if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) {
$protAttr = $xmlSheetNS->sheetProtection->attributes() ?? [];
foreach ($protAttr as $key => $value) {
$method = 'set' . ucfirst($key);
$docSheet->getProtection()->$method(self::boolean((string) $value));
}
}
if ($xmlSheet) {
$this->readSheetProtection($docSheet, $xmlSheet);
}
if ($this->readDataOnly === false) {
$this->readAutoFilter($xmlSheet, $docSheet);
$this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
}
if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) {
foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) {
/** @scrutinizer ignore-call */
$mergeCell = $mergeCellx->attributes();
$mergeRef = (string) ($mergeCell['ref'] ?? '');
if (strpos($mergeRef, ':') !== false) {
$docSheet->mergeCells($mergeRef, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
}
}
if ($xmlSheet && !$this->readDataOnly) {
$unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
}
if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) {
// Create dataValidations node if does not exists, maybe is better inside the foreach ?
if (!$xmlSheet->dataValidations) {
$xmlSheet->addChild('dataValidations');
}
foreach ($xmlSheet->extLst->ext->children(Namespaces::DATA_VALIDATIONS1)->dataValidations->dataValidation as $item) {
$item = self::testSimpleXml($item);
$node = self::testSimpleXml($xmlSheet->dataValidations)->addChild('dataValidation');
foreach ($item->attributes() ?? [] as $attr) {
$node->addAttribute($attr->getName(), $attr);
}
$node->addAttribute('sqref', $item->children(Namespaces::DATA_VALIDATIONS2)->sqref);
if (isset($item->formula1)) {
$childNode = $node->addChild('formula1');
if ($childNode !== null) { // null should never happen
$childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line
}
}
}
}
if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
(new DataValidations($docSheet, $xmlSheet))->load();
}
// unparsed sheet AlternateContent
if ($xmlSheet && !$this->readDataOnly) {
$mc = $xmlSheet->children(Namespaces::COMPATIBILITY);
if ($mc->AlternateContent) {
foreach ($mc->AlternateContent as $alternateContent) {
$alternateContent = self::testSimpleXml($alternateContent);
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
}
}
}
// Add hyperlinks
if (!$this->readDataOnly) {
$hyperlinkReader = new Hyperlinks($docSheet);
// Locate hyperlink relations
$relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
if ($zip->locateName($relationsFileName)) {
$relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);
$hyperlinkReader->readHyperlinks($relsWorksheet);
}
// Loop through hyperlinks
if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) {
$hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks);
}
}
// Add comments
$comments = [];
$vmlComments = [];
if (!$this->readDataOnly) {
// Locate comment relations
$commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
if ($zip->locateName($commentRelations)) {
$relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS);
foreach ($relsWorksheet->Relationship as $elex) {
$ele = self::getAttributes($elex);
if ($ele['Type'] == Namespaces::COMMENTS) {
$comments[(string) $ele['Id']] = (string) $ele['Target'];
}
if ($ele['Type'] == Namespaces::VML) {
$vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
}
}
}
// Loop through comments
foreach ($comments as $relName => $relPath) {
// Load comments file
$relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
// okay to ignore namespace - using xpath
$commentsFile = $this->loadZip($relPath, '');
// Utility variables
$authors = [];
$commentsFile->registerXpathNamespace('com', $mainNS);
$authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author');
foreach ($authorPath as $author) {
$authors[] = (string) $author;
}
// Loop through contents
$contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment');
foreach ($contentPath as $comment) {
$commentx = $comment->attributes();
$commentModel = $docSheet->getComment((string) $commentx['ref']);
if (isset($commentx['authorId'])) {
$commentModel->setAuthor($authors[(int) $commentx['authorId']]);
}
$commentModel->setText($this->parseRichText($comment->children($mainNS)->text));
}
}
// later we will remove from it real vmlComments
$unparsedVmlDrawings = $vmlComments;
$vmlDrawingContents = [];
// Loop through VML comments
foreach ($vmlComments as $relName => $relPath) {
// Load VML comments file
$relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
try {
// no namespace okay - processed with Xpath
$vmlCommentsFile = $this->loadZip($relPath, '', true);
$vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML);
} catch (Throwable $ex) {
//Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
continue;
}
// Locate VML drawings image relations
$drowingImages = [];
$VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels';
$vmlDrawingContents[$relName] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $relPath));
if ($zip->locateName($VMLDrawingsRelations)) {
$relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS);
foreach ($relsVMLDrawing->Relationship as $elex) {
$ele = self::getAttributes($elex);
if ($ele['Type'] == Namespaces::IMAGE) {
$drowingImages[(string) $ele['Id']] = (string) $ele['Target'];
}
}
}
$shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape');
foreach ($shapes as $shape) {
$shape->registerXPathNamespace('v', Namespaces::URN_VML);
if (isset($shape['style'])) {
$style = (string) $shape['style'];
$fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
$column = null;
$row = null;
$fillImageRelId = null;
$fillImageTitle = '';
$clientData = $shape->xpath('.//x:ClientData');
if (is_array($clientData) && !empty($clientData)) {
$clientData = $clientData[0];
if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
$temp = $clientData->xpath('.//x:Row');
if (is_array($temp)) {
$row = $temp[0];
}
$temp = $clientData->xpath('.//x:Column');
if (is_array($temp)) {
$column = $temp[0];
}
}
}
$fillImageRelNode = $shape->xpath('.//v:fill/@o:relid');
if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) {
$fillImageRelNode = $fillImageRelNode[0];
if (isset($fillImageRelNode['relid'])) {
$fillImageRelId = (string) $fillImageRelNode['relid'];
}
}
$fillImageTitleNode = $shape->xpath('.//v:fill/@o:title');
if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) {
$fillImageTitleNode = $fillImageTitleNode[0];
if (isset($fillImageTitleNode['title'])) {
$fillImageTitle = (string) $fillImageTitleNode['title'];
}
}
if (($column !== null) && ($row !== null)) {
// Set comment properties
$comment = $docSheet->getComment([$column + 1, $row + 1]);
$comment->getFillColor()->setRGB($fillColor);
if (isset($drowingImages[$fillImageRelId])) {
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$objDrawing->setName($fillImageTitle);
$imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]);
$objDrawing->setPath(
'zip://' . File::realpath($filename) . '#' . $imagePath,
true,
$zip
);
$comment->setBackgroundImage($objDrawing);
}
// Parse style
$styleArray = explode(';', str_replace(' ', '', $style));
foreach ($styleArray as $stylePair) {
$stylePair = explode(':', $stylePair);
if ($stylePair[0] == 'margin-left') {
$comment->setMarginLeft($stylePair[1]);
}
if ($stylePair[0] == 'margin-top') {
$comment->setMarginTop($stylePair[1]);
}
if ($stylePair[0] == 'width') {
$comment->setWidth($stylePair[1]);
}
if ($stylePair[0] == 'height') {
$comment->setHeight($stylePair[1]);
}
if ($stylePair[0] == 'visibility') {
$comment->setVisible($stylePair[1] == 'visible');
}
}
unset($unparsedVmlDrawings[$relName]);
}
}
}
}
// unparsed vmlDrawing
if ($unparsedVmlDrawings) {
foreach ($unparsedVmlDrawings as $rId => $relPath) {
$rId = substr($rId, 3); // rIdXXX
$unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
$unparsedVmlDrawing[$rId] = [];
$unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
$unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
$unparsedVmlDrawing[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
unset($unparsedVmlDrawing);
}
}
// Header/footer images
if ($xmlSheetNS && $xmlSheetNS->legacyDrawingHF) {
$vmlHfRid = '';
$vmlHfRidAttr = $xmlSheetNS->legacyDrawingHF->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
if ($vmlHfRidAttr !== null && isset($vmlHfRidAttr['id'])) {
$vmlHfRid = (string) $vmlHfRidAttr['id'][0];
}
if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
$relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS);
$vmlRelationship = '';
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] == Namespaces::VML && (string) $ele['Id'] === $vmlHfRid) {
$vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
break;
}
}
if ($vmlRelationship != '') {
// Fetch linked images
$relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS);
$drawings = [];
if (isset($relsVML->Relationship)) {
foreach ($relsVML->Relationship as $ele) {
if ($ele['Type'] == Namespaces::IMAGE) {
$drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
}
}
}
// Fetch VML document
$vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, '');
$vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML);
$hfImages = [];
$shapes = self::xpathNoFalse($vmlDrawing, '//v:shape');
foreach ($shapes as $idx => $shape) {
$shape->registerXPathNamespace('v', Namespaces::URN_VML);
$imageData = $shape->xpath('//v:imagedata');
if (empty($imageData)) {
continue;
}
$imageData = $imageData[$idx];
$imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE);
$style = self::toCSSArray((string) $shape['style']);
if (array_key_exists((string) $imageData['relid'], $drawings)) {
$shapeId = (string) $shape['id'];
$hfImages[$shapeId] = new HeaderFooterDrawing();
if (isset($imageData['title'])) {
$hfImages[$shapeId]->setName((string) $imageData['title']);
}
$hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false);
$hfImages[$shapeId]->setResizeProportional(false);
$hfImages[$shapeId]->setWidth($style['width']);
$hfImages[$shapeId]->setHeight($style['height']);
if (isset($style['margin-left'])) {
$hfImages[$shapeId]->setOffsetX($style['margin-left']);
}
$hfImages[$shapeId]->setOffsetY($style['margin-top']);
$hfImages[$shapeId]->setResizeProportional(true);
}
}
$docSheet->getHeaderFooter()->setImages($hfImages);
}
}
}
}
// TODO: Autoshapes from twoCellAnchors!
$drawingFilename = dirname("$dir/$fileWorksheet")
. '/_rels/'
. basename($fileWorksheet)
. '.rels';
if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
$drawingFilename = substr($drawingFilename, 4);
}
if (substr($drawingFilename, 0, 8) === '/xl//xl/') {
$drawingFilename = substr($drawingFilename, 5);
}
if ($zip->locateName($drawingFilename)) {
$relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
$drawings = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
$eleTarget = (string) $ele['Target'];
if (substr($eleTarget, 0, 4) === '/xl/') {
$drawings[(string) $ele['Id']] = substr($eleTarget, 1);
} else {
$drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
}
}
}
if ($xmlSheetNS->drawing && !$this->readDataOnly) {
$unparsedDrawings = [];
$fileDrawing = null;
foreach ($xmlSheetNS->drawing as $drawing) {
$drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
$fileDrawing = $drawings[$drawingRelId];
$drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels';
$relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase);
$images = [];
$hyperlinks = [];
if ($relsDrawing && $relsDrawing->Relationship) {
foreach ($relsDrawing->Relationship as $ele) {
$eleType = (string) $ele['Type'];
if ($eleType === Namespaces::HYPERLINK) {
$hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
}
if ($eleType === "$xmlNamespaceBase/image") {
$eleTarget = (string) $ele['Target'];
if (substr($eleTarget, 0, 4) === '/xl/') {
$eleTarget = substr($eleTarget, 1);
$images[(string) $ele['Id']] = $eleTarget;
} else {
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $eleTarget);
}
} elseif ($eleType === "$xmlNamespaceBase/chart") {
if ($this->includeCharts) {
$eleTarget = (string) $ele['Target'];
if (substr($eleTarget, 0, 4) === '/xl/') {
$index = substr($eleTarget, 1);
} else {
$index = self::dirAdd($fileDrawing, $eleTarget);
}
$charts[$index] = [
'id' => (string) $ele['Id'],
'sheet' => $docSheet->getTitle(),
];
}
}
}
}
$xmlDrawing = $this->loadZipNoNamespace($fileDrawing, '');
$xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING);
if ($xmlDrawingChildren->oneCellAnchor) {
foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
$oneCellAnchor = self::testSimpleXml($oneCellAnchor);
if ($oneCellAnchor->pic->blipFill) {
/** @var SimpleXMLElement $blip */
$blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
/** @var SimpleXMLElement $xfrm */
$xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;
/** @var SimpleXMLElement $outerShdw */
$outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
$objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name'));
$objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
$embedImageKey = (string) self::getArrayItem(
self::getAttributes($blip, $xmlNamespaceBase),
'embed'
);
if (isset($images[$embedImageKey])) {
$objDrawing->setPath(
'zip://' . File::realpath($filename) . '#' .
$images[$embedImageKey],
false
);
} else {
$linkImageKey = (string) self::getArrayItem(
$blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
'link'
);
if (isset($images[$linkImageKey])) {
$url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
$objDrawing->setPath($url);
}
}
$objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
$objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff));
$objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
$objDrawing->setResizeProportional(false);
$objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')));
$objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')));
if ($xfrm) {
$objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));
}
if ($outerShdw) {
$shadow = $objDrawing->getShadow();
$shadow->setVisible(true);
$shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad')));
$shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist')));
$shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir')));
$shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn'));
$clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
$shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val'));
$shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);
}
$this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
$objDrawing->setWorksheet($docSheet);
} elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) {
// Exported XLSX from Google Sheets positions charts with a oneCellAnchor
$coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
$offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
$offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
$width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'));
$height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy'));
$graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
/** @var SimpleXMLElement $chartRef */
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
$chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
'fromCoordinate' => $coordinates,
'fromOffsetX' => $offsetX,
'fromOffsetY' => $offsetY,
'width' => $width,
'height' => $height,
'worksheetTitle' => $docSheet->getTitle(),
'oneCellAnchor' => true,
];
}
}
}
if ($xmlDrawingChildren->twoCellAnchor) {
foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
$twoCellAnchor = self::testSimpleXml($twoCellAnchor);
if ($twoCellAnchor->pic->blipFill) {
$blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
$xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;
$outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
$objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
/** @scrutinizer ignore-call */
$editAs = $twoCellAnchor->attributes();
if (isset($editAs, $editAs['editAs'])) {
$objDrawing->setEditAs($editAs['editAs']);
}
$objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name'));
$objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
$embedImageKey = (string) self::getArrayItem(
self::getAttributes($blip, $xmlNamespaceBase),
'embed'
);
if (isset($images[$embedImageKey])) {
$objDrawing->setPath(
'zip://' . File::realpath($filename) . '#' .
$images[$embedImageKey],
false
);
} else {
$linkImageKey = (string) self::getArrayItem(
$blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
'link'
);
if (isset($images[$linkImageKey])) {
$url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
$objDrawing->setPath($url);
}
}
$objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));
$objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff));
$objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));
$objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1));
$objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff));
$objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff));
$objDrawing->setResizeProportional(false);
if ($xfrm) {
$objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx')));
$objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy')));
$objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));
}
if ($outerShdw) {
$shadow = $objDrawing->getShadow();
$shadow->setVisible(true);
$shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad')));
$shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist')));
$shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir')));
$shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn'));
$clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
$shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val'));
$shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);
}
$this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
$objDrawing->setWorksheet($docSheet);
} elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
$fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
$fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff);
$fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);
$toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);
$toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
$toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
$graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
/** @var SimpleXMLElement $chartRef */
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
$chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
'fromCoordinate' => $fromCoordinate,
'fromOffsetX' => $fromOffsetX,
'fromOffsetY' => $fromOffsetY,
'toCoordinate' => $toCoordinate,
'toOffsetX' => $toOffsetX,
'toOffsetY' => $toOffsetY,
'worksheetTitle' => $docSheet->getTitle(),
];
}
}
}
if ($xmlDrawingChildren->absoluteAnchor) {
foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) {
if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) {
$graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
/** @var SimpleXMLElement $chartRef */
$chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
$thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
$width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]);
$height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]);
$chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
'fromCoordinate' => 'A1',
'fromOffsetX' => 0,
'fromOffsetY' => 0,
'width' => $width,
'height' => $height,
'worksheetTitle' => $docSheet->getTitle(),
];
}
}
}
if (empty($relsDrawing) && $xmlDrawing->count() == 0) {
// Save Drawing without rels and children as unparsed
$unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
}
}
// store original rId of drawing files
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
$drawingRelId = (string) $ele['Id'];
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
if (isset($unparsedDrawings[$drawingRelId])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
}
}
}
if ($xmlSheet->legacyDrawing && !$this->readDataOnly) {
foreach ($xmlSheet->legacyDrawing as $drawing) {
$drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
if (isset($vmlDrawingContents[$drawingRelId])) {
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId];
}
}
}
// unparsed drawing AlternateContent
$xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);
if ($xmlAltDrawing->AlternateContent) {
foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
$alternateContent = self::testSimpleXml($alternateContent);
$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
}
}
}
}
$this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
$this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
// Loop through definedNames
if ($xmlWorkbook->definedNames) {
foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
// Extract range
$extractedRange = (string) $definedName;
if (($spos = strpos($extractedRange, '!')) !== false) {
$extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
} else {
$extractedRange = str_replace('$', '', $extractedRange);
}
// Valid range?
if ($extractedRange == '') {
continue;
}
// Some definedNames are only applicable if we are on the same sheet...
if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) {
// Switch on type
switch ((string) $definedName['name']) {
case '_xlnm._FilterDatabase':
if ((string) $definedName['hidden'] !== '1') {
$extractedRange = explode(',', $extractedRange);
foreach ($extractedRange as $range) {
$autoFilterRange = $range;
if (strpos($autoFilterRange, ':') !== false) {
$docSheet->getAutoFilter()->setRange($autoFilterRange);
}
}
}
break;
case '_xlnm.Print_Titles':
// Split $extractedRange
$extractedRange = explode(',', $extractedRange);
// Set print titles
foreach ($extractedRange as $range) {
$matches = [];
$range = str_replace('$', '', $range);
// check for repeating columns, e g. 'A:A' or 'A:D'
if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) {
$docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]);
} elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) {
// check for repeating rows, e.g. '1:1' or '1:5'
$docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]);
}
}
break;
case '_xlnm.Print_Area':
$rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: [];
$newRangeSets = [];
foreach ($rangeSets as $rangeSet) {
[, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
if (empty($rangeSet)) {
continue;
}
if (strpos($rangeSet, ':') === false) {
$rangeSet = $rangeSet . ':' . $rangeSet;
}
$newRangeSets[] = str_replace('$', '', $rangeSet);
}
if (count($newRangeSets) > 0) {
$docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
}
break;
default:
break;
}
}
}
}
// Next sheet id
++$sheetId;
}
// Loop through definedNames
if ($xmlWorkbook->definedNames) {
foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
// Extract range
$extractedRange = (string) $definedName;
// Valid range?
if ($extractedRange == '') {
continue;
}
// Some definedNames are only applicable if we are on the same sheet...
if ((string) $definedName['localSheetId'] != '') {
// Local defined name
// Switch on type
switch ((string) $definedName['name']) {
case '_xlnm._FilterDatabase':
case '_xlnm.Print_Titles':
case '_xlnm.Print_Area':
break;
default:
if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
$range = Worksheet::extractSheetTitle((string) $definedName, true);
$scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]);
if (strpos((string) $definedName, '!') !== false) {
$range[0] = str_replace("''", "'", $range[0]);
$range[0] = str_replace("'", '', $range[0]);
if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line
$excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));
} else {
$excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope));
}
} else {
$excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true));
}
}
break;
}
} elseif (!isset($definedName['localSheetId'])) {
$definedRange = (string) $definedName;
// "Global" definedNames
$locatedSheet = null;
if (strpos((string) $definedName, '!') !== false) {
// Modify range, and extract the first worksheet reference
// Need to split on a comma or a space if not in quotes, and extract the first part.
$definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange);
// Extract sheet name
[$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line
$extractedSheetName = trim($extractedSheetName, "'");
// Locate sheet
$locatedSheet = $excel->getSheetByName($extractedSheetName);
}
if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) {
$definedRange = '#REF!';
}
$excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false));
}
}
}
}
(new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly);
break;
}
}
if (!$this->readDataOnly) {
$contentTypes = $this->loadZip('[Content_Types].xml');
// Default content types
foreach ($contentTypes->Default as $contentType) {
switch ($contentType['ContentType']) {
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
$unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
break;
}
}
// Override content types
foreach ($contentTypes->Override as $contentType) {
switch ($contentType['ContentType']) {
case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
if ($this->includeCharts) {
$chartEntryRef = ltrim((string) $contentType['PartName'], '/');
$chartElements = $this->loadZip($chartEntryRef);
$chartReader = new Chart($chartNS, $drawingNS);
$objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));
if (isset($charts[$chartEntryRef])) {
$chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
if (isset($chartDetails[$chartPositionRef])) {
$excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line
$objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
// For oneCellAnchor or absoluteAnchor positioned charts,
// toCoordinate is not in the data. Does it need to be calculated?
if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) {
// twoCellAnchor
$objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
$objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
} else {
// oneCellAnchor or absoluteAnchor (e.g. Chart sheet)
$objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
$objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']);
if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) {
$objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']);
}
}
}
}
}
break;
// unparsed
case 'application/vnd.ms-excel.controlproperties+xml':
$unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
break;
}
}
}
$excel->setUnparsedLoadedData($unparsedLoadedData);
$zip->close();
return $excel;
}
/**
* @return RichText
*/
private function parseRichText(?SimpleXMLElement $is)
{
$value = new RichText();
if (isset($is->t)) {
$value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t));
} elseif ($is !== null) {
if (is_object($is->r)) {
/** @var SimpleXMLElement $run */
foreach ($is->r as $run) {
if (!isset($run->rPr)) {
$value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
} else {
$objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
$objFont = $objText->getFont() ?? new StyleFont();
if (isset($run->rPr->rFont)) {
$attr = $run->rPr->rFont->attributes();
if (isset($attr['val'])) {
$objFont->setName((string) $attr['val']);
}
}
if (isset($run->rPr->sz)) {
$attr = $run->rPr->sz->attributes();
if (isset($attr['val'])) {
$objFont->setSize((float) $attr['val']);
}
}
if (isset($run->rPr->color)) {
$objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color)));
}
if (isset($run->rPr->b)) {
$attr = $run->rPr->b->attributes();
if (
(isset($attr['val']) && self::boolean((string) $attr['val'])) ||
(!isset($attr['val']))
) {
$objFont->setBold(true);
}
}
if (isset($run->rPr->i)) {
$attr = $run->rPr->i->attributes();
if (
(isset($attr['val']) && self::boolean((string) $attr['val'])) ||
(!isset($attr['val']))
) {
$objFont->setItalic(true);
}
}
if (isset($run->rPr->vertAlign)) {
$attr = $run->rPr->vertAlign->attributes();
if (isset($attr['val'])) {
$vertAlign = strtolower((string) $attr['val']);
if ($vertAlign == 'superscript') {
$objFont->setSuperscript(true);
}
if ($vertAlign == 'subscript') {
$objFont->setSubscript(true);
}
}
}
if (isset($run->rPr->u)) {
$attr = $run->rPr->u->attributes();
if (!isset($attr['val'])) {
$objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
} else {
$objFont->setUnderline((string) $attr['val']);
}
}
if (isset($run->rPr->strike)) {
$attr = $run->rPr->strike->attributes();
if (
(isset($attr['val']) && self::boolean((string) $attr['val'])) ||
(!isset($attr['val']))
) {
$objFont->setStrikethrough(true);
}
}
}
}
}
}
return $value;
}
private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void
{
$baseDir = dirname($customUITarget);
$nameCustomUI = basename($customUITarget);
// get the xml file (ribbon)
$localRibbon = $this->getFromZipArchive($zip, $customUITarget);
$customUIImagesNames = [];
$customUIImagesBinaries = [];
// something like customUI/_rels/customUI.xml.rels
$pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels';
$dataRels = $this->getFromZipArchive($zip, $pathRels);
if ($dataRels) {
// exists and not empty if the ribbon have some pictures (other than internal MSO)
$UIRels = simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($dataRels),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if (false !== $UIRels) {
// we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image
foreach ($UIRels->Relationship as $ele) {
if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') {
// an image ?
$customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target'];
$customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']);
}
}
}
}
if ($localRibbon) {
$excel->setRibbonXMLData($customUITarget, $localRibbon);
if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) {
$excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries);
} else {
$excel->setRibbonBinObjects(null, null);
}
} else {
$excel->setRibbonXMLData(null, null);
$excel->setRibbonBinObjects(null, null);
}
}
/**
* @param null|array|bool|SimpleXMLElement $array
* @param int|string $key
*
* @return mixed
*/
private static function getArrayItem($array, $key = 0)
{
return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null);
}
/**
* @param null|SimpleXMLElement|string $base
* @param null|SimpleXMLElement|string $add
*/
private static function dirAdd($base, $add): string
{
$base = (string) $base;
$add = (string) $add;
return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
}
private static function toCSSArray(string $style): array
{
$style = self::stripWhiteSpaceFromStyleString($style);
$temp = explode(';', $style);
$style = [];
foreach ($temp as $item) {
$item = explode(':', $item);
if (strpos($item[1], 'px') !== false) {
$item[1] = str_replace('px', '', $item[1]);
}
if (strpos($item[1], 'pt') !== false) {
$item[1] = str_replace('pt', '', $item[1]);
$item[1] = (string) Font::fontSizeToPixels((int) $item[1]);
}
if (strpos($item[1], 'in') !== false) {
$item[1] = str_replace('in', '', $item[1]);
$item[1] = (string) Font::inchSizeToPixels((int) $item[1]);
}
if (strpos($item[1], 'cm') !== false) {
$item[1] = str_replace('cm', '', $item[1]);
$item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]);
}
$style[$item[0]] = $item[1];
}
return $style;
}
public static function stripWhiteSpaceFromStyleString(string $string): string
{
return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
}
private static function boolean(string $value): bool
{
if (is_numeric($value)) {
return (bool) $value;
}
return $value === 'true' || $value === 'TRUE';
}
/**
* @param array $hyperlinks
*/
private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void
{
$hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;
if ($hlinkClick->count() === 0) {
return;
}
$hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id'];
$hyperlink = new Hyperlink(
$hyperlinks[$hlinkId],
(string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name')
);
$objDrawing->setHyperlink($hyperlink);
}
private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void
{
if (!$xmlWorkbook->workbookProtection) {
return;
}
$excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision'));
$excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure'));
$excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));
if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
$excel->getSecurity()->setRevisionsPassword(
(string) $xmlWorkbook->workbookProtection['revisionsPassword'],
true
);
}
if ($xmlWorkbook->workbookProtection['workbookPassword']) {
$excel->getSecurity()->setWorkbookPassword(
(string) $xmlWorkbook->workbookProtection['workbookPassword'],
true
);
}
}
private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool
{
$returnValue = null;
$protectKey = $protection[$key];
if (!empty($protectKey)) {
$protectKey = (string) $protectKey;
$returnValue = $protectKey !== 'false' && (bool) $protectKey;
}
return $returnValue;
}
private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void
{
$zip = $this->zip;
if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
return;
}
$filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
$relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);
$ctrlProps = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') {
$ctrlProps[(string) $ele['Id']] = $ele;
}
}
$unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps'];
foreach ($ctrlProps as $rId => $ctrlProp) {
$rId = substr($rId, 3); // rIdXXX
$unparsedCtrlProps[$rId] = [];
$unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']);
$unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target'];
$unparsedCtrlProps[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath']));
}
unset($unparsedCtrlProps);
}
private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void
{
$zip = $this->zip;
if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
return;
}
$filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
$relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);
$sheetPrinterSettings = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') {
$sheetPrinterSettings[(string) $ele['Id']] = $ele;
}
}
$unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
foreach ($sheetPrinterSettings as $rId => $printerSettings) {
$rId = substr($rId, 3); // rIdXXX
if (substr($rId, -2) !== 'ps') {
$rId = $rId . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing
}
$unparsedPrinterSettings[$rId] = [];
$unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
$unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
$unparsedPrinterSettings[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath']));
}
unset($unparsedPrinterSettings);
}
private function getWorkbookBaseName(): array
{
$workbookBasename = '';
$xmlNamespaceBase = '';
// check if it is an OOXML archive
$rels = $this->loadZip(self::INITIAL_FILE);
foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) {
$rel = self::getAttributes($rel);
$type = (string) $rel['Type'];
switch ($type) {
case Namespaces::OFFICE_DOCUMENT:
case Namespaces::PURL_OFFICE_DOCUMENT:
$basename = basename((string) $rel['Target']);
$xmlNamespaceBase = dirname($type);
if (preg_match('/workbook.*\.xml/', $basename)) {
$workbookBasename = $basename;
}
break;
}
}
return [$workbookBasename, $xmlNamespaceBase];
}
private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void
{
if ($this->readDataOnly || !$xmlSheet->sheetProtection) {
return;
}
$algorithmName = (string) $xmlSheet->sheetProtection['algorithmName'];
$protection = $docSheet->getProtection();
$protection->setAlgorithm($algorithmName);
if ($algorithmName) {
$protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true);
$protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']);
$protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
} else {
$protection->setPassword((string) $xmlSheet->sheetProtection['password'], true);
}
if ($xmlSheet->protectedRanges->protectedRange) {
foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
$docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
}
}
}
private function readAutoFilter(
SimpleXMLElement $xmlSheet,
Worksheet $docSheet
): void {
if ($xmlSheet && $xmlSheet->autoFilter) {
(new AutoFilter($docSheet, $xmlSheet))->load();
}
}
private function readTables(
SimpleXMLElement $xmlSheet,
Worksheet $docSheet,
string $dir,
string $fileWorksheet,
ZipArchive $zip
): void {
if ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) {
$this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet);
}
}
private function readTablesInTablesFile(
SimpleXMLElement $xmlSheet,
string $dir,
string $fileWorksheet,
ZipArchive $zip,
Worksheet $docSheet
): void {
foreach ($xmlSheet->tableParts->tablePart as $tablePart) {
$relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT);
$tablePartRel = (string) $relation['id'];
$relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
if ($zip->locateName($relationsFileName)) {
$relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);
foreach ($relsTableReferences->Relationship as $relationship) {
$relationshipAttributes = self::getAttributes($relationship, '');
if ((string) $relationshipAttributes['Id'] === $tablePartRel) {
$relationshipFileName = (string) $relationshipAttributes['Target'];
$relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName;
$relationshipFilePath = File::realpath($relationshipFilePath);
if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) {
$tableXml = $this->loadZip($relationshipFilePath);
(new TableReader($docSheet, $tableXml))->load();
}
}
}
}
}
}
private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array
{
$array = [];
if ($sxml && $sxml->{$node1}->{$node2}) {
foreach ($sxml->{$node1}->{$node2} as $node) {
$array[] = $node;
}
}
return $array;
}
private static function extractPalette(?SimpleXMLElement $sxml): array
{
$array = [];
if ($sxml && $sxml->colors->indexedColors) {
foreach ($sxml->colors->indexedColors->rgbColor as $node) {
if ($node !== null) {
$attr = $node->attributes();
if (isset($attr['rgb'])) {
$array[] = (string) $attr['rgb'];
}
}
}
}
return $array;
}
private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void
{
$attributes = self::getAttributes($xml);
$sqref = (string) ($attributes['sqref'] ?? '');
$numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? '');
$formula = (string) ($attributes['formula'] ?? '');
$twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? '');
$evalError = (string) ($attributes['evalError'] ?? '');
if (!empty($sqref)) {
$explodedSqref = explode(' ', $sqref);
$pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/';
foreach ($explodedSqref as $sqref1) {
if (preg_match($pattern1, $sqref1, $matches) === 1) {
$firstRow = $matches[2];
$firstCol = $matches[1];
if (array_key_exists(3, $matches)) {
$lastCol = $matches[4];
$lastRow = $matches[5];
} else {
$lastCol = $firstCol;
$lastRow = $firstRow;
}
++$lastCol;
for ($row = $firstRow; $row <= $lastRow; ++$row) {
for ($col = $firstCol; $col !== $lastCol; ++$col) {
if ($numberStoredAsText === '1') {
$sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true);
}
if ($formula === '1') {
$sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true);
}
if ($twoDigitTextYear === '1') {
$sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true);
}
if ($evalError === '1') {
$sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true);
}
}
}
}
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php 0000644 00000047211 15243417764 0015757 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Slk extends BaseReader
{
/**
* Input encoding.
*
* @var string
*/
private $inputEncoding = 'ANSI';
/**
* Sheet index to read.
*
* @var int
*/
private $sheetIndex = 0;
/**
* Formats.
*
* @var array
*/
private $formats = [];
/**
* Format Count.
*
* @var int
*/
private $format = 0;
/**
* Fonts.
*
* @var array
*/
private $fonts = [];
/**
* Font Count.
*
* @var int
*/
private $fontcount = 0;
/**
* Create a new SYLK Reader instance.
*/
public function __construct()
{
parent::__construct();
}
/**
* Validate that the current file is a SYLK file.
*/
public function canRead(string $filename): bool
{
try {
$this->openFile($filename);
} catch (ReaderException $e) {
return false;
}
// Read sample data (first 2 KB will do)
$data = (string) fread($this->fileHandle, 2048);
// Count delimiters in file
$delimiterCount = substr_count($data, ';');
$hasDelimiter = $delimiterCount > 0;
// Analyze first line looking for ID; signature
$lines = explode("\n", $data);
$hasId = substr($lines[0], 0, 4) === 'ID;P';
fclose($this->fileHandle);
return $hasDelimiter && $hasId;
}
private function canReadOrBust(string $filename): void
{
if (!$this->canRead($filename)) {
throw new ReaderException($filename . ' is an Invalid SYLK file.');
}
$this->openFile($filename);
}
/**
* Set input encoding.
*
* @deprecated no use is made of this property
*
* @param string $inputEncoding Input encoding, eg: 'ANSI'
*
* @return $this
*
* @codeCoverageIgnore
*/
public function setInputEncoding($inputEncoding)
{
$this->inputEncoding = $inputEncoding;
return $this;
}
/**
* Get input encoding.
*
* @deprecated no use is made of this property
*
* @return string
*
* @codeCoverageIgnore
*/
public function getInputEncoding()
{
return $this->inputEncoding;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
// Open file
$this->canReadOrBust($filename);
$fileHandle = $this->fileHandle;
rewind($fileHandle);
$worksheetInfo = [];
$worksheetInfo[0]['worksheetName'] = basename($filename, '.slk');
// loop through one row (line) at a time in the file
$rowIndex = 0;
$columnIndex = 0;
while (($rowData = fgets($fileHandle)) !== false) {
$columnIndex = 0;
// convert SYLK encoded $rowData to UTF-8
$rowData = StringHelper::SYLKtoUTF8($rowData);
// explode each row at semicolons while taking into account that literal semicolon (;)
// is escaped like this (;;)
$rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData)))));
$dataType = array_shift($rowData);
if ($dataType == 'B') {
foreach ($rowData as $rowDatum) {
switch ($rowDatum[0]) {
case 'X':
$columnIndex = (int) substr($rowDatum, 1) - 1;
break;
case 'Y':
$rowIndex = substr($rowDatum, 1);
break;
}
}
break;
}
}
$worksheetInfo[0]['lastColumnIndex'] = $columnIndex;
$worksheetInfo[0]['totalRows'] = $rowIndex;
$worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
$worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
// Close file
fclose($fileHandle);
return $worksheetInfo;
}
/**
* Loads PhpSpreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
private const COLOR_ARRAY = [
'FF00FFFF', // 0 - cyan
'FF000000', // 1 - black
'FFFFFFFF', // 2 - white
'FFFF0000', // 3 - red
'FF00FF00', // 4 - green
'FF0000FF', // 5 - blue
'FFFFFF00', // 6 - yellow
'FFFF00FF', // 7 - magenta
];
private const FONT_STYLE_MAPPINGS = [
'B' => 'bold',
'I' => 'italic',
'U' => 'underline',
];
private function processFormula(string $rowDatum, bool &$hasCalculatedValue, string &$cellDataFormula, string $row, string $column): void
{
$cellDataFormula = '=' . substr($rowDatum, 1);
// Convert R1C1 style references to A1 style references (but only when not quoted)
$temp = explode('"', $cellDataFormula);
$key = false;
foreach ($temp as &$value) {
// Only count/replace in alternate array entries
$key = $key === false;
if ($key) {
preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);
// Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way
// through the formula from left to right. Reversing means that we work right to left.through
// the formula
$cellReferences = array_reverse($cellReferences);
// Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent,
// then modify the formula to use that new reference
foreach ($cellReferences as $cellReference) {
$rowReference = $cellReference[2][0];
// Empty R reference is the current row
if ($rowReference == '') {
$rowReference = $row;
}
// Bracketed R references are relative to the current row
if ($rowReference[0] == '[') {
$rowReference = (int) $row + (int) trim($rowReference, '[]');
}
$columnReference = $cellReference[4][0];
// Empty C reference is the current column
if ($columnReference == '') {
$columnReference = $column;
}
// Bracketed C references are relative to the current column
if ($columnReference[0] == '[') {
$columnReference = (int) $column + (int) trim($columnReference, '[]');
}
$A1CellReference = Coordinate::stringFromColumnIndex((int) $columnReference) . $rowReference;
$value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0]));
}
}
}
unset($value);
// Then rebuild the formula string
$cellDataFormula = implode('"', $temp);
$hasCalculatedValue = true;
}
private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void
{
// Read cell value data
$hasCalculatedValue = false;
$cellDataFormula = $cellData = '';
foreach ($rowData as $rowDatum) {
switch ($rowDatum[0]) {
case 'C':
case 'X':
$column = substr($rowDatum, 1);
break;
case 'R':
case 'Y':
$row = substr($rowDatum, 1);
break;
case 'K':
$cellData = substr($rowDatum, 1);
break;
case 'E':
$this->processFormula($rowDatum, $hasCalculatedValue, $cellDataFormula, $row, $column);
break;
case 'A':
$comment = substr($rowDatum, 1);
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
$spreadsheet->getActiveSheet()
->getComment("$columnLetter$row")
->getText()
->createText($comment);
break;
}
}
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
$cellData = Calculation::unwrapResult($cellData);
// Set cell value
$this->processCFinal($spreadsheet, $hasCalculatedValue, $cellDataFormula, $cellData, "$columnLetter$row");
}
private function processCFinal(Spreadsheet &$spreadsheet, bool $hasCalculatedValue, string $cellDataFormula, string $cellData, string $coordinate): void
{
// Set cell value
$spreadsheet->getActiveSheet()->getCell($coordinate)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData);
if ($hasCalculatedValue) {
$cellData = Calculation::unwrapResult($cellData);
$spreadsheet->getActiveSheet()->getCell($coordinate)->setCalculatedValue($cellData);
}
}
private function processFRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void
{
// Read cell formatting
$formatStyle = $columnWidth = '';
$startCol = $endCol = '';
$fontStyle = '';
$styleData = [];
foreach ($rowData as $rowDatum) {
switch ($rowDatum[0]) {
case 'C':
case 'X':
$column = substr($rowDatum, 1);
break;
case 'R':
case 'Y':
$row = substr($rowDatum, 1);
break;
case 'P':
$formatStyle = $rowDatum;
break;
case 'W':
[$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1));
break;
case 'S':
$this->styleSettings($rowDatum, $styleData, $fontStyle);
break;
}
}
$this->addFormats($spreadsheet, $formatStyle, $row, $column);
$this->addFonts($spreadsheet, $fontStyle, $row, $column);
$this->addStyle($spreadsheet, $styleData, $row, $column);
$this->addWidth($spreadsheet, $columnWidth, $startCol, $endCol);
}
private const STYLE_SETTINGS_FONT = ['D' => 'bold', 'I' => 'italic'];
private const STYLE_SETTINGS_BORDER = [
'B' => 'bottom',
'L' => 'left',
'R' => 'right',
'T' => 'top',
];
private function styleSettings(string $rowDatum, array &$styleData, string &$fontStyle): void
{
$styleSettings = substr($rowDatum, 1);
$iMax = strlen($styleSettings);
for ($i = 0; $i < $iMax; ++$i) {
$char = $styleSettings[$i];
if (array_key_exists($char, self::STYLE_SETTINGS_FONT)) {
$styleData['font'][self::STYLE_SETTINGS_FONT[$char]] = true;
} elseif (array_key_exists($char, self::STYLE_SETTINGS_BORDER)) {
$styleData['borders'][self::STYLE_SETTINGS_BORDER[$char]]['borderStyle'] = Border::BORDER_THIN;
} elseif ($char == 'S') {
$styleData['fill']['fillType'] = \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_PATTERN_GRAY125;
} elseif ($char == 'M') {
if (preg_match('/M([1-9]\\d*)/', $styleSettings, $matches)) {
$fontStyle = $matches[1];
}
}
}
}
private function addFormats(Spreadsheet &$spreadsheet, string $formatStyle, string $row, string $column): void
{
if ($formatStyle && $column > '' && $row > '') {
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
if (isset($this->formats[$formatStyle])) {
$spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]);
}
}
}
private function addFonts(Spreadsheet &$spreadsheet, string $fontStyle, string $row, string $column): void
{
if ($fontStyle && $column > '' && $row > '') {
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
if (isset($this->fonts[$fontStyle])) {
$spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->fonts[$fontStyle]);
}
}
}
private function addStyle(Spreadsheet &$spreadsheet, array $styleData, string $row, string $column): void
{
if ((!empty($styleData)) && $column > '' && $row > '') {
$columnLetter = Coordinate::stringFromColumnIndex((int) $column);
$spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData);
}
}
private function addWidth(Spreadsheet $spreadsheet, string $columnWidth, string $startCol, string $endCol): void
{
if ($columnWidth > '') {
if ($startCol == $endCol) {
$startCol = Coordinate::stringFromColumnIndex((int) $startCol);
$spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth);
} else {
$startCol = Coordinate::stringFromColumnIndex((int) $startCol);
$endCol = Coordinate::stringFromColumnIndex((int) $endCol);
$spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth);
do {
$spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth);
} while ($startCol !== $endCol);
}
}
}
private function processPRecord(array $rowData, Spreadsheet &$spreadsheet): void
{
// Read shared styles
$formatArray = [];
$fromFormats = ['\-', '\ '];
$toFormats = ['-', ' '];
foreach ($rowData as $rowDatum) {
switch ($rowDatum[0]) {
case 'P':
$formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1));
break;
case 'E':
case 'F':
$formatArray['font']['name'] = substr($rowDatum, 1);
break;
case 'M':
$formatArray['font']['size'] = ((float) substr($rowDatum, 1)) / 20;
break;
case 'L':
$this->processPColors($rowDatum, $formatArray);
break;
case 'S':
$this->processPFontStyles($rowDatum, $formatArray);
break;
}
}
$this->processPFinal($spreadsheet, $formatArray);
}
private function processPColors(string $rowDatum, array &$formatArray): void
{
if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) {
$fontColor = $matches[1] % 8;
$formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor];
}
}
private function processPFontStyles(string $rowDatum, array &$formatArray): void
{
$styleSettings = substr($rowDatum, 1);
$iMax = strlen($styleSettings);
for ($i = 0; $i < $iMax; ++$i) {
if (array_key_exists($styleSettings[$i], self::FONT_STYLE_MAPPINGS)) {
$formatArray['font'][self::FONT_STYLE_MAPPINGS[$styleSettings[$i]]] = true;
}
}
}
private function processPFinal(Spreadsheet &$spreadsheet, array $formatArray): void
{
if (array_key_exists('numberFormat', $formatArray)) {
$this->formats['P' . $this->format] = $formatArray;
++$this->format;
} elseif (array_key_exists('font', $formatArray)) {
++$this->fontcount;
$this->fonts[$this->fontcount] = $formatArray;
if ($this->fontcount === 1) {
$spreadsheet->getDefaultStyle()->applyFromArray($formatArray);
}
}
}
/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*
* @param string $filename
*
* @return Spreadsheet
*/
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
{
// Open file
$this->canReadOrBust($filename);
$fileHandle = $this->fileHandle;
rewind($fileHandle);
// Create new Worksheets
while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
$spreadsheet->createSheet();
}
$spreadsheet->setActiveSheetIndex($this->sheetIndex);
$spreadsheet->getActiveSheet()->setTitle(substr(basename($filename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH));
// Loop through file
$column = $row = '';
// loop through one row (line) at a time in the file
while (($rowDataTxt = fgets($fileHandle)) !== false) {
// convert SYLK encoded $rowData to UTF-8
$rowDataTxt = StringHelper::SYLKtoUTF8($rowDataTxt);
// explode each row at semicolons while taking into account that literal semicolon (;)
// is escaped like this (;;)
$rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowDataTxt)))));
$dataType = array_shift($rowData);
if ($dataType == 'P') {
// Read shared styles
$this->processPRecord($rowData, $spreadsheet);
} elseif ($dataType == 'C') {
// Read cell value data
$this->processCRecord($rowData, $spreadsheet, $row, $column);
} elseif ($dataType == 'F') {
// Read cell formatting
$this->processFRecord($rowData, $spreadsheet, $row, $column);
} else {
$this->columnRowFromRowData($rowData, $column, $row);
}
}
// Close file
fclose($fileHandle);
// Return
return $spreadsheet;
}
private function columnRowFromRowData(array $rowData, string &$column, string &$row): void
{
foreach ($rowData as $rowDatum) {
$char0 = $rowDatum[0];
if ($char0 === 'X' || $char0 == 'C') {
$column = substr($rowDatum, 1);
} elseif ($char0 === 'Y' || $char0 == 'R') {
$row = substr($rowDatum, 1);
}
}
}
/**
* Get sheet index.
*
* @return int
*/
public function getSheetIndex()
{
return $this->sheetIndex;
}
/**
* Set sheet index.
*
* @param int $sheetIndex Sheet index
*
* @return $this
*/
public function setSheetIndex($sheetIndex)
{
$this->sheetIndex = $sheetIndex;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php 0000644 00000000640 15243417764 0017353 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
interface IReadFilter
{
/**
* Should this cell be read?
*
* @param string $columnAddress Column address (as a string value like "A", or "IV")
* @param int $row Row number
* @param string $worksheetName Optional worksheet name
*
* @return bool
*/
public function readCell($columnAddress, $row, $worksheetName = '');
}
phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php 0000644 00000012263 15243417764 0017222 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
abstract class BaseReader implements IReader
{
/**
* Read data only?
* Identifies whether the Reader should only read data values for cells, and ignore any formatting information;
* or whether it should read both data and formatting.
*
* @var bool
*/
protected $readDataOnly = false;
/**
* Read empty cells?
* Identifies whether the Reader should read data values for cells all cells, or should ignore cells containing
* null value or empty string.
*
* @var bool
*/
protected $readEmptyCells = true;
/**
* Read charts that are defined in the workbook?
* Identifies whether the Reader should read the definitions for any charts that exist in the workbook;.
*
* @var bool
*/
protected $includeCharts = false;
/**
* Restrict which sheets should be loaded?
* This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded.
*
* @var null|string[]
*/
protected $loadSheetsOnly;
/**
* IReadFilter instance.
*
* @var IReadFilter
*/
protected $readFilter;
/** @var resource */
protected $fileHandle;
/**
* @var ?XmlScanner
*/
protected $securityScanner;
public function __construct()
{
$this->readFilter = new DefaultReadFilter();
}
public function getReadDataOnly()
{
return $this->readDataOnly;
}
public function setReadDataOnly($readCellValuesOnly)
{
$this->readDataOnly = (bool) $readCellValuesOnly;
return $this;
}
public function getReadEmptyCells()
{
return $this->readEmptyCells;
}
public function setReadEmptyCells($readEmptyCells)
{
$this->readEmptyCells = (bool) $readEmptyCells;
return $this;
}
public function getIncludeCharts()
{
return $this->includeCharts;
}
public function setIncludeCharts($includeCharts)
{
$this->includeCharts = (bool) $includeCharts;
return $this;
}
public function getLoadSheetsOnly()
{
return $this->loadSheetsOnly;
}
public function setLoadSheetsOnly($sheetList)
{
if ($sheetList === null) {
return $this->setLoadAllSheets();
}
$this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList];
return $this;
}
public function setLoadAllSheets()
{
$this->loadSheetsOnly = null;
return $this;
}
public function getReadFilter()
{
return $this->readFilter;
}
public function setReadFilter(IReadFilter $readFilter)
{
$this->readFilter = $readFilter;
return $this;
}
public function getSecurityScanner(): ?XmlScanner
{
return $this->securityScanner;
}
public function getSecurityScannerOrThrow(): XmlScanner
{
if ($this->securityScanner === null) {
throw new ReaderException('Security scanner is unexpectedly null');
}
return $this->securityScanner;
}
protected function processFlags(int $flags): void
{
if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) {
$this->setIncludeCharts(true);
}
if (((bool) ($flags & self::READ_DATA_ONLY)) === true) {
$this->setReadDataOnly(true);
}
if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) {
$this->setReadEmptyCells(false);
}
}
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method');
}
/**
* Loads Spreadsheet from file.
*
* @param int $flags the optional second parameter flags may be used to identify specific elements
* that should be loaded, but which won't be loaded by default, using these values:
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
*/
public function load(string $filename, int $flags = 0): Spreadsheet
{
$this->processFlags($flags);
try {
return $this->loadSpreadsheetFromFile($filename);
} catch (ReaderException $e) {
throw $e;
}
}
/**
* Open file for reading.
*/
protected function openFile(string $filename): void
{
$fileHandle = false;
if ($filename) {
File::assertFile($filename);
// Open file
$fileHandle = fopen($filename, 'rb');
}
if ($fileHandle === false) {
throw new ReaderException('Could not open file ' . $filename . ' for reading.');
}
$this->fileHandle = $fileHandle;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php 0000644 00000000731 15243417764 0020550 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
class DefaultReadFilter implements IReadFilter
{
/**
* Should this cell be read?
*
* @param string $columnAddress Column address (as a string value like "A", or "IV")
* @param int $row Row number
* @param string $worksheetName Optional worksheet name
*
* @return bool
*/
public function readCell($columnAddress, $row, $worksheetName = '')
{
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php 0000644 00000053444 15243417764 0017004 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\ReferenceHelper;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
use XMLReader;
class Gnumeric extends BaseReader
{
const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets
const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance';
const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0';
const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink';
const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/';
const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0';
const NAMESPACE_OOO = 'http://openoffice.org/2004/office';
/**
* Shared Expressions.
*
* @var array
*/
private $expressions = [];
/**
* Spreadsheet shared across all functions.
*
* @var Spreadsheet
*/
private $spreadsheet;
/** @var ReferenceHelper */
private $referenceHelper;
/** @var array */
public static $mappings = [
'dataType' => [
'10' => DataType::TYPE_NULL,
'20' => DataType::TYPE_BOOL,
'30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel
'40' => DataType::TYPE_NUMERIC, // Float
'50' => DataType::TYPE_ERROR,
'60' => DataType::TYPE_STRING,
//'70': // Cell Range
//'80': // Array
],
];
/**
* Create a new Gnumeric.
*/
public function __construct()
{
parent::__construct();
$this->referenceHelper = ReferenceHelper::getInstance();
$this->securityScanner = XmlScanner::getInstance($this);
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
$data = null;
if (File::testFileNoThrow($filename)) {
$data = $this->gzfileGetContents($filename);
if (strpos($data, self::NAMESPACE_GNM) === false) {
$data = '';
}
}
return !empty($data);
}
private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
{
return $xml->namespaceURI === self::NAMESPACE_GNM
&& $xml->localName === $expectedLocalName
&& $xml->nodeType === XMLReader::ELEMENT;
}
/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
* @param string $filename
*
* @return array
*/
public function listWorksheetNames($filename)
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an invalid Gnumeric file.');
}
$xml = new XMLReader();
$contents = $this->gzfileGetContents($filename);
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);
$worksheetNames = [];
while ($xml->read()) {
if (self::matchXml($xml, 'SheetName')) {
$xml->read(); // Move onto the value node
$worksheetNames[] = (string) $xml->value;
} elseif (self::matchXml($xml, 'Sheets')) {
// break out of the loop once we've got our sheet names rather than parse the entire file
break;
}
}
return $worksheetNames;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an invalid Gnumeric file.');
}
$xml = new XMLReader();
$contents = $this->gzfileGetContents($filename);
$xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);
$worksheetInfo = [];
while ($xml->read()) {
if (self::matchXml($xml, 'Sheet')) {
$tmpInfo = [
'worksheetName' => '',
'lastColumnLetter' => 'A',
'lastColumnIndex' => 0,
'totalRows' => 0,
'totalColumns' => 0,
];
while ($xml->read()) {
if (self::matchXml($xml, 'Name')) {
$xml->read(); // Move onto the value node
$tmpInfo['worksheetName'] = (string) $xml->value;
} elseif (self::matchXml($xml, 'MaxCol')) {
$xml->read(); // Move onto the value node
$tmpInfo['lastColumnIndex'] = (int) $xml->value;
$tmpInfo['totalColumns'] = (int) $xml->value + 1;
} elseif (self::matchXml($xml, 'MaxRow')) {
$xml->read(); // Move onto the value node
$tmpInfo['totalRows'] = (int) $xml->value + 1;
break;
}
}
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
$worksheetInfo[] = $tmpInfo;
}
}
return $worksheetInfo;
}
/**
* @param string $filename
*
* @return string
*/
private function gzfileGetContents($filename)
{
$data = '';
$contents = @file_get_contents($filename);
if ($contents !== false) {
if (substr($contents, 0, 2) === "\x1f\x8b") {
// Check if gzlib functions are available
if (function_exists('gzdecode')) {
$contents = @gzdecode($contents);
if ($contents !== false) {
$data = $contents;
}
}
} else {
$data = $contents;
}
}
if ($data !== '') {
$data = $this->getSecurityScannerOrThrow()->scan($data);
}
return $data;
}
public static function gnumericMappings(): array
{
return array_merge(self::$mappings, Styles::$mappings);
}
private function processComments(SimpleXMLElement $sheet): void
{
if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) {
$commentAttributes = $comment->attributes();
// Only comment objects are handled at the moment
if ($commentAttributes && $commentAttributes->Text) {
$this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)
->setAuthor((string) $commentAttributes->Author)
->setText($this->parseRichText((string) $commentAttributes->Text));
}
}
}
}
/**
* @param mixed $value
*/
private static function testSimpleXml($value): SimpleXMLElement
{
return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
}
/**
* Loads Spreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
$spreadsheet->removeSheetByIndex(0);
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
/**
* Loads from file into Spreadsheet instance.
*/
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
{
$this->spreadsheet = $spreadsheet;
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an invalid Gnumeric file.');
}
$gFileData = $this->gzfileGetContents($filename);
$xml2 = simplexml_load_string($gFileData, 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
$xml = self::testSimpleXml($xml2);
$gnmXML = $xml->children(self::NAMESPACE_GNM);
(new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
$worksheetID = 0;
foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
$sheet = self::testSimpleXml($sheetOrNull);
$worksheetName = (string) $sheet->Name;
if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) {
continue;
}
$maxRow = $maxCol = 0;
// Create new Worksheet
$this->spreadsheet->createSheet();
$this->spreadsheet->setActiveSheetIndex($worksheetID);
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
// cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
// name in line with the formula, not the reverse
$this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
$visibility = $sheet->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE';
if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') {
$this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN);
}
if (!$this->readDataOnly) {
(new PageSetup($this->spreadsheet))
->printInformation($sheet)
->sheetMargins($sheet);
}
foreach ($sheet->Cells->Cell as $cellOrNull) {
$cell = self::testSimpleXml($cellOrNull);
$cellAttributes = self::testSimpleXml($cell->attributes());
$row = (int) $cellAttributes->Row + 1;
$column = (int) $cellAttributes->Col;
$maxRow = max($maxRow, $row);
$maxCol = max($maxCol, $column);
$column = Coordinate::stringFromColumnIndex($column + 1);
// Read cell?
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) {
continue;
}
}
$this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row);
}
if ($sheet->Styles !== null) {
(new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol);
}
$this->processComments($sheet);
$this->processColumnWidths($sheet, $maxCol);
$this->processRowHeights($sheet, $maxRow);
$this->processMergedCells($sheet);
$this->processAutofilter($sheet);
$this->setSelectedCells($sheet);
++$worksheetID;
}
$this->processDefinedNames($gnmXML);
$this->setSelectedSheet($gnmXML);
// Return
return $this->spreadsheet;
}
private function setSelectedSheet(SimpleXMLElement $gnmXML): void
{
if (isset($gnmXML->UIData)) {
$attributes = self::testSimpleXml($gnmXML->UIData->attributes());
$selectedSheet = (int) $attributes['SelectedTab'];
$this->spreadsheet->setActiveSheetIndex($selectedSheet);
}
}
private function setSelectedCells(?SimpleXMLElement $sheet): void
{
if ($sheet !== null && isset($sheet->Selections)) {
foreach ($sheet->Selections as $selection) {
$startCol = (int) ($selection->StartCol ?? 0);
$startRow = (int) ($selection->StartRow ?? 0) + 1;
$endCol = (int) ($selection->EndCol ?? $startCol);
$endRow = (int) ($selection->endRow ?? 0) + 1;
$startColumn = Coordinate::stringFromColumnIndex($startCol + 1);
$endColumn = Coordinate::stringFromColumnIndex($endCol + 1);
$startCell = "{$startColumn}{$startRow}";
$endCell = "{$endColumn}{$endRow}";
$selectedRange = $startCell . (($endCell !== $startCell) ? ':' . $endCell : '');
$this->spreadsheet->getActiveSheet()->setSelectedCell($selectedRange);
break;
}
}
}
private function processMergedCells(?SimpleXMLElement $sheet): void
{
// Handle Merged Cells in this worksheet
if ($sheet !== null && isset($sheet->MergedRegions)) {
foreach ($sheet->MergedRegions->Merge as $mergeCells) {
if (strpos((string) $mergeCells, ':') !== false) {
$this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
}
}
}
private function processAutofilter(?SimpleXMLElement $sheet): void
{
if ($sheet !== null && isset($sheet->Filters)) {
foreach ($sheet->Filters->Filter as $autofilter) {
if ($autofilter !== null) {
$attributes = $autofilter->attributes();
if (isset($attributes['Area'])) {
$this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']);
}
}
}
}
}
private function setColumnWidth(int $whichColumn, float $defaultWidth): void
{
$columnDimension = $this->spreadsheet->getActiveSheet()
->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
if ($columnDimension !== null) {
$columnDimension->setWidth($defaultWidth);
}
}
private function setColumnInvisible(int $whichColumn): void
{
$columnDimension = $this->spreadsheet->getActiveSheet()
->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
if ($columnDimension !== null) {
$columnDimension->setVisible(false);
}
}
private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int
{
$columnOverride = self::testSimpleXml($columnOverride);
$columnAttributes = self::testSimpleXml($columnOverride->attributes());
$column = $columnAttributes['No'];
$columnWidth = ((float) $columnAttributes['Unit']) / 5.4;
$hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1');
$columnCount = (int) ($columnAttributes['Count'] ?? 1);
while ($whichColumn < $column) {
$this->setColumnWidth($whichColumn, $defaultWidth);
++$whichColumn;
}
while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) {
$this->setColumnWidth($whichColumn, $columnWidth);
if ($hidden) {
$this->setColumnInvisible($whichColumn);
}
++$whichColumn;
}
return $whichColumn;
}
private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void
{
if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) {
// Column Widths
$defaultWidth = 0;
$columnAttributes = $sheet->Cols->attributes();
if ($columnAttributes !== null) {
$defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
}
$whichColumn = 0;
foreach ($sheet->Cols->ColInfo as $columnOverride) {
$whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth);
}
while ($whichColumn <= $maxCol) {
$this->setColumnWidth($whichColumn, $defaultWidth);
++$whichColumn;
}
}
}
private function setRowHeight(int $whichRow, float $defaultHeight): void
{
$rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
if ($rowDimension !== null) {
$rowDimension->setRowHeight($defaultHeight);
}
}
private function setRowInvisible(int $whichRow): void
{
$rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
if ($rowDimension !== null) {
$rowDimension->setVisible(false);
}
}
private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int
{
$rowOverride = self::testSimpleXml($rowOverride);
$rowAttributes = self::testSimpleXml($rowOverride->attributes());
$row = $rowAttributes['No'];
$rowHeight = (float) $rowAttributes['Unit'];
$hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1');
$rowCount = (int) ($rowAttributes['Count'] ?? 1);
while ($whichRow < $row) {
++$whichRow;
$this->setRowHeight($whichRow, $defaultHeight);
}
while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) {
++$whichRow;
$this->setRowHeight($whichRow, $rowHeight);
if ($hidden) {
$this->setRowInvisible($whichRow);
}
}
return $whichRow;
}
private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void
{
if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) {
// Row Heights
$defaultHeight = 0;
$rowAttributes = $sheet->Rows->attributes();
if ($rowAttributes !== null) {
$defaultHeight = (float) $rowAttributes['DefaultSizePts'];
}
$whichRow = 0;
foreach ($sheet->Rows->RowInfo as $rowOverride) {
$whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight);
}
// never executed, I can't figure out any circumstances
// under which it would be executed, and, even if
// such exist, I'm not convinced this is needed.
//while ($whichRow < $maxRow) {
// ++$whichRow;
// $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight);
//}
}
}
private function processDefinedNames(?SimpleXMLElement $gnmXML): void
{
// Loop through definedNames (global named ranges)
if ($gnmXML !== null && isset($gnmXML->Names)) {
foreach ($gnmXML->Names->Name as $definedName) {
$name = (string) $definedName->name;
$value = (string) $definedName->value;
if (stripos($value, '#REF!') !== false) {
continue;
}
[$worksheetName] = Worksheet::extractSheetTitle($value, true);
$worksheetName = trim($worksheetName, "'");
$worksheet = $this->spreadsheet->getSheetByName($worksheetName);
// Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
if ($worksheet !== null) {
$this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value));
}
}
}
}
private function parseRichText(string $is): RichText
{
$value = new RichText();
$value->createText($is);
return $value;
}
private function loadCell(
SimpleXMLElement $cell,
string $worksheetName,
SimpleXMLElement $cellAttributes,
string $column,
int $row
): void {
$ValueType = $cellAttributes->ValueType;
$ExprID = (string) $cellAttributes->ExprID;
$type = DataType::TYPE_FORMULA;
if ($ExprID > '') {
if (((string) $cell) > '') {
$this->expressions[$ExprID] = [
'column' => $cellAttributes->Col,
'row' => $cellAttributes->Row,
'formula' => (string) $cell,
];
} else {
$expression = $this->expressions[$ExprID];
$cell = $this->referenceHelper->updateFormulaReferences(
$expression['formula'],
'A1',
$cellAttributes->Col - $expression['column'],
$cellAttributes->Row - $expression['row'],
$worksheetName
);
}
$type = DataType::TYPE_FORMULA;
} else {
$vtype = (string) $ValueType;
if (array_key_exists($vtype, self::$mappings['dataType'])) {
$type = self::$mappings['dataType'][$vtype];
}
if ($vtype === '20') { // Boolean
$cell = $cell == 'TRUE';
}
}
$this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
if (isset($cellAttributes->ValueFormat)) {
$this->spreadsheet->getActiveSheet()->getCell($column . $row)
->getStyle()->getNumberFormat()
->setFormatCode((string) $cellAttributes->ValueFormat);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php 0000644 00000010554 15243417764 0021107 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Security;
use PhpOffice\PhpSpreadsheet\Reader;
class XmlScanner
{
/**
* String used to identify risky xml elements.
*
* @var string
*/
private $pattern;
/** @var ?callable */
private $callback;
/** @var ?bool */
private static $libxmlDisableEntityLoaderValue;
/**
* @var bool
*/
private static $shutdownRegistered = false;
public function __construct(string $pattern = '<!DOCTYPE')
{
$this->pattern = $pattern;
$this->disableEntityLoaderCheck();
// A fatal error will bypass the destructor, so we register a shutdown here
if (!self::$shutdownRegistered) {
self::$shutdownRegistered = true;
register_shutdown_function([__CLASS__, 'shutdown']);
}
}
public static function getInstance(Reader\IReader $reader): self
{
$pattern = ($reader instanceof Reader\Html) ? '<!ENTITY' : '<!DOCTYPE';
return new self($pattern);
}
/**
* @codeCoverageIgnore
*/
public static function threadSafeLibxmlDisableEntityLoaderAvailability(): bool
{
if (PHP_MAJOR_VERSION === 7) {
switch (PHP_MINOR_VERSION) {
case 2:
return PHP_RELEASE_VERSION >= 1;
case 1:
return PHP_RELEASE_VERSION >= 13;
case 0:
return PHP_RELEASE_VERSION >= 27;
}
return true;
}
return false;
}
/**
* @codeCoverageIgnore
*/
private function disableEntityLoaderCheck(): void
{
if (\PHP_VERSION_ID < 80000) {
$libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true);
if (self::$libxmlDisableEntityLoaderValue === null) {
self::$libxmlDisableEntityLoaderValue = $libxmlDisableEntityLoaderValue;
}
}
}
/**
* @codeCoverageIgnore
*/
public static function shutdown(): void
{
if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue);
self::$libxmlDisableEntityLoaderValue = null;
}
}
public function __destruct()
{
self::shutdown();
}
public function setAdditionalCallback(callable $callback): void
{
$this->callback = $callback;
}
/** @param mixed $arg */
private static function forceString($arg): string
{
return is_string($arg) ? $arg : '';
}
/**
* @param string $xml
*
* @return string
*/
private function toUtf8($xml)
{
$pattern = '/encoding="(.*?)"/';
$result = preg_match($pattern, $xml, $matches);
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
if ($charset !== 'UTF-8') {
$xml = self::forceString(mb_convert_encoding($xml, 'UTF-8', $charset));
$result = preg_match($pattern, $xml, $matches);
$charset = strtoupper($result ? $matches[1] : 'UTF-8');
if ($charset !== 'UTF-8') {
throw new Reader\Exception('Suspicious Double-encoded XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
}
}
return $xml;
}
/**
* Scan the XML for use of <!ENTITY to prevent XXE/XEE attacks.
*
* @param false|string $xml
*
* @return string
*/
public function scan($xml)
{
$xml = "$xml";
$this->disableEntityLoaderCheck();
$xml = $this->toUtf8($xml);
// Don't rely purely on libxml_disable_entity_loader()
$pattern = '/\\0?' . implode('\\0?', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\\0?/';
if (preg_match($pattern, $xml)) {
throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
}
if ($this->callback !== null) {
$xml = call_user_func($this->callback, $xml);
}
return $xml;
}
/**
* Scan theXML for use of <!ENTITY to prevent XXE/XEE attacks.
*
* @param string $filestream
*
* @return string
*/
public function scanFile($filestream)
{
return $this->scan(file_get_contents($filestream));
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php 0000644 00000001013 15243417764 0017646 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class ErrorCode
{
private const ERROR_CODE_MAP = [
0x00 => '#NULL!',
0x07 => '#DIV/0!',
0x0F => '#VALUE!',
0x17 => '#REF!',
0x1D => '#NAME?',
0x24 => '#NUM!',
0x2A => '#N/A',
];
/**
* Map error code, e.g. '#N/A'.
*
* @param int $code
*
* @return bool|string
*/
public static function lookup($code)
{
return self::ERROR_CODE_MAP[$code] ?? false;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php 0000644 00000003602 15243417764 0022014 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
class DataValidationHelper
{
/**
* @var array<int, string>
*/
private static $types = [
0x00 => DataValidation::TYPE_NONE,
0x01 => DataValidation::TYPE_WHOLE,
0x02 => DataValidation::TYPE_DECIMAL,
0x03 => DataValidation::TYPE_LIST,
0x04 => DataValidation::TYPE_DATE,
0x05 => DataValidation::TYPE_TIME,
0x06 => DataValidation::TYPE_TEXTLENGTH,
0x07 => DataValidation::TYPE_CUSTOM,
];
/**
* @var array<int, string>
*/
private static $errorStyles = [
0x00 => DataValidation::STYLE_STOP,
0x01 => DataValidation::STYLE_WARNING,
0x02 => DataValidation::STYLE_INFORMATION,
];
/**
* @var array<int, string>
*/
private static $operators = [
0x00 => DataValidation::OPERATOR_BETWEEN,
0x01 => DataValidation::OPERATOR_NOTBETWEEN,
0x02 => DataValidation::OPERATOR_EQUAL,
0x03 => DataValidation::OPERATOR_NOTEQUAL,
0x04 => DataValidation::OPERATOR_GREATERTHAN,
0x05 => DataValidation::OPERATOR_LESSTHAN,
0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL,
0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL,
];
public static function type(int $type): ?string
{
if (isset(self::$types[$type])) {
return self::$types[$type];
}
return null;
}
public static function errorStyle(int $errorStyle): ?string
{
if (isset(self::$errorStyles[$errorStyle])) {
return self::$errorStyles[$errorStyle];
}
return null;
}
public static function operator(int $operator): ?string
{
if (isset(self::$operators[$operator])) {
return self::$operators[$operator];
}
return null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php 0000644 00000045726 15243417764 0017216 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
class Escher
{
const DGGCONTAINER = 0xF000;
const BSTORECONTAINER = 0xF001;
const DGCONTAINER = 0xF002;
const SPGRCONTAINER = 0xF003;
const SPCONTAINER = 0xF004;
const DGG = 0xF006;
const BSE = 0xF007;
const DG = 0xF008;
const SPGR = 0xF009;
const SP = 0xF00A;
const OPT = 0xF00B;
const CLIENTTEXTBOX = 0xF00D;
const CLIENTANCHOR = 0xF010;
const CLIENTDATA = 0xF011;
const BLIPJPEG = 0xF01D;
const BLIPPNG = 0xF01E;
const SPLITMENUCOLORS = 0xF11E;
const TERTIARYOPT = 0xF122;
/**
* Escher stream data (binary).
*
* @var string
*/
private $data;
/**
* Size in bytes of the Escher stream data.
*
* @var int
*/
private $dataSize;
/**
* Current position of stream pointer in Escher stream data.
*
* @var int
*/
private $pos;
/**
* The object to be returned by the reader. Modified during load.
*
* @var BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
*/
private $object;
/**
* Create a new Escher instance.
*
* @param mixed $object
*/
public function __construct($object)
{
$this->object = $object;
}
private const WHICH_ROUTINE = [
self::DGGCONTAINER => 'readDggContainer',
self::DGG => 'readDgg',
self::BSTORECONTAINER => 'readBstoreContainer',
self::BSE => 'readBSE',
self::BLIPJPEG => 'readBlipJPEG',
self::BLIPPNG => 'readBlipPNG',
self::OPT => 'readOPT',
self::TERTIARYOPT => 'readTertiaryOPT',
self::SPLITMENUCOLORS => 'readSplitMenuColors',
self::DGCONTAINER => 'readDgContainer',
self::DG => 'readDg',
self::SPGRCONTAINER => 'readSpgrContainer',
self::SPCONTAINER => 'readSpContainer',
self::SPGR => 'readSpgr',
self::SP => 'readSp',
self::CLIENTTEXTBOX => 'readClientTextbox',
self::CLIENTANCHOR => 'readClientAnchor',
self::CLIENTDATA => 'readClientData',
];
/**
* Load Escher stream data. May be a partial Escher stream.
*
* @param string $data
*
* @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
*/
public function load($data)
{
$this->data = $data;
// total byte size of Excel data (workbook global substream + sheet substreams)
$this->dataSize = strlen($this->data);
$this->pos = 0;
// Parse Escher stream
while ($this->pos < $this->dataSize) {
// offset: 2; size: 2: Record Type
$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
$routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault';
if (method_exists($this, $routine)) {
$this->$routine();
}
}
return $this->object;
}
/**
* Read a generic record.
*/
private function readDefault(): void
{
// offset 0; size: 2; recVer and recInstance
//$verInstance = Xls::getUInt2d($this->data, $this->pos);
// offset: 2; size: 2: Record Type
//$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
// bit: 0-3; mask: 0x000F; recVer
//$recVer = (0x000F & $verInstance) >> 0;
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read DggContainer record (Drawing Group Container).
*/
private function readDggContainer(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// record is a container, read contents
$dggContainer = new DggContainer();
$this->applyAttribute('setDggContainer', $dggContainer);
$reader = new self($dggContainer);
$reader->load($recordData);
}
/**
* Read Dgg record (Drawing Group).
*/
private function readDgg(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read BstoreContainer record (Blip Store Container).
*/
private function readBstoreContainer(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// record is a container, read contents
$bstoreContainer = new BstoreContainer();
$this->applyAttribute('setBstoreContainer', $bstoreContainer);
$reader = new self($bstoreContainer);
$reader->load($recordData);
}
/**
* Read BSE record.
*/
private function readBSE(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// add BSE to BstoreContainer
$BSE = new BSE();
$this->applyAttribute('addBSE', $BSE);
$BSE->setBLIPType($recInstance);
// offset: 0; size: 1; btWin32 (MSOBLIPTYPE)
//$btWin32 = ord($recordData[0]);
// offset: 1; size: 1; btWin32 (MSOBLIPTYPE)
//$btMacOS = ord($recordData[1]);
// offset: 2; size: 16; MD4 digest
//$rgbUid = substr($recordData, 2, 16);
// offset: 18; size: 2; tag
//$tag = Xls::getUInt2d($recordData, 18);
// offset: 20; size: 4; size of BLIP in bytes
//$size = Xls::getInt4d($recordData, 20);
// offset: 24; size: 4; number of references to this BLIP
//$cRef = Xls::getInt4d($recordData, 24);
// offset: 28; size: 4; MSOFO file offset
//$foDelay = Xls::getInt4d($recordData, 28);
// offset: 32; size: 1; unused1
//$unused1 = ord($recordData[32]);
// offset: 33; size: 1; size of nameData in bytes (including null terminator)
$cbName = ord($recordData[33]);
// offset: 34; size: 1; unused2
//$unused2 = ord($recordData[34]);
// offset: 35; size: 1; unused3
//$unused3 = ord($recordData[35]);
// offset: 36; size: $cbName; nameData
//$nameData = substr($recordData, 36, $cbName);
// offset: 36 + $cbName, size: var; the BLIP data
$blipData = substr($recordData, 36 + $cbName);
// record is a container, read contents
$reader = new self($BSE);
$reader->load($blipData);
}
/**
* Read BlipJPEG record. Holds raw JPEG image data.
*/
private function readBlipJPEG(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
$pos = 0;
// offset: 0; size: 16; rgbUid1 (MD4 digest of)
//$rgbUid1 = substr($recordData, 0, 16);
$pos += 16;
// offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
if (in_array($recInstance, [0x046B, 0x06E3])) {
//$rgbUid2 = substr($recordData, 16, 16);
$pos += 16;
}
// offset: var; size: 1; tag
//$tag = ord($recordData[$pos]);
++$pos;
// offset: var; size: var; the raw image data
$data = substr($recordData, $pos);
$blip = new Blip();
$blip->setData($data);
$this->applyAttribute('setBlip', $blip);
}
/**
* Read BlipPNG record. Holds raw PNG image data.
*/
private function readBlipPNG(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
$pos = 0;
// offset: 0; size: 16; rgbUid1 (MD4 digest of)
//$rgbUid1 = substr($recordData, 0, 16);
$pos += 16;
// offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
if ($recInstance == 0x06E1) {
//$rgbUid2 = substr($recordData, 16, 16);
$pos += 16;
}
// offset: var; size: 1; tag
//$tag = ord($recordData[$pos]);
++$pos;
// offset: var; size: var; the raw image data
$data = substr($recordData, $pos);
$blip = new Blip();
$blip->setData($data);
$this->applyAttribute('setBlip', $blip);
}
/**
* Read OPT record. This record may occur within DggContainer record or SpContainer.
*/
private function readOPT(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
$this->readOfficeArtRGFOPTE($recordData, $recInstance);
}
/**
* Read TertiaryOPT record.
*/
private function readTertiaryOPT(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read SplitMenuColors record.
*/
private function readSplitMenuColors(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read DgContainer record (Drawing Container).
*/
private function readDgContainer(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// record is a container, read contents
$dgContainer = new DgContainer();
$this->applyAttribute('setDgContainer', $dgContainer);
$reader = new self($dgContainer);
$reader->load($recordData);
}
/**
* Read Dg record (Drawing).
*/
private function readDg(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read SpgrContainer record (Shape Group Container).
*/
private function readSpgrContainer(): void
{
// context is either context DgContainer or SpgrContainer
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// record is a container, read contents
$spgrContainer = new SpgrContainer();
if ($this->object instanceof DgContainer) {
// DgContainer
$this->object->setSpgrContainer($spgrContainer);
} elseif ($this->object instanceof SpgrContainer) {
// SpgrContainer
$this->object->addChild($spgrContainer);
}
$reader = new self($spgrContainer);
$reader->load($recordData);
}
/**
* Read SpContainer record (Shape Container).
*/
private function readSpContainer(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// add spContainer to spgrContainer
$spContainer = new SpContainer();
$this->applyAttribute('addChild', $spContainer);
// move stream pointer to next record
$this->pos += 8 + $length;
// record is a container, read contents
$reader = new self($spContainer);
$reader->load($recordData);
}
/**
* Read Spgr record (Shape Group).
*/
private function readSpgr(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read Sp record (Shape).
*/
private function readSp(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read ClientTextbox record.
*/
private function readClientTextbox(): void
{
// offset: 0; size: 2; recVer and recInstance
// bit: 4-15; mask: 0xFFF0; recInstance
//$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet.
*/
private function readClientAnchor(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
// offset: 2; size: 2; upper-left corner column index (0-based)
$c1 = Xls::getUInt2d($recordData, 2);
// offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width
$startOffsetX = Xls::getUInt2d($recordData, 4);
// offset: 6; size: 2; upper-left corner row index (0-based)
$r1 = Xls::getUInt2d($recordData, 6);
// offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height
$startOffsetY = Xls::getUInt2d($recordData, 8);
// offset: 10; size: 2; bottom-right corner column index (0-based)
$c2 = Xls::getUInt2d($recordData, 10);
// offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width
$endOffsetX = Xls::getUInt2d($recordData, 12);
// offset: 14; size: 2; bottom-right corner row index (0-based)
$r2 = Xls::getUInt2d($recordData, 14);
// offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height
$endOffsetY = Xls::getUInt2d($recordData, 16);
$this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));
$this->applyAttribute('setStartOffsetX', $startOffsetX);
$this->applyAttribute('setStartOffsetY', $startOffsetY);
$this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));
$this->applyAttribute('setEndOffsetX', $endOffsetX);
$this->applyAttribute('setEndOffsetY', $endOffsetY);
}
/**
* @param mixed $value
*/
private function applyAttribute(string $name, $value): void
{
if (method_exists($this->object, $name)) {
$this->object->$name($value);
}
}
/**
* Read ClientData record.
*/
private function readClientData(): void
{
$length = Xls::getInt4d($this->data, $this->pos + 4);
//$recordData = substr($this->data, $this->pos + 8, $length);
// move stream pointer to next record
$this->pos += 8 + $length;
}
/**
* Read OfficeArtRGFOPTE table of property-value pairs.
*
* @param string $data Binary data
* @param int $n Number of properties
*/
private function readOfficeArtRGFOPTE($data, $n): void
{
$splicedComplexData = substr($data, 6 * $n);
// loop through property-value pairs
for ($i = 0; $i < $n; ++$i) {
// read 6 bytes at a time
$fopte = substr($data, 6 * $i, 6);
// offset: 0; size: 2; opid
$opid = Xls::getUInt2d($fopte, 0);
// bit: 0-13; mask: 0x3FFF; opid.opid
$opidOpid = (0x3FFF & $opid) >> 0;
// bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier
//$opidFBid = (0x4000 & $opid) >> 14;
// bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data
$opidFComplex = (0x8000 & $opid) >> 15;
// offset: 2; size: 4; the value for this property
$op = Xls::getInt4d($fopte, 2);
if ($opidFComplex) {
$complexData = substr($splicedComplexData, 0, $op);
$splicedComplexData = substr($splicedComplexData, $op);
// we store string value with complex data
$value = $complexData;
} else {
// we store integer value
$value = $op;
}
if (method_exists($this->object, 'setOPT')) {
$this->object->setOPT($opidOpid, $value);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php 0000644 00000001300 15243417764 0020405 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BuiltIn
{
private const BUILTIN_COLOR_MAP = [
0x00 => '000000',
0x01 => 'FFFFFF',
0x02 => 'FF0000',
0x03 => '00FF00',
0x04 => '0000FF',
0x05 => 'FFFF00',
0x06 => 'FF00FF',
0x07 => '00FFFF',
0x40 => '000000', // system window text color
0x41 => 'FFFFFF', // system window background color
];
/**
* Map built-in color to RGB value.
*
* @param int $color Indexed color
*
* @return array
*/
public static function lookup($color)
{
return ['rgb' => self::BUILTIN_COLOR_MAP[$color] ?? '000000'];
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php 0000644 00000003452 15243417764 0017647 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BIFF8
{
private const BIFF8_COLOR_MAP = [
0x08 => '000000',
0x09 => 'FFFFFF',
0x0A => 'FF0000',
0x0B => '00FF00',
0x0C => '0000FF',
0x0D => 'FFFF00',
0x0E => 'FF00FF',
0x0F => '00FFFF',
0x10 => '800000',
0x11 => '008000',
0x12 => '000080',
0x13 => '808000',
0x14 => '800080',
0x15 => '008080',
0x16 => 'C0C0C0',
0x17 => '808080',
0x18 => '9999FF',
0x19 => '993366',
0x1A => 'FFFFCC',
0x1B => 'CCFFFF',
0x1C => '660066',
0x1D => 'FF8080',
0x1E => '0066CC',
0x1F => 'CCCCFF',
0x20 => '000080',
0x21 => 'FF00FF',
0x22 => 'FFFF00',
0x23 => '00FFFF',
0x24 => '800080',
0x25 => '800000',
0x26 => '008080',
0x27 => '0000FF',
0x28 => '00CCFF',
0x29 => 'CCFFFF',
0x2A => 'CCFFCC',
0x2B => 'FFFF99',
0x2C => '99CCFF',
0x2D => 'FF99CC',
0x2E => 'CC99FF',
0x2F => 'FFCC99',
0x30 => '3366FF',
0x31 => '33CCCC',
0x32 => '99CC00',
0x33 => 'FFCC00',
0x34 => 'FF9900',
0x35 => 'FF6600',
0x36 => '666699',
0x37 => '969696',
0x38 => '003366',
0x39 => '339966',
0x3A => '003300',
0x3B => '333300',
0x3C => '993300',
0x3D => '993366',
0x3E => '333399',
0x3F => '333333',
];
/**
* Map color array from BIFF8 built-in color index.
*
* @param int $color
*
* @return array
*/
public static function lookup($color)
{
return ['rgb' => self::BIFF8_COLOR_MAP[$color] ?? '000000'];
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php 0000644 00000003452 15243417764 0017644 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BIFF5
{
private const BIFF5_COLOR_MAP = [
0x08 => '000000',
0x09 => 'FFFFFF',
0x0A => 'FF0000',
0x0B => '00FF00',
0x0C => '0000FF',
0x0D => 'FFFF00',
0x0E => 'FF00FF',
0x0F => '00FFFF',
0x10 => '800000',
0x11 => '008000',
0x12 => '000080',
0x13 => '808000',
0x14 => '800080',
0x15 => '008080',
0x16 => 'C0C0C0',
0x17 => '808080',
0x18 => '8080FF',
0x19 => '802060',
0x1A => 'FFFFC0',
0x1B => 'A0E0F0',
0x1C => '600080',
0x1D => 'FF8080',
0x1E => '0080C0',
0x1F => 'C0C0FF',
0x20 => '000080',
0x21 => 'FF00FF',
0x22 => 'FFFF00',
0x23 => '00FFFF',
0x24 => '800080',
0x25 => '800000',
0x26 => '008080',
0x27 => '0000FF',
0x28 => '00CFFF',
0x29 => '69FFFF',
0x2A => 'E0FFE0',
0x2B => 'FFFF80',
0x2C => 'A6CAF0',
0x2D => 'DD9CB3',
0x2E => 'B38FEE',
0x2F => 'E3E3E3',
0x30 => '2A6FF9',
0x31 => '3FB8CD',
0x32 => '488436',
0x33 => '958C41',
0x34 => '8E5E42',
0x35 => 'A0627A',
0x36 => '624FAC',
0x37 => '969696',
0x38 => '1D2FBE',
0x39 => '286676',
0x3A => '004500',
0x3B => '453E01',
0x3C => '6A2813',
0x3D => '85396A',
0x3E => '4A3285',
0x3F => '424242',
];
/**
* Map color array from BIFF5 built-in color index.
*
* @param int $color
*
* @return array
*/
public static function lookup($color)
{
return ['rgb' => self::BIFF5_COLOR_MAP[$color] ?? '000000'];
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php 0000644 00000016665 15243417764 0016372 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class MD5
{
/**
* @var int
*/
private $a;
/**
* @var int
*/
private $b;
/**
* @var int
*/
private $c;
/**
* @var int
*/
private $d;
/**
* @var int
*/
private static $allOneBits;
/**
* MD5 stream constructor.
*/
public function __construct()
{
self::$allOneBits = self::signedInt(0xffffffff);
$this->reset();
}
/**
* Reset the MD5 stream context.
*/
public function reset(): void
{
$this->a = 0x67452301;
$this->b = self::signedInt(0xEFCDAB89);
$this->c = self::signedInt(0x98BADCFE);
$this->d = 0x10325476;
}
/**
* Get MD5 stream context.
*
* @return string
*/
public function getContext()
{
$s = '';
foreach (['a', 'b', 'c', 'd'] as $i) {
$v = $this->{$i};
$s .= chr($v & 0xff);
$s .= chr(($v >> 8) & 0xff);
$s .= chr(($v >> 16) & 0xff);
$s .= chr(($v >> 24) & 0xff);
}
return $s;
}
/**
* Add data to context.
*
* @param string $data Data to add
*/
public function add(string $data): void
{
// @phpstan-ignore-next-line
$words = array_values(unpack('V16', $data));
$A = $this->a;
$B = $this->b;
$C = $this->c;
$D = $this->d;
$F = [self::class, 'f'];
$G = [self::class, 'g'];
$H = [self::class, 'h'];
$I = [self::class, 'i'];
// ROUND 1
self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478);
self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756);
self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db);
self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee);
self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf);
self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a);
self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613);
self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501);
self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8);
self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af);
self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1);
self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be);
self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122);
self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193);
self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e);
self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821);
// ROUND 2
self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562);
self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340);
self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51);
self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa);
self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d);
self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453);
self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681);
self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8);
self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6);
self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6);
self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87);
self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed);
self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905);
self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8);
self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9);
self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a);
// ROUND 3
self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942);
self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681);
self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122);
self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c);
self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44);
self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9);
self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60);
self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70);
self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6);
self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa);
self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085);
self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05);
self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039);
self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5);
self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8);
self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665);
// ROUND 4
self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244);
self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97);
self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7);
self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039);
self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3);
self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92);
self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d);
self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1);
self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f);
self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0);
self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314);
self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1);
self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82);
self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235);
self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb);
self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391);
$this->a = ($this->a + $A) & self::$allOneBits;
$this->b = ($this->b + $B) & self::$allOneBits;
$this->c = ($this->c + $C) & self::$allOneBits;
$this->d = ($this->d + $D) & self::$allOneBits;
}
private static function f(int $X, int $Y, int $Z): int
{
return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z
}
private static function g(int $X, int $Y, int $Z): int
{
return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z
}
private static function h(int $X, int $Y, int $Z): int
{
return $X ^ $Y ^ $Z; // X XOR Y XOR Z
}
private static function i(int $X, int $Y, int $Z): int
{
return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z)
}
/** @param float|int $t may be float on 32-bit system */
private static function step(callable $func, int &$A, int $B, int $C, int $D, int $M, int $s, $t): void
{
$t = self::signedInt($t);
$A = (int) ($A + call_user_func($func, $B, $C, $D) + $M + $t) & self::$allOneBits;
$A = self::rotate($A, $s);
$A = (int) ($B + $A) & self::$allOneBits;
}
/** @param float|int $result may be float on 32-bit system */
private static function signedInt($result): int
{
return is_int($result) ? $result : (int) (PHP_INT_MIN + $result - 1 - PHP_INT_MAX);
}
private static function rotate(int $decimal, int $bits): int
{
$binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT);
return self::signedInt(bindec(substr($binary, $bits) . substr($binary, 0, $bits)));
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php 0000644 00000002633 15243417764 0021317 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Fill;
class FillPattern
{
/**
* @var array<int, string>
*/
protected static $fillPatternMap = [
0x00 => Fill::FILL_NONE,
0x01 => Fill::FILL_SOLID,
0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
0x03 => Fill::FILL_PATTERN_DARKGRAY,
0x04 => Fill::FILL_PATTERN_LIGHTGRAY,
0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL,
0x06 => Fill::FILL_PATTERN_DARKVERTICAL,
0x07 => Fill::FILL_PATTERN_DARKDOWN,
0x08 => Fill::FILL_PATTERN_DARKUP,
0x09 => Fill::FILL_PATTERN_DARKGRID,
0x0A => Fill::FILL_PATTERN_DARKTRELLIS,
0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL,
0x0D => Fill::FILL_PATTERN_LIGHTDOWN,
0x0E => Fill::FILL_PATTERN_LIGHTUP,
0x0F => Fill::FILL_PATTERN_LIGHTGRID,
0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS,
0x11 => Fill::FILL_PATTERN_GRAY125,
0x12 => Fill::FILL_PATTERN_GRAY0625,
];
/**
* Get fill pattern from index
* OpenOffice documentation: 2.5.12.
*
* @param int $index
*
* @return string
*/
public static function lookup($index)
{
if (isset(self::$fillPatternMap[$index])) {
return self::$fillPatternMap[$index];
}
return Fill::FILL_NONE;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellFont.php 0000644 00000001645 15243417764 0020603 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Font;
class CellFont
{
public static function escapement(Font $font, int $escapement): void
{
switch ($escapement) {
case 0x0001:
$font->setSuperscript(true);
break;
case 0x0002:
$font->setSubscript(true);
break;
}
}
/**
* @var array<int, string>
*/
protected static $underlineMap = [
0x01 => Font::UNDERLINE_SINGLE,
0x02 => Font::UNDERLINE_DOUBLE,
0x21 => Font::UNDERLINE_SINGLEACCOUNTING,
0x22 => Font::UNDERLINE_DOUBLEACCOUNTING,
];
public static function underline(Font $font, int $underline): void
{
if (array_key_exists($underline, self::$underlineMap)) {
$font->setUnderline(self::$underlineMap[$underline]);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php 0000644 00000002657 15243417764 0021617 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
class CellAlignment
{
/**
* @var array<int, string>
*/
protected static $horizontalAlignmentMap = [
0 => Alignment::HORIZONTAL_GENERAL,
1 => Alignment::HORIZONTAL_LEFT,
2 => Alignment::HORIZONTAL_CENTER,
3 => Alignment::HORIZONTAL_RIGHT,
4 => Alignment::HORIZONTAL_FILL,
5 => Alignment::HORIZONTAL_JUSTIFY,
6 => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
];
/**
* @var array<int, string>
*/
protected static $verticalAlignmentMap = [
0 => Alignment::VERTICAL_TOP,
1 => Alignment::VERTICAL_CENTER,
2 => Alignment::VERTICAL_BOTTOM,
3 => Alignment::VERTICAL_JUSTIFY,
];
public static function horizontal(Alignment $alignment, int $horizontal): void
{
if (array_key_exists($horizontal, self::$horizontalAlignmentMap)) {
$alignment->setHorizontal(self::$horizontalAlignmentMap[$horizontal]);
}
}
public static function vertical(Alignment $alignment, int $vertical): void
{
if (array_key_exists($vertical, self::$verticalAlignmentMap)) {
$alignment->setVertical(self::$verticalAlignmentMap[$vertical]);
}
}
public static function wrap(Alignment $alignment, int $wrap): void
{
$alignment->setWrapText((bool) $wrap);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php 0000644 00000002110 15243417764 0020276 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Border as StyleBorder;
class Border
{
/**
* @var array<int, string>
*/
protected static $borderStyleMap = [
0x00 => StyleBorder::BORDER_NONE,
0x01 => StyleBorder::BORDER_THIN,
0x02 => StyleBorder::BORDER_MEDIUM,
0x03 => StyleBorder::BORDER_DASHED,
0x04 => StyleBorder::BORDER_DOTTED,
0x05 => StyleBorder::BORDER_THICK,
0x06 => StyleBorder::BORDER_DOUBLE,
0x07 => StyleBorder::BORDER_HAIR,
0x08 => StyleBorder::BORDER_MEDIUMDASHED,
0x09 => StyleBorder::BORDER_DASHDOT,
0x0A => StyleBorder::BORDER_MEDIUMDASHDOT,
0x0B => StyleBorder::BORDER_DASHDOTDOT,
0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT,
0x0D => StyleBorder::BORDER_SLANTDASHDOT,
];
public static function lookup(int $index): string
{
if (isset(self::$borderStyleMap[$index])) {
return self::$borderStyleMap[$index];
}
return StyleBorder::BORDER_NONE;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php 0000644 00000002323 15243417764 0022265 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
class ConditionalFormatting
{
/**
* @var array<int, string>
*/
private static $types = [
0x01 => Conditional::CONDITION_CELLIS,
0x02 => Conditional::CONDITION_EXPRESSION,
];
/**
* @var array<int, string>
*/
private static $operators = [
0x00 => Conditional::OPERATOR_NONE,
0x01 => Conditional::OPERATOR_BETWEEN,
0x02 => Conditional::OPERATOR_NOTBETWEEN,
0x03 => Conditional::OPERATOR_EQUAL,
0x04 => Conditional::OPERATOR_NOTEQUAL,
0x05 => Conditional::OPERATOR_GREATERTHAN,
0x06 => Conditional::OPERATOR_LESSTHAN,
0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL,
0x08 => Conditional::OPERATOR_LESSTHANOREQUAL,
];
public static function type(int $type): ?string
{
if (isset(self::$types[$type])) {
return self::$types[$type];
}
return null;
}
public static function operator(int $operator): ?string
{
if (isset(self::$operators[$operator])) {
return self::$operators[$operator];
}
return null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php 0000644 00000002774 15243417764 0016371 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class RC4
{
/** @var int[] */
protected $s = []; // Context
/** @var int */
protected $i = 0;
/** @var int */
protected $j = 0;
/**
* RC4 stream decryption/encryption constrcutor.
*
* @param string $key Encryption key/passphrase
*/
public function __construct($key)
{
$len = strlen($key);
for ($this->i = 0; $this->i < 256; ++$this->i) {
$this->s[$this->i] = $this->i;
}
$this->j = 0;
for ($this->i = 0; $this->i < 256; ++$this->i) {
$this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256;
$t = $this->s[$this->i];
$this->s[$this->i] = $this->s[$this->j];
$this->s[$this->j] = $t;
}
$this->i = $this->j = 0;
}
/**
* Symmetric decryption/encryption function.
*
* @param string $data Data to encrypt/decrypt
*
* @return string
*/
public function RC4($data)
{
$len = strlen($data);
for ($c = 0; $c < $len; ++$c) {
$this->i = ($this->i + 1) % 256;
$this->j = ($this->j + $this->s[$this->i]) % 256;
$t = $this->s[$this->i];
$this->s[$this->i] = $this->s[$this->j];
$this->s[$this->j] = $t;
$t = ($this->s[$this->i] + $this->s[$this->j]) % 256;
$data[$c] = chr(ord($data[$c]) ^ $this->s[$t]);
}
return $data;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php 0000644 00000001616 15243417764 0017051 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
class Color
{
/**
* Read color.
*
* @param int $color Indexed color
* @param array $palette Color palette
* @param int $version
*
* @return array RGB color value, example: ['rgb' => 'FF0000']
*/
public static function map($color, $palette, $version)
{
if ($color <= 0x07 || $color >= 0x40) {
// special built-in color
return Color\BuiltIn::lookup($color);
} elseif (isset($palette[$color - 8])) {
// palette color, color index 0x08 maps to pallete index 0
return $palette[$color - 8];
}
// default color table
if ($version == Xls::XLS_BIFF8) {
return Color\BIFF8::lookup($color);
}
// BIFF5
return Color\BIFF5::lookup($color);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php 0000644 00000000253 15243417764 0017157 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Exception extends PhpSpreadsheetException
{
}
phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php 0000644 00000011132 15243417764 0016532 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
interface IReader
{
public const LOAD_WITH_CHARTS = 1;
public const READ_DATA_ONLY = 2;
public const SKIP_EMPTY_CELLS = 4;
public const IGNORE_EMPTY_CELLS = 4;
/**
* IReader constructor.
*/
public function __construct();
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool;
/**
* Read data only?
* If this is true, then the Reader will only read data values for cells, it will not read any formatting
* or structural information (like merges).
* If false (the default) it will read data and formatting.
*
* @return bool
*/
public function getReadDataOnly();
/**
* Set read data only
* Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting
* or structural information (like merges).
* Set to false (the default) to advise the Reader to read both data and formatting for cells.
*
* @param bool $readDataOnly
*
* @return IReader
*/
public function setReadDataOnly($readDataOnly);
/**
* Read empty cells?
* If this is true (the default), then the Reader will read data values for all cells, irrespective of value.
* If false it will not read data for cells containing a null value or an empty string.
*
* @return bool
*/
public function getReadEmptyCells();
/**
* Set read empty cells
* Set to true (the default) to advise the Reader read data values for all cells, irrespective of value.
* Set to false to advise the Reader to ignore cells containing a null value or an empty string.
*
* @param bool $readEmptyCells
*
* @return IReader
*/
public function setReadEmptyCells($readEmptyCells);
/**
* Read charts in workbook?
* If this is true, then the Reader will include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* If false (the default) it will ignore any charts defined in the workbook file.
*
* @return bool
*/
public function getIncludeCharts();
/**
* Set read charts in workbook
* Set to true, to advise the Reader to include any charts that exist in the workbook.
* Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value.
* Set to false (the default) to discard charts.
*
* @param bool $includeCharts
*
* @return IReader
*/
public function setIncludeCharts($includeCharts);
/**
* Get which sheets to load
* Returns either an array of worksheet names (the list of worksheets that should be loaded), or a null
* indicating that all worksheets in the workbook should be loaded.
*
* @return mixed
*/
public function getLoadSheetsOnly();
/**
* Set which sheets to load.
*
* @param mixed $value
* This should be either an array of worksheet names to be loaded, or a string containing a single worksheet name.
* If NULL, then it tells the Reader to read all worksheets in the workbook
*
* @return IReader
*/
public function setLoadSheetsOnly($value);
/**
* Set all sheets to load
* Tells the Reader to load all worksheets from the workbook.
*
* @return IReader
*/
public function setLoadAllSheets();
/**
* Read filter.
*
* @return IReadFilter
*/
public function getReadFilter();
/**
* Set read filter.
*
* @return IReader
*/
public function setReadFilter(IReadFilter $readFilter);
/**
* Loads PhpSpreadsheet from file.
*
* @param string $filename The name of the file to load
* @param int $flags Flags that can change the behaviour of the Writer:
* self::LOAD_WITH_CHARTS Load any charts that are defined (if the Reader supports Charts)
* self::READ_DATA_ONLY Read only data, not style or structure information, from the file
* self::SKIP_EMPTY_CELLS Don't read empty cells (cells that contain a null value,
* empty string, or a string containing only whitespace characters)
*
* @return \PhpOffice\PhpSpreadsheet\Spreadsheet
*/
public function load(string $filename, int $flags = 0);
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php 0000644 00000107161 15243417764 0015754 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use DOMAttr;
use DOMDocument;
use DOMElement;
use DOMNode;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension;
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator;
use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings;
use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Throwable;
use XMLReader;
use ZipArchive;
class Ods extends BaseReader
{
const INITIAL_FILE = 'content.xml';
/**
* Create a new Ods Reader instance.
*/
public function __construct()
{
parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this);
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
$mimeType = 'UNKNOWN';
// Load file
if (File::testFileNoThrow($filename, '')) {
$zip = new ZipArchive();
if ($zip->open($filename) === true) {
// check if it is an OOXML archive
$stat = $zip->statName('mimetype');
if (!empty($stat) && ($stat['size'] <= 255)) {
$mimeType = $zip->getFromName($stat['name']);
} elseif ($zip->statName('META-INF/manifest.xml')) {
$xml = simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($zip->getFromName('META-INF/manifest.xml')),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if ($xml !== false) {
$namespacesContent = $xml->getNamespaces(true);
if (isset($namespacesContent['manifest'])) {
$manifest = $xml->children($namespacesContent['manifest']);
foreach ($manifest as $manifestDataSet) {
/** @scrutinizer ignore-call */
$manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') {
$mimeType = (string) $manifestAttributes->{'media-type'};
break;
}
}
}
}
}
$zip->close();
}
}
return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
}
/**
* Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
*
* @param string $filename
*
* @return string[]
*/
public function listWorksheetNames($filename)
{
File::assertFile($filename, self::INITIAL_FILE);
$worksheetNames = [];
$xml = new XMLReader();
$xml->xml(
$this->getSecurityScannerOrThrow()->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
null,
Settings::getLibXmlLoaderOptions()
);
$xml->setParserProperty(2, true);
// Step into the first level of content of the XML
$xml->read();
while ($xml->read()) {
// Quickly jump through to the office:body node
while (self::getXmlName($xml) !== 'office:body') {
if ($xml->isEmptyElement) {
$xml->read();
} else {
$xml->next();
}
}
// Now read each node until we find our first table:table node
while ($xml->read()) {
$xmlName = self::getXmlName($xml);
if ($xmlName == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
// Loop through each table:table node reading the table:name attribute for each worksheet name
do {
$worksheetName = $xml->getAttribute('table:name');
if (!empty($worksheetName)) {
$worksheetNames[] = $worksheetName;
}
$xml->next();
} while (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
}
}
}
return $worksheetNames;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
File::assertFile($filename, self::INITIAL_FILE);
$worksheetInfo = [];
$xml = new XMLReader();
$xml->xml(
$this->getSecurityScannerOrThrow()->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
null,
Settings::getLibXmlLoaderOptions()
);
$xml->setParserProperty(2, true);
// Step into the first level of content of the XML
$xml->read();
while ($xml->read()) {
// Quickly jump through to the office:body node
while (self::getXmlName($xml) !== 'office:body') {
if ($xml->isEmptyElement) {
$xml->read();
} else {
$xml->next();
}
}
// Now read each node until we find our first table:table node
while ($xml->read()) {
if (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
$worksheetNames[] = $xml->getAttribute('table:name');
$tmpInfo = [
'worksheetName' => $xml->getAttribute('table:name'),
'lastColumnLetter' => 'A',
'lastColumnIndex' => 0,
'totalRows' => 0,
'totalColumns' => 0,
];
// Loop through each child node of the table:table element reading
$currCells = 0;
do {
$xml->read();
if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
$rowspan = $xml->getAttribute('table:number-rows-repeated');
$rowspan = empty($rowspan) ? 1 : $rowspan;
$tmpInfo['totalRows'] += $rowspan;
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
$currCells = 0;
// Step into the row
$xml->read();
do {
$doread = true;
if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
if (!$xml->isEmptyElement) {
++$currCells;
$xml->next();
$doread = false;
}
} elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
$mergeSize = $xml->getAttribute('table:number-columns-repeated');
$currCells += (int) $mergeSize;
}
if ($doread) {
$xml->read();
}
} while (self::getXmlName($xml) != 'table:table-row');
}
} while (self::getXmlName($xml) != 'table:table');
$tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
$tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
$worksheetInfo[] = $tmpInfo;
}
}
}
return $worksheetInfo;
}
/**
* Counteract Phpstan caching.
*
* @phpstan-impure
*/
private static function getXmlName(XMLReader $xml): string
{
return $xml->name;
}
/**
* Loads PhpSpreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
/**
* Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
*
* @param string $filename
*
* @return Spreadsheet
*/
public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
{
File::assertFile($filename, self::INITIAL_FILE);
$zip = new ZipArchive();
$zip->open($filename);
// Meta
$xml = @simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($zip->getFromName('meta.xml')),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
if ($xml === false) {
throw new Exception('Unable to read data from {$pFilename}');
}
$namespacesMeta = $xml->getNamespaces(true);
(new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta);
// Styles
$dom = new DOMDocument('1.01', 'UTF-8');
$dom->loadXML(
$this->getSecurityScannerOrThrow()->scan($zip->getFromName('styles.xml')),
Settings::getLibXmlLoaderOptions()
);
$pageSettings = new PageSettings($dom);
// Main Content
$dom = new DOMDocument('1.01', 'UTF-8');
$dom->loadXML(
$this->getSecurityScannerOrThrow()->scan($zip->getFromName(self::INITIAL_FILE)),
Settings::getLibXmlLoaderOptions()
);
$officeNs = $dom->lookupNamespaceUri('office');
$tableNs = $dom->lookupNamespaceUri('table');
$textNs = $dom->lookupNamespaceUri('text');
$xlinkNs = $dom->lookupNamespaceUri('xlink');
$styleNs = $dom->lookupNamespaceUri('style');
$pageSettings->readStyleCrossReferences($dom);
$autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
$definedNameReader = new DefinedNames($spreadsheet, $tableNs);
$columnWidths = [];
$automaticStyle0 = $dom->getElementsByTagNameNS($officeNs, 'automatic-styles')->item(0);
$automaticStyles = ($automaticStyle0 === null) ? [] : $automaticStyle0->getElementsByTagNameNS($styleNs, 'style');
foreach ($automaticStyles as $automaticStyle) {
$styleName = $automaticStyle->getAttributeNS($styleNs, 'name');
$styleFamily = $automaticStyle->getAttributeNS($styleNs, 'family');
if ($styleFamily === 'table-column') {
$tcprops = $automaticStyle->getElementsByTagNameNS($styleNs, 'table-column-properties');
if ($tcprops !== null) {
$tcprop = $tcprops->item(0);
if ($tcprop !== null) {
$columnWidth = $tcprop->getAttributeNs($styleNs, 'column-width');
$columnWidths[$styleName] = $columnWidth;
}
}
}
}
// Content
$item0 = $dom->getElementsByTagNameNS($officeNs, 'body')->item(0);
$spreadsheets = ($item0 === null) ? [] : $item0->getElementsByTagNameNS($officeNs, 'spreadsheet');
foreach ($spreadsheets as $workbookData) {
/** @var DOMElement $workbookData */
$tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
$worksheetID = 0;
foreach ($tables as $worksheetDataSet) {
/** @var DOMElement $worksheetDataSet */
$worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
// Check loadSheetsOnly
if (
$this->loadSheetsOnly !== null
&& $worksheetName
&& !in_array($worksheetName, $this->loadSheetsOnly)
) {
continue;
}
$worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name');
// Create sheet
if ($worksheetID > 0) {
$spreadsheet->createSheet(); // First sheet is added by default
}
$spreadsheet->setActiveSheetIndex($worksheetID);
if ($worksheetName || is_numeric($worksheetName)) {
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
// formula cells... during the load, all formulae should be correct, and we're simply
// bringing the worksheet name in line with the formula, not the reverse
$spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false);
}
// Go through every child of table element
$rowID = 1;
$tableColumnIndex = 1;
foreach ($worksheetDataSet->childNodes as $childNode) {
/** @var DOMElement $childNode */
// Filter elements which are not under the "table" ns
if ($childNode->namespaceURI != $tableNs) {
continue;
}
$key = $childNode->nodeName;
// Remove ns from node name
if (strpos($key, ':') !== false) {
$keyChunks = explode(':', $key);
$key = array_pop($keyChunks);
}
switch ($key) {
case 'table-header-rows':
/// TODO :: Figure this out. This is only a partial implementation I guess.
// ($rowData it's not used at all and I'm not sure that PHPExcel
// has an API for this)
// foreach ($rowData as $keyRowData => $cellData) {
// $rowData = $cellData;
// break;
// }
break;
case 'table-column':
if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) {
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
} else {
$rowRepeats = 1;
}
$tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name');
if (isset($columnWidths[$tableStyleName])) {
$columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
$tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex);
for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) {
$spreadsheet->getActiveSheet()
->getColumnDimension($tableColumnString)
->setWidth($columnWidth->toUnit('cm'), 'cm');
++$tableColumnString;
}
}
$tableColumnIndex += $rowRepeats;
break;
case 'table-row':
if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
$rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
} else {
$rowRepeats = 1;
}
$columnID = 'A';
/** @var DOMElement $cellData */
foreach ($childNode->childNodes as $cellData) {
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
} else {
$colRepeats = 1;
}
for ($i = 0; $i < $colRepeats; ++$i) {
++$columnID;
}
continue;
}
}
// Initialize variables
$formatting = $hyperlink = null;
$hasCalculatedValue = false;
$cellDataFormula = '';
if ($cellData->hasAttributeNS($tableNs, 'formula')) {
$cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
$hasCalculatedValue = true;
}
// Annotations
$annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
if ($annotation->length > 0 && $annotation->item(0) !== null) {
$textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
if ($textNode->length > 0 && $textNode->item(0) !== null) {
$text = $this->scanElementForText($textNode->item(0));
$spreadsheet->getActiveSheet()
->getComment($columnID . $rowID)
->setText($this->parseRichText($text));
// ->setAuthor( $author )
}
}
// Content
/** @var DOMElement[] $paragraphs */
$paragraphs = [];
foreach ($cellData->childNodes as $item) {
/** @var DOMElement $item */
// Filter text:p elements
if ($item->nodeName == 'text:p') {
$paragraphs[] = $item;
}
}
if (count($paragraphs) > 0) {
// Consolidate if there are multiple p records (maybe with spans as well)
$dataArray = [];
// Text can have multiple text:p and within those, multiple text:span.
// text:p newlines, but text:span does not.
// Also, here we assume there is no text data is span fields are specified, since
// we have no way of knowing proper positioning anyway.
foreach ($paragraphs as $pData) {
$dataArray[] = $this->scanElementForText($pData);
}
$allCellDataText = implode("\n", $dataArray);
$type = $cellData->getAttributeNS($officeNs, 'value-type');
switch ($type) {
case 'string':
$type = DataType::TYPE_STRING;
$dataValue = $allCellDataText;
foreach ($paragraphs as $paragraph) {
$link = $paragraph->getElementsByTagNameNS($textNs, 'a');
if ($link->length > 0 && $link->item(0) !== null) {
$hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
}
}
break;
case 'boolean':
$type = DataType::TYPE_BOOL;
$dataValue = ($allCellDataText == 'TRUE') ? true : false;
break;
case 'percentage':
$type = DataType::TYPE_NUMERIC;
$dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
// percentage should always be float
//if (floor($dataValue) == $dataValue) {
// $dataValue = (int) $dataValue;
//}
$formatting = NumberFormat::FORMAT_PERCENTAGE_00;
break;
case 'currency':
$type = DataType::TYPE_NUMERIC;
$dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
if (floor($dataValue) == $dataValue) {
$dataValue = (int) $dataValue;
}
$formatting = NumberFormat::FORMAT_CURRENCY_USD_INTEGER;
break;
case 'float':
$type = DataType::TYPE_NUMERIC;
$dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
if (floor($dataValue) == $dataValue) {
if ($dataValue == (int) $dataValue) {
$dataValue = (int) $dataValue;
}
}
break;
case 'date':
$type = DataType::TYPE_NUMERIC;
$value = $cellData->getAttributeNS($officeNs, 'date-value');
$dataValue = Date::convertIsoDate($value);
if ($dataValue != floor($dataValue)) {
$formatting = NumberFormat::FORMAT_DATE_XLSX15
. ' '
. NumberFormat::FORMAT_DATE_TIME4;
} else {
$formatting = NumberFormat::FORMAT_DATE_XLSX15;
}
break;
case 'time':
$type = DataType::TYPE_NUMERIC;
$timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
$dataValue = Date::PHPToExcel(
strtotime(
'01-01-1970 ' . implode(':', /** @scrutinizer ignore-type */ sscanf($timeValue, 'PT%dH%dM%dS') ?? [])
)
);
$formatting = NumberFormat::FORMAT_DATE_TIME4;
break;
default:
$dataValue = null;
}
} else {
$type = DataType::TYPE_NULL;
$dataValue = null;
}
if ($hasCalculatedValue) {
$type = DataType::TYPE_FORMULA;
$cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
$cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula);
}
if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
$colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
} else {
$colRepeats = 1;
}
if ($type !== null) {
for ($i = 0; $i < $colRepeats; ++$i) {
if ($i > 0) {
++$columnID;
}
if ($type !== DataType::TYPE_NULL) {
for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
$rID = $rowID + $rowAdjust;
$cell = $spreadsheet->getActiveSheet()
->getCell($columnID . $rID);
// Set value
if ($hasCalculatedValue) {
$cell->setValueExplicit($cellDataFormula, $type);
} else {
$cell->setValueExplicit($dataValue, $type);
}
if ($hasCalculatedValue) {
$cell->setCalculatedValue($dataValue);
}
// Set other properties
if ($formatting !== null) {
$spreadsheet->getActiveSheet()
->getStyle($columnID . $rID)
->getNumberFormat()
->setFormatCode($formatting);
} else {
$spreadsheet->getActiveSheet()
->getStyle($columnID . $rID)
->getNumberFormat()
->setFormatCode(NumberFormat::FORMAT_GENERAL);
}
if ($hyperlink !== null) {
$cell->getHyperlink()
->setUrl($hyperlink);
}
}
}
}
}
// Merged cells
$this->processMergedCells($cellData, $tableNs, $type, $columnID, $rowID, $spreadsheet);
++$columnID;
}
$rowID += $rowRepeats;
break;
}
}
$pageSettings->setVisibilityForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
$pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
++$worksheetID;
}
$autoFilterReader->read($workbookData);
$definedNameReader->read($workbookData);
}
$spreadsheet->setActiveSheetIndex(0);
if ($zip->locateName('settings.xml') !== false) {
$this->processSettings($zip, $spreadsheet);
}
// Return
return $spreadsheet;
}
private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void
{
$dom = new DOMDocument('1.01', 'UTF-8');
$dom->loadXML(
$this->getSecurityScannerOrThrow()->scan($zip->getFromName('settings.xml')),
Settings::getLibXmlLoaderOptions()
);
//$xlinkNs = $dom->lookupNamespaceUri('xlink');
$configNs = $dom->lookupNamespaceUri('config');
//$oooNs = $dom->lookupNamespaceUri('ooo');
$officeNs = $dom->lookupNamespaceUri('office');
$settings = $dom->getElementsByTagNameNS($officeNs, 'settings')
->item(0);
if ($settings !== null) {
$this->lookForActiveSheet($settings, $spreadsheet, $configNs);
$this->lookForSelectedCells($settings, $spreadsheet, $configNs);
}
}
private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
{
/** @var DOMElement $t */
foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) {
if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') {
try {
$spreadsheet->setActiveSheetIndexByName($t->nodeValue ?? '');
} catch (Throwable $e) {
// do nothing
}
break;
}
}
}
private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
{
/** @var DOMElement $t */
foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) {
if ($t->getAttributeNs($configNs, 'name') === 'Tables') {
foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) {
$setRow = $setCol = '';
$wsname = $ws->getAttributeNs($configNs, 'name');
foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) {
$attrName = $configItem->getAttributeNs($configNs, 'name');
if ($attrName === 'CursorPositionX') {
$setCol = $configItem->nodeValue;
}
if ($attrName === 'CursorPositionY') {
$setRow = $configItem->nodeValue;
}
}
$this->setSelected($spreadsheet, $wsname, "$setCol", "$setRow");
}
break;
}
}
}
private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void
{
if (is_numeric($setCol) && is_numeric($setRow)) {
$sheet = $spreadsheet->getSheetByName($wsname);
if ($sheet !== null) {
$sheet->setSelectedCells([(int) $setCol + 1, (int) $setRow + 1]);
}
}
}
/**
* Recursively scan element.
*
* @return string
*/
protected function scanElementForText(DOMNode $element)
{
$str = '';
foreach ($element->childNodes as $child) {
/** @var DOMNode $child */
if ($child->nodeType == XML_TEXT_NODE) {
$str .= $child->nodeValue;
} elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
// It's a space
// Multiple spaces?
$attributes = $child->attributes;
/** @var ?DOMAttr $cAttr */
$cAttr = ($attributes === null) ? null : $attributes->getNamedItem('c');
$multiplier = self::getMultiplier($cAttr);
$str .= str_repeat(' ', $multiplier);
}
if ($child->hasChildNodes()) {
$str .= $this->scanElementForText($child);
}
}
return $str;
}
private static function getMultiplier(?DOMAttr $cAttr): int
{
if ($cAttr) {
$multiplier = (int) $cAttr->nodeValue;
} else {
$multiplier = 1;
}
return $multiplier;
}
/**
* @param string $is
*
* @return RichText
*/
private function parseRichText($is)
{
$value = new RichText();
$value->createText($is);
return $value;
}
private function processMergedCells(
DOMElement $cellData,
string $tableNs,
string $type,
string $columnID,
int $rowID,
Spreadsheet $spreadsheet
): void {
if (
$cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
|| $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
) {
if (($type !== DataType::TYPE_NULL) || ($this->readDataOnly === false)) {
$columnTo = $columnID;
if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
$columnIndex = Coordinate::columnIndexFromString($columnID);
$columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
$columnIndex -= 2;
$columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
}
$rowTo = $rowID;
if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
$rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
}
$cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
$spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php 0000644 00000064032 15243417764 0015766 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader;
use DateTime;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings;
use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties;
use PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
/**
* Reader for SpreadsheetML, the XML schema for Microsoft Office Excel 2003.
*/
class Xml extends BaseReader
{
public const NAMESPACES_SS = 'urn:schemas-microsoft-com:office:spreadsheet';
/**
* Formats.
*
* @var array
*/
protected $styles = [];
/**
* Create a new Excel2003XML Reader instance.
*/
public function __construct()
{
parent::__construct();
$this->securityScanner = XmlScanner::getInstance($this);
}
/** @var string */
private $fileContents = '';
public static function xmlMappings(): array
{
return array_merge(
Style\Fill::FILL_MAPPINGS,
Style\Border::BORDER_MAPPINGS
);
}
/**
* Can the current IReader read the file?
*/
public function canRead(string $filename): bool
{
// Office xmlns:o="urn:schemas-microsoft-com:office:office"
// Excel xmlns:x="urn:schemas-microsoft-com:office:excel"
// XML Spreadsheet xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
// Spreadsheet component xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet"
// XML schema xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
// XML data type xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
// MS-persist recordset xmlns:rs="urn:schemas-microsoft-com:rowset"
// Rowset xmlns:z="#RowsetSchema"
//
$signature = [
'<?xml version="1.0"',
'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet',
];
// Open file
$data = file_get_contents($filename) ?: '';
// Why?
//$data = str_replace("'", '"', $data); // fix headers with single quote
$valid = true;
foreach ($signature as $match) {
// every part of the signature must be present
if (strpos($data, $match) === false) {
$valid = false;
break;
}
}
// Retrieve charset encoding
if (preg_match('/<?xml.*encoding=[\'"](.*?)[\'"].*?>/m', $data, $matches)) {
$charSet = strtoupper($matches[1]);
if (preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet) === 1) {
$data = StringHelper::convertEncoding($data, 'UTF-8', $charSet);
$data = (string) preg_replace('/(<?xml.*encoding=[\'"]).*?([\'"].*?>)/um', '$1' . 'UTF-8' . '$2', $data, 1);
}
}
$this->fileContents = $data;
return $valid;
}
/**
* Check if the file is a valid SimpleXML.
*
* @param string $filename
*
* @return false|SimpleXMLElement
*/
public function trySimpleXMLLoadString($filename)
{
try {
$xml = simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($this->fileContents ?: file_get_contents($filename)),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
} catch (\Exception $e) {
throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e);
}
$this->fileContents = '';
return $xml;
}
/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
* @param string $filename
*
* @return array
*/
public function listWorksheetNames($filename)
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
}
$worksheetNames = [];
$xml = $this->trySimpleXMLLoadString($filename);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
}
$xml_ss = $xml->children(self::NAMESPACES_SS);
foreach ($xml_ss->Worksheet as $worksheet) {
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
$worksheetNames[] = (string) $worksheet_ss['Name'];
}
return $worksheetNames;
}
/**
* Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
*
* @param string $filename
*
* @return array
*/
public function listWorksheetInfo($filename)
{
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
}
$worksheetInfo = [];
$xml = $this->trySimpleXMLLoadString($filename);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
}
$worksheetID = 1;
$xml_ss = $xml->children(self::NAMESPACES_SS);
foreach ($xml_ss->Worksheet as $worksheet) {
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
$tmpInfo = [];
$tmpInfo['worksheetName'] = '';
$tmpInfo['lastColumnLetter'] = 'A';
$tmpInfo['lastColumnIndex'] = 0;
$tmpInfo['totalRows'] = 0;
$tmpInfo['totalColumns'] = 0;
$tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}";
if (isset($worksheet_ss['Name'])) {
$tmpInfo['worksheetName'] = (string) $worksheet_ss['Name'];
}
if (isset($worksheet->Table->Row)) {
$rowIndex = 0;
foreach ($worksheet->Table->Row as $rowData) {
$columnIndex = 0;
$rowHasData = false;
foreach ($rowData->Cell as $cell) {
if (isset($cell->Data)) {
$tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex);
$rowHasData = true;
}
++$columnIndex;
}
++$rowIndex;
if ($rowHasData) {
$tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex);
}
}
}
$tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
$tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1;
$worksheetInfo[] = $tmpInfo;
++$worksheetID;
}
return $worksheetInfo;
}
/**
* Loads Spreadsheet from string.
*/
public function loadSpreadsheetFromString(string $contents): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
$spreadsheet->removeSheetByIndex(0);
// Load into this instance
return $this->loadIntoExisting($contents, $spreadsheet, true);
}
/**
* Loads Spreadsheet from file.
*/
protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
{
// Create new Spreadsheet
$spreadsheet = new Spreadsheet();
$spreadsheet->removeSheetByIndex(0);
// Load into this instance
return $this->loadIntoExisting($filename, $spreadsheet);
}
/**
* Loads from file or contents into Spreadsheet instance.
*
* @param string $filename file name if useContents is false else file contents
*/
public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet
{
if ($useContents) {
$this->fileContents = $filename;
} else {
File::assertFile($filename);
if (!$this->canRead($filename)) {
throw new Exception($filename . ' is an Invalid Spreadsheet file.');
}
}
$xml = $this->trySimpleXMLLoadString($filename);
if ($xml === false) {
throw new Exception("Problem reading {$filename}");
}
$namespaces = $xml->getNamespaces(true);
(new Properties($spreadsheet))->readProperties($xml, $namespaces);
$this->styles = (new Style())->parseStyles($xml, $namespaces);
if (isset($this->styles['Default'])) {
$spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']);
}
$worksheetID = 0;
$xml_ss = $xml->children(self::NAMESPACES_SS);
/** @var null|SimpleXMLElement $worksheetx */
foreach ($xml_ss->Worksheet as $worksheetx) {
$worksheet = $worksheetx ?? new SimpleXMLElement('<xml></xml>');
$worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
if (
isset($this->loadSheetsOnly, $worksheet_ss['Name']) &&
(!in_array($worksheet_ss['Name'], /** @scrutinizer ignore-type */ $this->loadSheetsOnly))
) {
continue;
}
// Create new Worksheet
$spreadsheet->createSheet();
$spreadsheet->setActiveSheetIndex($worksheetID);
$worksheetName = '';
if (isset($worksheet_ss['Name'])) {
$worksheetName = (string) $worksheet_ss['Name'];
// Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
// formula cells... during the load, all formulae should be correct, and we're simply bringing
// the worksheet name in line with the formula, not the reverse
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
}
if (isset($worksheet_ss['Protected'])) {
$protection = (string) $worksheet_ss['Protected'] === '1';
$spreadsheet->getActiveSheet()->getProtection()->setSheet($protection);
}
// locally scoped defined names
if (isset($worksheet->Names[0])) {
foreach ($worksheet->Names[0] as $definedName) {
$definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
$name = (string) $definedName_ss['Name'];
$definedValue = (string) $definedName_ss['RefersTo'];
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
if ($convertedValue[0] === '=') {
$convertedValue = substr($convertedValue, 1);
}
$spreadsheet->addDefinedName(DefinedName::createInstance($name, $spreadsheet->getActiveSheet(), $convertedValue, true));
}
}
$columnID = 'A';
if (isset($worksheet->Table->Column)) {
foreach ($worksheet->Table->Column as $columnData) {
$columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS);
$colspan = 0;
if (isset($columnData_ss['Span'])) {
$spanAttr = (string) $columnData_ss['Span'];
if (is_numeric($spanAttr)) {
$colspan = max(0, (int) $spanAttr);
}
}
if (isset($columnData_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
}
$columnWidth = null;
if (isset($columnData_ss['Width'])) {
$columnWidth = $columnData_ss['Width'];
}
$columnVisible = null;
if (isset($columnData_ss['Hidden'])) {
$columnVisible = ((string) $columnData_ss['Hidden']) !== '1';
}
while ($colspan >= 0) {
if (isset($columnWidth)) {
$spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
}
if (isset($columnVisible)) {
$spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible);
}
++$columnID;
--$colspan;
}
}
}
$rowID = 1;
if (isset($worksheet->Table->Row)) {
$additionalMergedCells = 0;
foreach ($worksheet->Table->Row as $rowData) {
$rowHasData = false;
$row_ss = self::getAttributes($rowData, self::NAMESPACES_SS);
if (isset($row_ss['Index'])) {
$rowID = (int) $row_ss['Index'];
}
if (isset($row_ss['Hidden'])) {
$rowVisible = ((string) $row_ss['Hidden']) !== '1';
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
}
$columnID = 'A';
foreach ($rowData->Cell as $cell) {
$cell_ss = self::getAttributes($cell, self::NAMESPACES_SS);
if (isset($cell_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
}
$cellRange = $columnID . $rowID;
if ($this->getReadFilter() !== null) {
if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
++$columnID;
continue;
}
}
if (isset($cell_ss['HRef'])) {
$spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl((string) $cell_ss['HRef']);
}
if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) {
$columnTo = $columnID;
if (isset($cell_ss['MergeAcross'])) {
$additionalMergedCells += (int) $cell_ss['MergeAcross'];
$columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']));
}
$rowTo = $rowID;
if (isset($cell_ss['MergeDown'])) {
$rowTo = $rowTo + $cell_ss['MergeDown'];
}
$cellRange .= ':' . $columnTo . $rowTo;
$spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE);
}
$hasCalculatedValue = false;
$cellDataFormula = '';
if (isset($cell_ss['Formula'])) {
$cellDataFormula = $cell_ss['Formula'];
$hasCalculatedValue = true;
}
if (isset($cell->Data)) {
$cellData = $cell->Data;
$cellValue = (string) $cellData;
$type = DataType::TYPE_NULL;
$cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS);
if (isset($cellData_ss['Type'])) {
$cellDataType = $cellData_ss['Type'];
switch ($cellDataType) {
/*
const TYPE_STRING = 's';
const TYPE_FORMULA = 'f';
const TYPE_NUMERIC = 'n';
const TYPE_BOOL = 'b';
const TYPE_NULL = 'null';
const TYPE_INLINE = 'inlineStr';
const TYPE_ERROR = 'e';
*/
case 'String':
$type = DataType::TYPE_STRING;
break;
case 'Number':
$type = DataType::TYPE_NUMERIC;
$cellValue = (float) $cellValue;
if (floor($cellValue) == $cellValue) {
$cellValue = (int) $cellValue;
}
break;
case 'Boolean':
$type = DataType::TYPE_BOOL;
$cellValue = ($cellValue != 0);
break;
case 'DateTime':
$type = DataType::TYPE_NUMERIC;
$dateTime = new DateTime($cellValue, new DateTimeZone('UTC'));
$cellValue = Date::PHPToExcel($dateTime);
break;
case 'Error':
$type = DataType::TYPE_ERROR;
$hasCalculatedValue = false;
break;
}
}
if ($hasCalculatedValue) {
$type = DataType::TYPE_FORMULA;
$columnNumber = Coordinate::columnIndexFromString($columnID);
$cellDataFormula = AddressHelper::convertFormulaToA1($cellDataFormula, $rowID, $columnNumber);
}
$spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type);
if ($hasCalculatedValue) {
$spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue);
}
$rowHasData = true;
}
if (isset($cell->Comment)) {
$this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID);
}
if (isset($cell_ss['StyleID'])) {
$style = (string) $cell_ss['StyleID'];
if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) {
//if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) {
// $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null);
//}
$spreadsheet->getActiveSheet()->getStyle($cellRange)
->applyFromArray($this->styles[$style]);
}
}
++$columnID;
while ($additionalMergedCells > 0) {
++$columnID;
--$additionalMergedCells;
}
}
if ($rowHasData) {
if (isset($row_ss['Height'])) {
$rowHeight = $row_ss['Height'];
$spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight);
}
}
++$rowID;
}
}
$dataValidations = new Xml\DataValidations();
$dataValidations->loadDataValidations($worksheet, $spreadsheet);
$xmlX = $worksheet->children(Namespaces::URN_EXCEL);
if (isset($xmlX->WorksheetOptions)) {
if (isset($xmlX->WorksheetOptions->FreezePanes)) {
$freezeRow = $freezeColumn = 1;
if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
$freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1;
}
if (isset($xmlX->WorksheetOptions->SplitVertical)) {
$freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
}
$spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow);
}
(new PageSettings($xmlX))->loadPageSettings($spreadsheet);
if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
$leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible;
$leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible;
if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
$leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
$spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
}
}
$rangeCalculated = false;
if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
$selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
. $selectionMatches[1]
. ':'
. Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
. $selectionMatches[3];
$spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
$rangeCalculated = true;
}
}
if (!$rangeCalculated) {
if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) {
$activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow;
} else {
$activeRow = 0;
}
if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) {
$activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol;
} else {
$activeColumn = 0;
}
if (is_numeric($activeRow) && is_numeric($activeColumn)) {
$selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1);
$spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
}
}
}
++$worksheetID;
}
// Globally scoped defined names
$activeSheetIndex = 0;
if (isset($xml->ExcelWorkbook->ActiveSheet)) {
$activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet;
}
$activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex);
if (isset($xml->Names[0])) {
foreach ($xml->Names[0] as $definedName) {
$definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
$name = (string) $definedName_ss['Name'];
$definedValue = (string) $definedName_ss['RefersTo'];
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
if ($convertedValue[0] === '=') {
$convertedValue = substr($convertedValue, 1);
}
$spreadsheet->addDefinedName(DefinedName::createInstance($name, $activeWorksheet, $convertedValue));
}
}
// Return
return $spreadsheet;
}
protected function parseCellComment(
SimpleXMLElement $comment,
Spreadsheet $spreadsheet,
string $columnID,
int $rowID
): void {
$commentAttributes = $comment->attributes(self::NAMESPACES_SS);
$author = 'unknown';
if (isset($commentAttributes->Author)) {
$author = (string) $commentAttributes->Author;
}
$node = $comment->Data->asXML();
$annotation = strip_tags((string) $node);
$spreadsheet->getActiveSheet()->getComment($columnID . $rowID)
->setAuthor($author)
->setText($this->parseRichText($annotation));
}
protected function parseRichText(string $annotation): RichText
{
$value = new RichText();
$value->createText($annotation);
return $value;
}
private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
{
return ($simple === null)
? new SimpleXMLElement('<xml></xml>')
: ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php 0000644 00000000655 15243417764 0021202 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class BaseParserClass
{
/**
* @param mixed $value
*/
protected static function boolean($value): bool
{
if (is_object($value)) {
$value = (string) $value; // @phpstan-ignore-line
}
if (is_numeric($value)) {
return (bool) $value;
}
return $value === 'true' || $value === 'TRUE';
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php 0000644 00000004331 15243417764 0020310 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class Hyperlinks
{
/** @var Worksheet */
private $worksheet;
/** @var array */
private $hyperlinks = [];
public function __construct(Worksheet $workSheet)
{
$this->worksheet = $workSheet;
}
public function readHyperlinks(SimpleXMLElement $relsWorksheet): void
{
foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) {
$element = Xlsx::getAttributes($elementx);
if ($element->Type == Namespaces::HYPERLINK) {
$this->hyperlinks[(string) $element->Id] = (string) $element->Target;
}
}
}
public function setHyperlinks(SimpleXMLElement $worksheetXml): void
{
foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) {
if ($hyperlink !== null) {
$this->setHyperlink($hyperlink, $this->worksheet);
}
}
}
private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void
{
// Link url
$linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT);
$attributes = Xlsx::getAttributes($hyperlink);
foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) {
$cell = $worksheet->getCell($cellReference);
if (isset($linkRel['id'])) {
$hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null;
if (isset($attributes['location'])) {
$hyperlinkUrl .= '#' . (string) $attributes['location'];
}
$cell->getHyperlink()->setUrl($hyperlinkUrl);
} elseif (isset($attributes['location'])) {
$cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']);
}
// Tooltip
if (isset($attributes['tooltip'])) {
$cell->getHyperlink()->setTooltip((string) $attributes['tooltip']);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php 0000644 00000007173 15243417764 0020341 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class TableReader
{
/**
* @var Worksheet
*/
private $worksheet;
/**
* @var SimpleXMLElement
*/
private $tableXml;
public function __construct(Worksheet $workSheet, SimpleXMLElement $tableXml)
{
$this->worksheet = $workSheet;
$this->tableXml = $tableXml;
}
/**
* Loads Table into the Worksheet.
*/
public function load(): void
{
// Remove all "$" in the table range
$tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? '');
if (strpos($tableRange, ':') !== false) {
$this->readTable($tableRange, $this->tableXml);
}
}
/**
* Read Table from xml.
*/
private function readTable(string $tableRange, SimpleXMLElement $tableXml): void
{
$table = new Table($tableRange);
$table->setName((string) $tableXml['displayName']);
$table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0');
$table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1');
$this->readTableAutoFilter($table, $tableXml->autoFilter);
$this->readTableColumns($table, $tableXml->tableColumns);
$this->readTableStyle($table, $tableXml->tableStyleInfo);
(new AutoFilter($table, $tableXml))->load();
$this->worksheet->addTable($table);
}
/**
* Reads TableAutoFilter from xml.
*/
private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void
{
if ($autoFilterXml->filterColumn === null) {
$table->setAllowFilter(false);
return;
}
foreach ($autoFilterXml->filterColumn as $filterColumn) {
$column = $table->getColumnByOffset((int) $filterColumn['colId']);
$column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1');
}
}
/**
* Reads TableColumns from xml.
*/
private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void
{
$offset = 0;
foreach ($tableColumnsXml->tableColumn as $tableColumn) {
$column = $table->getColumnByOffset($offset++);
if ($table->getShowTotalsRow()) {
if ($tableColumn['totalsRowLabel']) {
$column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']);
}
if ($tableColumn['totalsRowFunction']) {
$column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']);
}
}
if ($tableColumn->calculatedColumnFormula) {
$column->setColumnFormula((string) $tableColumn->calculatedColumnFormula);
}
}
}
/**
* Reads TableStyle from xml.
*/
private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
{
$tableStyle = new TableStyle();
$tableStyle->setTheme((string) $tableStyleInfoXml['name']);
$tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1');
$tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1');
$tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1');
$tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1');
$table->setStyle($tableStyle);
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php 0000644 00000037711 15243417764 0017453 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style;
use SimpleXMLElement;
use stdClass;
class Styles extends BaseParserClass
{
/**
* Theme instance.
*
* @var ?Theme
*/
private $theme;
/** @var array */
private $workbookPalette = [];
/** @var array */
private $styles = [];
/** @var array */
private $cellStyles = [];
/** @var SimpleXMLElement */
private $styleXml;
/** @var string */
private $namespace = '';
public function setNamespace(string $namespace): void
{
$this->namespace = $namespace;
}
public function setWorkbookPalette(array $palette): void
{
$this->workbookPalette = $palette;
}
/**
* Cast SimpleXMLElement to bool to overcome Scrutinizer problem.
*
* @param mixed $value
*/
private static function castBool($value): bool
{
return (bool) $value;
}
private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement
{
$attr = null;
if (self::castBool($value)) {
$attr = $value->attributes('');
if ($attr === null || count($attr) === 0) {
$attr = $value->attributes($this->namespace);
}
}
return Xlsx::testSimpleXml($attr);
}
public function setStyleXml(SimpleXmlElement $styleXml): void
{
$this->styleXml = $styleXml;
}
public function setTheme(Theme $theme): void
{
$this->theme = $theme;
}
public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void
{
$this->theme = $theme;
$this->styles = $styles;
$this->cellStyles = $cellStyles;
}
public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
{
if (isset($fontStyleXml->name)) {
$attr = $this->getStyleAttributes($fontStyleXml->name);
if (isset($attr['val'])) {
$fontStyle->setName((string) $attr['val']);
}
}
if (isset($fontStyleXml->sz)) {
$attr = $this->getStyleAttributes($fontStyleXml->sz);
if (isset($attr['val'])) {
$fontStyle->setSize((float) $attr['val']);
}
}
if (isset($fontStyleXml->b)) {
$attr = $this->getStyleAttributes($fontStyleXml->b);
$fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val']));
}
if (isset($fontStyleXml->i)) {
$attr = $this->getStyleAttributes($fontStyleXml->i);
$fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val']));
}
if (isset($fontStyleXml->strike)) {
$attr = $this->getStyleAttributes($fontStyleXml->strike);
$fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val']));
}
$fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color));
if (isset($fontStyleXml->u)) {
$attr = $this->getStyleAttributes($fontStyleXml->u);
if (!isset($attr['val'])) {
$fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
} else {
$fontStyle->setUnderline((string) $attr['val']);
}
}
if (isset($fontStyleXml->vertAlign)) {
$attr = $this->getStyleAttributes($fontStyleXml->vertAlign);
if (isset($attr['val'])) {
$verticalAlign = strtolower((string) $attr['val']);
if ($verticalAlign === 'superscript') {
$fontStyle->setSuperscript(true);
} elseif ($verticalAlign === 'subscript') {
$fontStyle->setSubscript(true);
}
}
}
if (isset($fontStyleXml->scheme)) {
$attr = $this->getStyleAttributes($fontStyleXml->scheme);
$fontStyle->setScheme((string) $attr['val']);
}
}
private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
{
if ((string) $numfmtStyleXml['formatCode'] !== '') {
$numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode']));
return;
}
$numfmt = $this->getStyleAttributes($numfmtStyleXml);
if (isset($numfmt['formatCode'])) {
$numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode']));
}
}
public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
{
if ($fillStyleXml->gradientFill) {
/** @var SimpleXMLElement $gradientFill */
$gradientFill = $fillStyleXml->gradientFill[0];
$attr = $this->getStyleAttributes($gradientFill);
if (!empty($attr['type'])) {
$fillStyle->setFillType((string) $attr['type']);
}
$fillStyle->setRotation((float) ($attr['degree']));
$gradientFill->registerXPathNamespace('sml', Namespaces::MAIN);
$fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
$fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
} elseif ($fillStyleXml->patternFill) {
$defaultFillStyle = Fill::FILL_NONE;
if ($fillStyleXml->patternFill->fgColor) {
$fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true));
$defaultFillStyle = Fill::FILL_SOLID;
}
if ($fillStyleXml->patternFill->bgColor) {
$fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true));
$defaultFillStyle = Fill::FILL_SOLID;
}
$type = '';
if ((string) $fillStyleXml->patternFill['patternType'] !== '') {
$type = (string) $fillStyleXml->patternFill['patternType'];
} else {
$attr = $this->getStyleAttributes($fillStyleXml->patternFill);
$type = (string) $attr['patternType'];
}
$patternType = ($type === '') ? $defaultFillStyle : $type;
$fillStyle->setFillType($patternType);
}
}
public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
{
$diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp');
$diagonalUp = self::boolean($diagonalUp);
$diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown');
$diagonalDown = self::boolean($diagonalDown);
if ($diagonalUp === false) {
if ($diagonalDown === false) {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
} else {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
}
} elseif ($diagonalDown === false) {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
} else {
$borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
}
if (isset($borderStyleXml->left)) {
$this->readBorder($borderStyle->getLeft(), $borderStyleXml->left);
}
if (isset($borderStyleXml->right)) {
$this->readBorder($borderStyle->getRight(), $borderStyleXml->right);
}
if (isset($borderStyleXml->top)) {
$this->readBorder($borderStyle->getTop(), $borderStyleXml->top);
}
if (isset($borderStyleXml->bottom)) {
$this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
}
if (isset($borderStyleXml->diagonal)) {
$this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
}
}
private function getAttribute(SimpleXMLElement $xml, string $attribute): string
{
$style = '';
if ((string) $xml[$attribute] !== '') {
$style = (string) $xml[$attribute];
} else {
$attr = $this->getStyleAttributes($xml);
if (isset($attr[$attribute])) {
$style = (string) $attr[$attribute];
}
}
return $style;
}
private function readBorder(Border $border, SimpleXMLElement $borderXml): void
{
$style = $this->getAttribute($borderXml, 'style');
if ($style !== '') {
$border->setBorderStyle((string) $style);
} else {
$border->setBorderStyle(Border::BORDER_NONE);
}
if (isset($borderXml->color)) {
$border->getColor()->setARGB($this->readColor($borderXml->color));
}
}
public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
{
$horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
if ($horizontal !== '') {
$alignment->setHorizontal($horizontal);
}
$vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
if ($vertical !== '') {
$alignment->setVertical($vertical);
}
$textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
if ($textRotation > 90) {
$textRotation = 90 - $textRotation;
}
$alignment->setTextRotation($textRotation);
$wrapText = $this->getAttribute($alignmentXml, 'wrapText');
$alignment->setWrapText(self::boolean((string) $wrapText));
$shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit');
$alignment->setShrinkToFit(self::boolean((string) $shrinkToFit));
$indent = (int) $this->getAttribute($alignmentXml, 'indent');
$alignment->setIndent(max($indent, 0));
$readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder');
$alignment->setReadOrder(max($readingOrder, 0));
}
private static function formatGeneral(string $formatString): string
{
if ($formatString === 'GENERAL') {
$formatString = NumberFormat::FORMAT_GENERAL;
}
return $formatString;
}
/**
* Read style.
*
* @param SimpleXMLElement|stdClass $style
*/
public function readStyle(Style $docStyle, $style): void
{
if ($style instanceof SimpleXMLElement) {
$this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
} else {
$docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt));
}
if (isset($style->font)) {
$this->readFontStyle($docStyle->getFont(), $style->font);
}
if (isset($style->fill)) {
$this->readFillStyle($docStyle->getFill(), $style->fill);
}
if (isset($style->border)) {
$this->readBorderStyle($docStyle->getBorders(), $style->border);
}
if (isset($style->alignment)) {
$this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
}
// protection
if (isset($style->protection)) {
$this->readProtectionLocked($docStyle, $style->protection);
$this->readProtectionHidden($docStyle, $style->protection);
}
// top-level style settings
if (isset($style->quotePrefix)) {
$docStyle->setQuotePrefix((bool) $style->quotePrefix);
}
}
/**
* Read protection locked attribute.
*/
public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void
{
$locked = '';
if ((string) $style['locked'] !== '') {
$locked = (string) $style['locked'];
} else {
$attr = $this->getStyleAttributes($style);
if (isset($attr['locked'])) {
$locked = (string) $attr['locked'];
}
}
if ($locked !== '') {
if (self::boolean($locked)) {
$docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
} else {
$docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
}
}
}
/**
* Read protection hidden attribute.
*/
public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void
{
$hidden = '';
if ((string) $style['hidden'] !== '') {
$hidden = (string) $style['hidden'];
} else {
$attr = $this->getStyleAttributes($style);
if (isset($attr['hidden'])) {
$hidden = (string) $attr['hidden'];
}
}
if ($hidden !== '') {
if (self::boolean((string) $hidden)) {
$docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
} else {
$docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
}
}
}
public function readColor(SimpleXMLElement $color, bool $background = false): string
{
$attr = $this->getStyleAttributes($color);
if (isset($attr['rgb'])) {
return (string) $attr['rgb'];
}
if (isset($attr['indexed'])) {
$indexedColor = (int) $attr['indexed'];
if ($indexedColor >= count($this->workbookPalette)) {
return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? '';
}
return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? '';
}
if (isset($attr['theme'])) {
if ($this->theme !== null) {
$returnColour = $this->theme->getColourByIndex((int) $attr['theme']);
if (isset($attr['tint'])) {
$tintAdjust = (float) $attr['tint'];
$returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust);
}
return 'FF' . $returnColour;
}
}
return ($background) ? 'FFFFFFFF' : 'FF000000';
}
public function dxfs(bool $readDataOnly = false): array
{
$dxfs = [];
if (!$readDataOnly && $this->styleXml) {
// Conditional Styles
if ($this->styleXml->dxfs) {
foreach ($this->styleXml->dxfs->dxf as $dxf) {
$style = new Style(false, true);
$this->readStyle($style, $dxf);
$dxfs[] = $style;
}
}
// Cell Styles
if ($this->styleXml->cellStyles) {
foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) {
$cellStyle = Xlsx::getAttributes($cellStylex);
if ((int) ($cellStyle['builtinId']) == 0) {
if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
// Set default style
$style = new Style();
$this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
// normal style, currently not using it for anything
}
}
}
}
}
return $dxfs;
}
public function styles(): array
{
return $this->styles;
}
/**
* Get array item.
*
* @param mixed $array (usually array, in theory can be false)
*
* @return stdClass
*/
private static function getArrayItem($array, int $key = 0)
{
return is_array($array) ? ($array[$key] ?? null) : null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php 0000644 00000230535 15243417764 0017230 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Chart\Axis;
use PhpOffice\PhpSpreadsheet\Chart\AxisText;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\GridLines;
use PhpOffice\PhpSpreadsheet\Chart\Layout;
use PhpOffice\PhpSpreadsheet\Chart\Legend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Font;
use SimpleXMLElement;
class Chart
{
/** @var string */
private $cNamespace;
/** @var string */
private $aNamespace;
public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML)
{
$this->cNamespace = $cNamespace;
$this->aNamespace = $aNamespace;
}
/**
* @param string $name
* @param string $format
*
* @return null|bool|float|int|string
*/
private static function getAttribute(SimpleXMLElement $component, $name, $format)
{
$attributes = $component->attributes();
if (@isset($attributes[$name])) {
if ($format == 'string') {
return (string) $attributes[$name];
} elseif ($format == 'integer') {
return (int) $attributes[$name];
} elseif ($format == 'boolean') {
$value = (string) $attributes[$name];
return $value === 'true' || $value === '1';
}
return (float) $attributes[$name];
}
return null;
}
/**
* @param string $chartName
*
* @return \PhpOffice\PhpSpreadsheet\Chart\Chart
*/
public function readChart(SimpleXMLElement $chartElements, $chartName)
{
$chartElementsC = $chartElements->children($this->cNamespace);
$XaxisLabel = $YaxisLabel = $legend = $title = null;
$dispBlanksAs = $plotVisOnly = null;
$plotArea = null;
$rotX = $rotY = $rAngAx = $perspective = null;
$xAxis = new Axis();
$yAxis = new Axis();
$autoTitleDeleted = null;
$chartNoFill = false;
$chartBorderLines = null;
$chartFillColor = null;
$gradientArray = [];
$gradientLin = null;
$roundedCorners = false;
$gapWidth = null;
$useUpBars = null;
$useDownBars = null;
foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) {
case 'spPr':
$children = $chartElementsC->spPr->children($this->aNamespace);
if (isset($children->noFill)) {
$chartNoFill = true;
}
if (isset($children->solidFill)) {
$chartFillColor = $this->readColor($children->solidFill);
}
if (isset($children->ln)) {
$chartBorderLines = new GridLines();
$this->readLineStyle($chartElementsC, $chartBorderLines);
}
break;
case 'roundedCorners':
/** @var bool */
$roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean');
break;
case 'chart':
foreach ($chartElement as $chartDetailsKey => $chartDetails) {
$chartDetails = Xlsx::testSimpleXml($chartDetails);
switch ($chartDetailsKey) {
case 'autoTitleDeleted':
/** @var bool */
$autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean');
break;
case 'view3D':
$rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer');
$rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer');
$rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer');
$perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer');
break;
case 'plotArea':
$plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
$plotSeries = $plotAttributes = [];
$catAxRead = false;
$plotNoFill = false;
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
$chartDetail = Xlsx::testSimpleXml($chartDetail);
switch ($chartDetailKey) {
case 'spPr':
$possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
if (isset($possibleNoFill->noFill)) {
$plotNoFill = true;
}
if (isset($possibleNoFill->gradFill->gsLst)) {
foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
$gradient = Xlsx::testSimpleXml($gradient);
/** @var float */
$pos = self::getAttribute($gradient, 'pos', 'float');
$gradientArray[] = [
$pos / ChartProperties::PERCENTAGE_MULTIPLIER,
new ChartColor($this->readColor($gradient)),
];
}
}
if (isset($possibleNoFill->gradFill->lin)) {
$gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string'));
}
break;
case 'layout':
$plotAreaLayout = $this->chartLayoutDetails($chartDetail);
break;
case Axis::AXIS_TYPE_CATEGORY:
case Axis::AXIS_TYPE_DATE:
$catAxRead = true;
if (isset($chartDetail->title)) {
$XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
}
$xAxis->setAxisType($chartDetailKey);
$this->readEffects($chartDetail, $xAxis);
$this->readLineStyle($chartDetail, $xAxis);
if (isset($chartDetail->spPr)) {
$sppr = $chartDetail->spPr->children($this->aNamespace);
if (isset($sppr->solidFill)) {
$axisColorArray = $this->readColor($sppr->solidFill);
$xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
}
if (isset($chartDetail->spPr->ln->noFill)) {
$xAxis->setNoFill(true);
}
}
if (isset($chartDetail->majorGridlines)) {
$majorGridlines = new GridLines();
if (isset($chartDetail->majorGridlines->spPr)) {
$this->readEffects($chartDetail->majorGridlines, $majorGridlines);
$this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
}
$xAxis->setMajorGridlines($majorGridlines);
}
if (isset($chartDetail->minorGridlines)) {
$minorGridlines = new GridLines();
$minorGridlines->activateObject();
if (isset($chartDetail->minorGridlines->spPr)) {
$this->readEffects($chartDetail->minorGridlines, $minorGridlines);
$this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
}
$xAxis->setMinorGridlines($minorGridlines);
}
$this->setAxisProperties($chartDetail, $xAxis);
break;
case Axis::AXIS_TYPE_VALUE:
$whichAxis = null;
$axPos = null;
if (isset($chartDetail->axPos)) {
$axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
}
if ($catAxRead) {
$whichAxis = $yAxis;
$yAxis->setAxisType($chartDetailKey);
} elseif (!empty($axPos)) {
switch ($axPos) {
case 't':
case 'b':
$whichAxis = $xAxis;
$xAxis->setAxisType($chartDetailKey);
break;
case 'r':
case 'l':
$whichAxis = $yAxis;
$yAxis->setAxisType($chartDetailKey);
break;
}
}
if (isset($chartDetail->title)) {
$axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
switch ($axPos) {
case 't':
case 'b':
$XaxisLabel = $axisLabel;
break;
case 'r':
case 'l':
$YaxisLabel = $axisLabel;
break;
}
}
$this->readEffects($chartDetail, $whichAxis);
$this->readLineStyle($chartDetail, $whichAxis);
if ($whichAxis !== null && isset($chartDetail->spPr)) {
$sppr = $chartDetail->spPr->children($this->aNamespace);
if (isset($sppr->solidFill)) {
$axisColorArray = $this->readColor($sppr->solidFill);
$whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
}
if (isset($sppr->ln->noFill)) {
$whichAxis->setNoFill(true);
}
}
if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
$majorGridlines = new GridLines();
if (isset($chartDetail->majorGridlines->spPr)) {
$this->readEffects($chartDetail->majorGridlines, $majorGridlines);
$this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
}
$whichAxis->setMajorGridlines($majorGridlines);
}
if ($whichAxis !== null && isset($chartDetail->minorGridlines)) {
$minorGridlines = new GridLines();
$minorGridlines->activateObject();
if (isset($chartDetail->minorGridlines->spPr)) {
$this->readEffects($chartDetail->minorGridlines, $minorGridlines);
$this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
}
$whichAxis->setMinorGridlines($minorGridlines);
}
$this->setAxisProperties($chartDetail, $whichAxis);
break;
case 'barChart':
case 'bar3DChart':
$barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotDirection("$barDirection");
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'lineChart':
case 'line3DChart':
$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'areaChart':
case 'area3DChart':
$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'doughnutChart':
case 'pieChart':
case 'pie3DChart':
$explosion = self::getAttribute($chartDetail->ser->explosion, 'val', 'string');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle("$explosion");
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'scatterChart':
/** @var string */
$scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle($scatterStyle);
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'bubbleChart':
$bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle("$bubbleScale");
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'radarChart':
/** @var string */
$radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle($radarStyle);
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'surfaceChart':
case 'surface3DChart':
$wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
$plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
$plotSer->setPlotStyle("$wireFrame");
$plotSeries[] = $plotSer;
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
case 'stockChart':
$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
if (isset($chartDetail->upDownBars->gapWidth)) {
$gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer');
}
if (isset($chartDetail->upDownBars->upBars)) {
$useUpBars = true;
}
if (isset($chartDetail->upDownBars->downBars)) {
$useDownBars = true;
}
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
}
}
if ($plotAreaLayout == null) {
$plotAreaLayout = new Layout();
}
$plotArea = new PlotArea($plotAreaLayout, $plotSeries);
$this->setChartAttributes($plotAreaLayout, $plotAttributes);
if ($plotNoFill) {
$plotArea->setNoFill(true);
}
if (!empty($gradientArray)) {
$plotArea->setGradientFillProperties($gradientArray, $gradientLin);
}
if (is_int($gapWidth)) {
$plotArea->setGapWidth($gapWidth);
}
if ($useUpBars === true) {
$plotArea->setUseUpBars(true);
}
if ($useDownBars === true) {
$plotArea->setUseDownBars(true);
}
break;
case 'plotVisOnly':
$plotVisOnly = self::getAttribute($chartDetails, 'val', 'string');
break;
case 'dispBlanksAs':
$dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string');
break;
case 'title':
$title = $this->chartTitle($chartDetails);
break;
case 'legend':
$legendPos = 'r';
$legendLayout = null;
$legendOverlay = false;
$legendBorderLines = null;
$legendFillColor = null;
$legendText = null;
$addLegendText = false;
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
$chartDetail = Xlsx::testSimpleXml($chartDetail);
switch ($chartDetailKey) {
case 'legendPos':
$legendPos = self::getAttribute($chartDetail, 'val', 'string');
break;
case 'overlay':
$legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
break;
case 'layout':
$legendLayout = $this->chartLayoutDetails($chartDetail);
break;
case 'spPr':
$children = $chartDetails->spPr->children($this->aNamespace);
if (isset($children->solidFill)) {
$legendFillColor = $this->readColor($children->solidFill);
}
if (isset($children->ln)) {
$legendBorderLines = new GridLines();
$this->readLineStyle($chartDetails, $legendBorderLines);
}
break;
case 'txPr':
$children = $chartDetails->txPr->children($this->aNamespace);
$addLegendText = false;
$legendText = new AxisText();
if (isset($children->p->pPr->defRPr->solidFill)) {
$colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
$legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
$addLegendText = true;
}
if (isset($children->p->pPr->defRPr->effectLst)) {
$this->readEffects($children->p->pPr->defRPr, $legendText, false);
$addLegendText = true;
}
break;
}
}
$legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
if ($legendFillColor !== null) {
$legend->getFillColor()->setColorPropertiesArray($legendFillColor);
}
if ($legendBorderLines !== null) {
$legend->setBorderLines($legendBorderLines);
}
if ($addLegendText) {
$legend->setLegendText($legendText);
}
break;
}
}
}
}
$chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
if ($chartNoFill) {
$chart->setNoFill(true);
}
if ($chartFillColor !== null) {
$chart->getFillColor()->setColorPropertiesArray($chartFillColor);
}
if ($chartBorderLines !== null) {
$chart->setBorderLines($chartBorderLines);
}
$chart->setRoundedCorners($roundedCorners);
if (is_bool($autoTitleDeleted)) {
$chart->setAutoTitleDeleted($autoTitleDeleted);
}
if (is_int($rotX)) {
$chart->setRotX($rotX);
}
if (is_int($rotY)) {
$chart->setRotY($rotY);
}
if (is_int($rAngAx)) {
$chart->setRAngAx($rAngAx);
}
if (is_int($perspective)) {
$chart->setPerspective($perspective);
}
return $chart;
}
private function chartTitle(SimpleXMLElement $titleDetails): Title
{
$caption = [];
$titleLayout = null;
$titleOverlay = false;
foreach ($titleDetails as $titleDetailKey => $chartDetail) {
$chartDetail = Xlsx::testSimpleXml($chartDetail);
switch ($titleDetailKey) {
case 'tx':
if (isset($chartDetail->rich)) {
$titleDetails = $chartDetail->rich->children($this->aNamespace);
foreach ($titleDetails as $titleKey => $titleDetail) {
$titleDetail = Xlsx::testSimpleXml($titleDetail);
switch ($titleKey) {
case 'p':
$titleDetailPart = $titleDetail->children($this->aNamespace);
$caption[] = $this->parseRichText($titleDetailPart);
}
}
} elseif (isset($chartDetail->strRef->strCache)) {
foreach ($chartDetail->strRef->strCache->pt as $pt) {
if (isset($pt->v)) {
$caption[] = (string) $pt->v;
}
}
}
break;
case 'overlay':
$titleOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
break;
case 'layout':
$titleLayout = $this->chartLayoutDetails($chartDetail);
break;
}
}
return new Title($caption, $titleLayout, (bool) $titleOverlay);
}
private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
{
if (!isset($chartDetail->manualLayout)) {
return null;
}
$details = $chartDetail->manualLayout->children($this->cNamespace);
if ($details === null) {
return null;
}
$layout = [];
foreach ($details as $detailKey => $detail) {
$detail = Xlsx::testSimpleXml($detail);
$layout[$detailKey] = self::getAttribute($detail, 'val', 'string');
}
return new Layout($layout);
}
private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
{
$multiSeriesType = null;
$smoothLine = false;
$seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
$seriesDetailSet = $chartDetail->children($this->cNamespace);
foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
switch ($seriesDetailKey) {
case 'grouping':
$multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string');
break;
case 'ser':
$marker = null;
$seriesIndex = '';
$fillColor = null;
$pointSize = null;
$noFill = false;
$bubble3D = false;
$dptColors = [];
$markerFillColor = null;
$markerBorderColor = null;
$lineStyle = null;
$labelLayout = null;
$trendLines = [];
foreach ($seriesDetails as $seriesKey => $seriesDetail) {
$seriesDetail = Xlsx::testSimpleXml($seriesDetail);
switch ($seriesKey) {
case 'idx':
$seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer');
break;
case 'order':
$seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer');
$plotOrder[$seriesIndex] = $seriesOrder;
break;
case 'tx':
$seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
break;
case 'spPr':
$children = $seriesDetail->children($this->aNamespace);
if (isset($children->ln)) {
$ln = $children->ln;
if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
$noFill = true;
}
$lineStyle = new GridLines();
$this->readLineStyle($seriesDetails, $lineStyle);
}
if (isset($children->effectLst)) {
if ($lineStyle === null) {
$lineStyle = new GridLines();
}
$this->readEffects($seriesDetails, $lineStyle);
}
if (isset($children->solidFill)) {
$fillColor = new ChartColor($this->readColor($children->solidFill));
}
break;
case 'dPt':
$dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string');
if (isset($seriesDetail->spPr)) {
$children = $seriesDetail->spPr->children($this->aNamespace);
if (isset($children->solidFill)) {
$arrayColors = $this->readColor($children->solidFill);
$dptColors[$dptIdx] = new ChartColor($arrayColors);
}
}
break;
case 'trendline':
$trendLine = new TrendLine();
$this->readLineStyle($seriesDetail, $trendLine);
/** @var ?string */
$trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string');
/** @var ?bool */
$dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean');
/** @var ?bool */
$dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean');
/** @var ?int */
$order = self::getAttribute($seriesDetail->order, 'val', 'integer');
/** @var ?int */
$period = self::getAttribute($seriesDetail->period, 'val', 'integer');
/** @var ?float */
$forward = self::getAttribute($seriesDetail->forward, 'val', 'float');
/** @var ?float */
$backward = self::getAttribute($seriesDetail->backward, 'val', 'float');
/** @var ?float */
$intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float');
/** @var ?string */
$name = (string) $seriesDetail->name;
$trendLine->setTrendLineProperties(
$trendLineType,
$order,
$period,
$dispRSqr,
$dispEq,
$backward,
$forward,
$intercept,
$name
);
$trendLines[] = $trendLine;
break;
case 'marker':
$marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
$pointSize = self::getAttribute($seriesDetail->size, 'val', 'string');
$pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
if (isset($seriesDetail->spPr)) {
$children = $seriesDetail->spPr->children($this->aNamespace);
if (isset($children->solidFill)) {
$markerFillColor = $this->readColor($children->solidFill);
}
if (isset($children->ln->solidFill)) {
$markerBorderColor = $this->readColor($children->ln->solidFill);
}
}
break;
case 'smooth':
$smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean');
break;
case 'cat':
$seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
break;
case 'val':
$seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
break;
case 'xVal':
$seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
break;
case 'yVal':
$seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
break;
case 'bubbleSize':
$seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
break;
case 'bubble3D':
$bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
break;
case 'dLbls':
$labelLayout = new Layout($this->readChartAttributes($seriesDetails));
break;
}
}
if ($labelLayout) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setLabelLayout($labelLayout);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setLabelLayout($labelLayout);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setLabelLayout($labelLayout);
}
}
if ($noFill) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setScatterLines(false);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setScatterLines(false);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setScatterLines(false);
}
}
if ($lineStyle !== null) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->copyLineStyles($lineStyle);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->copyLineStyles($lineStyle);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->copyLineStyles($lineStyle);
}
}
if ($bubble3D) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setBubble3D($bubble3D);
}
}
if (!empty($dptColors)) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setFillColor($dptColors);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setFillColor($dptColors);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setFillColor($dptColors);
}
}
if ($markerFillColor !== null) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
}
}
if ($markerBorderColor !== null) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
}
}
if ($smoothLine) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setSmoothLine(true);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setSmoothLine(true);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setSmoothLine(true);
}
}
if (!empty($trendLines)) {
if (isset($seriesLabel[$seriesIndex])) {
$seriesLabel[$seriesIndex]->setTrendLines($trendLines);
}
if (isset($seriesCategory[$seriesIndex])) {
$seriesCategory[$seriesIndex]->setTrendLines($trendLines);
}
if (isset($seriesValues[$seriesIndex])) {
$seriesValues[$seriesIndex]->setTrendLines($trendLines);
}
}
}
}
/** @phpstan-ignore-next-line */
$series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
$series->setPlotBubbleSizes($seriesBubbles);
return $series;
}
/**
* @return mixed
*/
private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null)
{
if (isset($seriesDetail->strRef)) {
$seriesSource = (string) $seriesDetail->strRef->f;
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
if (isset($seriesDetail->strRef->strCache)) {
$seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
}
return $seriesValues;
} elseif (isset($seriesDetail->numRef)) {
$seriesSource = (string) $seriesDetail->numRef->f;
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
if (isset($seriesDetail->numRef->numCache)) {
$seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
}
return $seriesValues;
} elseif (isset($seriesDetail->multiLvlStrRef)) {
$seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
$seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
}
return $seriesValues;
} elseif (isset($seriesDetail->multiLvlNumRef)) {
$seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
$seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
$seriesValues
->setFormatCode($seriesData['formatCode'])
->setDataValues($seriesData['dataValues']);
}
return $seriesValues;
}
if (isset($seriesDetail->v)) {
return new DataSeriesValues(
DataSeriesValues::DATASERIES_TYPE_STRING,
null,
null,
1,
[(string) $seriesDetail->v]
);
}
return null;
}
private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
{
$seriesVal = [];
$formatCode = '';
$pointCount = 0;
foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
$seriesValue = Xlsx::testSimpleXml($seriesValue);
switch ($seriesValueIdx) {
case 'ptCount':
$pointCount = self::getAttribute($seriesValue, 'val', 'integer');
break;
case 'formatCode':
$formatCode = (string) $seriesValue;
break;
case 'pt':
$pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
if ($dataType == 's') {
$seriesVal[$pointVal] = (string) $seriesValue->v;
} elseif ((string) $seriesValue->v === ExcelError::NA()) {
$seriesVal[$pointVal] = null;
} else {
$seriesVal[$pointVal] = (float) $seriesValue->v;
}
break;
}
}
return [
'formatCode' => $formatCode,
'pointCount' => $pointCount,
'dataValues' => $seriesVal,
];
}
private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
{
$seriesVal = [];
$formatCode = '';
$pointCount = 0;
foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
$seriesValue = Xlsx::testSimpleXml($seriesValue);
switch ($seriesValueIdx) {
case 'ptCount':
$pointCount = self::getAttribute($seriesValue, 'val', 'integer');
break;
case 'formatCode':
$formatCode = (string) $seriesValue;
break;
case 'pt':
$pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
if ($dataType == 's') {
$seriesVal[$pointVal][] = (string) $seriesValue->v;
} elseif ((string) $seriesValue->v === ExcelError::NA()) {
$seriesVal[$pointVal] = null;
} else {
$seriesVal[$pointVal][] = (float) $seriesValue->v;
}
break;
}
}
}
return [
'formatCode' => $formatCode,
'pointCount' => $pointCount,
'dataValues' => $seriesVal,
];
}
private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
{
$value = new RichText();
$defaultFontSize = null;
$defaultBold = null;
$defaultItalic = null;
$defaultUnderscore = null;
$defaultStrikethrough = null;
$defaultBaseline = null;
$defaultFontName = null;
$defaultLatin = null;
$defaultEastAsian = null;
$defaultComplexScript = null;
$defaultFontColor = null;
if (isset($titleDetailPart->pPr->defRPr)) {
/** @var ?int */
$defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
/** @var ?bool */
$defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
/** @var ?bool */
$defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
/** @var ?string */
$defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
/** @var ?string */
$defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
/** @var ?int */
$defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer');
if (isset($titleDetailPart->defRPr->rFont['val'])) {
$defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
}
if (isset($titleDetailPart->pPr->defRPr->latin)) {
/** @var ?string */
$defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->ea)) {
/** @var ?string */
$defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->cs)) {
/** @var ?string */
$defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
$defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill);
}
}
foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
if (
(string) $titleDetailElementKey !== 'r'
|| !isset($titleDetailElement->t)
) {
continue;
}
$objText = $value->createTextRun((string) $titleDetailElement->t);
if ($objText->getFont() === null) {
// @codeCoverageIgnoreStart
continue;
// @codeCoverageIgnoreEnd
}
$fontSize = null;
$bold = null;
$italic = null;
$underscore = null;
$strikethrough = null;
$baseline = null;
$fontName = null;
$latinName = null;
$eastAsian = null;
$complexScript = null;
$fontColor = null;
$underlineColor = null;
if (isset($titleDetailElement->rPr)) {
// not used now, not sure it ever was, grandfathering
if (isset($titleDetailElement->rPr->rFont['val'])) {
// @codeCoverageIgnoreStart
$fontName = (string) $titleDetailElement->rPr->rFont['val'];
// @codeCoverageIgnoreEnd
}
if (isset($titleDetailElement->rPr->latin)) {
/** @var ?string */
$latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
}
if (isset($titleDetailElement->rPr->ea)) {
/** @var ?string */
$eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string');
}
if (isset($titleDetailElement->rPr->cs)) {
/** @var ?string */
$complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string');
}
/** @var ?int */
$fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer');
// not used now, not sure it ever was, grandfathering
if (isset($titleDetailElement->rPr->solidFill)) {
$fontColor = $this->readColor($titleDetailElement->rPr->solidFill);
}
/** @var ?bool */
$bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
/** @var ?bool */
$italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
/** @var ?int */
$baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
/** @var ?string */
$underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string');
if (isset($titleDetailElement->rPr->uFill->solidFill)) {
$underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill);
}
/** @var ?string */
$strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string');
}
$fontFound = false;
$latinName = $latinName ?? $defaultLatin;
if ($latinName !== null) {
$objText->getFont()->setLatin($latinName);
$fontFound = true;
}
$eastAsian = $eastAsian ?? $defaultEastAsian;
if ($eastAsian !== null) {
$objText->getFont()->setEastAsian($eastAsian);
$fontFound = true;
}
$complexScript = $complexScript ?? $defaultComplexScript;
if ($complexScript !== null) {
$objText->getFont()->setComplexScript($complexScript);
$fontFound = true;
}
$fontName = $fontName ?? $defaultFontName;
if ($fontName !== null) {
// @codeCoverageIgnoreStart
$objText->getFont()->setName($fontName);
$fontFound = true;
// @codeCoverageIgnoreEnd
}
$fontSize = $fontSize ?? $defaultFontSize;
if (is_int($fontSize)) {
$objText->getFont()->setSize(floor($fontSize / 100));
$fontFound = true;
} else {
$objText->getFont()->setSize(null, true);
}
$fontColor = $fontColor ?? $defaultFontColor;
if (!empty($fontColor)) {
$objText->getFont()->setChartColor($fontColor);
$fontFound = true;
}
$bold = $bold ?? $defaultBold;
if ($bold !== null) {
$objText->getFont()->setBold($bold);
$fontFound = true;
}
$italic = $italic ?? $defaultItalic;
if ($italic !== null) {
$objText->getFont()->setItalic($italic);
$fontFound = true;
}
$baseline = $baseline ?? $defaultBaseline;
if ($baseline !== null) {
$objText->getFont()->setBaseLine($baseline);
if ($baseline > 0) {
$objText->getFont()->setSuperscript(true);
} elseif ($baseline < 0) {
$objText->getFont()->setSubscript(true);
}
$fontFound = true;
}
$underscore = $underscore ?? $defaultUnderscore;
if ($underscore !== null) {
if ($underscore == 'sng') {
$objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
} elseif ($underscore == 'dbl') {
$objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
} elseif ($underscore !== '') {
$objText->getFont()->setUnderline($underscore);
} else {
$objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
}
$fontFound = true;
if ($underlineColor) {
$objText->getFont()->setUnderlineColor($underlineColor);
}
}
$strikethrough = $strikethrough ?? $defaultStrikethrough;
if ($strikethrough !== null) {
$objText->getFont()->setStrikeType($strikethrough);
if ($strikethrough == 'noStrike') {
$objText->getFont()->setStrikethrough(false);
} else {
$objText->getFont()->setStrikethrough(true);
}
$fontFound = true;
}
if ($fontFound === false) {
$objText->setFont(null);
}
}
return $value;
}
private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
{
if (!isset($titleDetailPart->pPr->defRPr)) {
return null;
}
$fontArray = [];
$fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
$fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
$fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
$fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
$fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
if (isset($titleDetailPart->pPr->defRPr->latin)) {
$fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->ea)) {
$fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->cs)) {
$fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
}
if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
$fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
}
$font = new Font();
$font->setSize(null, true);
$font->applyFromArray($fontArray);
return $font;
}
/**
* @param ?SimpleXMLElement $chartDetail
*/
private function readChartAttributes($chartDetail): array
{
$plotAttributes = [];
if (isset($chartDetail->dLbls)) {
if (isset($chartDetail->dLbls->dLblPos)) {
$plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string');
}
if (isset($chartDetail->dLbls->numFmt)) {
$plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string');
$plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean');
}
if (isset($chartDetail->dLbls->showLegendKey)) {
$plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string');
}
if (isset($chartDetail->dLbls->showVal)) {
$plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string');
}
if (isset($chartDetail->dLbls->showCatName)) {
$plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string');
}
if (isset($chartDetail->dLbls->showSerName)) {
$plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string');
}
if (isset($chartDetail->dLbls->showPercent)) {
$plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string');
}
if (isset($chartDetail->dLbls->showBubbleSize)) {
$plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string');
}
if (isset($chartDetail->dLbls->showLeaderLines)) {
$plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string');
}
if (isset($chartDetail->dLbls->spPr)) {
$sppr = $chartDetail->dLbls->spPr->children($this->aNamespace);
if (isset($sppr->solidFill)) {
$plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill));
}
if (isset($sppr->ln->solidFill)) {
$plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill));
}
}
if (isset($chartDetail->dLbls->txPr)) {
$txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
if (isset($txpr->p)) {
$plotAttributes['labelFont'] = $this->parseFont($txpr->p);
if (isset($txpr->p->pPr->defRPr->effectLst)) {
$labelEffects = new GridLines();
$this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
$plotAttributes['labelEffects'] = $labelEffects;
}
}
}
}
return $plotAttributes;
}
/**
* @param mixed $plotAttributes
*/
private function setChartAttributes(Layout $plotArea, $plotAttributes): void
{
foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
switch ($plotAttributeKey) {
case 'showLegendKey':
$plotArea->setShowLegendKey($plotAttributeValue);
break;
case 'showVal':
$plotArea->setShowVal($plotAttributeValue);
break;
case 'showCatName':
$plotArea->setShowCatName($plotAttributeValue);
break;
case 'showSerName':
$plotArea->setShowSerName($plotAttributeValue);
break;
case 'showPercent':
$plotArea->setShowPercent($plotAttributeValue);
break;
case 'showBubbleSize':
$plotArea->setShowBubbleSize($plotAttributeValue);
break;
case 'showLeaderLines':
$plotArea->setShowLeaderLines($plotAttributeValue);
break;
}
}
}
private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
{
if (!isset($chartObject)) {
return;
}
if ($getSppr) {
if (!isset($chartDetail->spPr)) {
return;
}
$sppr = $chartDetail->spPr->children($this->aNamespace);
} else {
$sppr = $chartDetail;
}
if (isset($sppr->effectLst->glow)) {
$axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
if ($axisGlowSize != 0.0) {
$colorArray = $this->readColor($sppr->effectLst->glow);
$chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
}
}
if (isset($sppr->effectLst->softEdge)) {
/** @var string */
$softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string');
if (is_numeric($softEdgeSize)) {
$chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize));
}
}
$type = '';
foreach (self::SHADOW_TYPES as $shadowType) {
if (isset($sppr->effectLst->$shadowType)) {
$type = $shadowType;
break;
}
}
if ($type !== '') {
/** @var string */
$blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string');
$blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null;
/** @var string */
$dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string');
$dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null;
/** @var string */
$direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string');
$direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null;
$algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string');
$rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string');
$size = [];
foreach (['sx', 'sy'] as $sizeType) {
$sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
if (is_numeric($sizeValue)) {
$size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue);
} else {
$size[$sizeType] = null;
}
}
foreach (['kx', 'ky'] as $sizeType) {
$sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
if (is_numeric($sizeValue)) {
$size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue);
} else {
$size[$sizeType] = null;
}
}
$colorArray = $this->readColor($sppr->effectLst->$type);
$chartObject
->setShadowProperty('effect', $type)
->setShadowProperty('blur', $blur)
->setShadowProperty('direction', $direction)
->setShadowProperty('distance', $dist)
->setShadowProperty('algn', $algn)
->setShadowProperty('rotWithShape', $rot)
->setShadowProperty('size', $size)
->setShadowProperty('color', $colorArray);
}
}
private const SHADOW_TYPES = [
'outerShdw',
'innerShdw',
];
private function readColor(SimpleXMLElement $colorXml): array
{
$result = [
'type' => null,
'value' => null,
'alpha' => null,
'brightness' => null,
];
foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
if (isset($colorXml->$type)) {
$result['type'] = $type;
$result['value'] = self::getAttribute($colorXml->$type, 'val', 'string');
if (isset($colorXml->$type->alpha)) {
/** @var string */
$alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string');
if (is_numeric($alpha)) {
$result['alpha'] = ChartColor::alphaFromXml($alpha);
}
}
if (isset($colorXml->$type->lumMod)) {
/** @var string */
$brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string');
if (is_numeric($brightness)) {
$result['brightness'] = ChartColor::alphaFromXml($brightness);
}
}
break;
}
}
return $result;
}
private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
{
if (!isset($chartObject, $chartDetail->spPr)) {
return;
}
$sppr = $chartDetail->spPr->children($this->aNamespace);
if (!isset($sppr->ln)) {
return;
}
$lineWidth = null;
/** @var string */
$lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string');
if (is_numeric($lineWidthTemp)) {
$lineWidth = ChartProperties::xmlToPoints($lineWidthTemp);
}
/** @var string */
$compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string');
/** @var string */
$dashType = self::getAttribute($sppr->ln->prstDash, 'val', 'string');
/** @var string */
$capType = self::getAttribute($sppr->ln, 'cap', 'string');
if (isset($sppr->ln->miter)) {
$joinType = ChartProperties::LINE_STYLE_JOIN_MITER;
} elseif (isset($sppr->ln->bevel)) {
$joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL;
} else {
$joinType = '';
}
$headArrowSize = '';
$endArrowSize = '';
/** @var string */
$headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string');
/** @var string */
$headArrowWidth = self::getAttribute($sppr->ln->headEnd, 'w', 'string');
/** @var string */
$headArrowLength = self::getAttribute($sppr->ln->headEnd, 'len', 'string');
/** @var string */
$endArrowType = self::getAttribute($sppr->ln->tailEnd, 'type', 'string');
/** @var string */
$endArrowWidth = self::getAttribute($sppr->ln->tailEnd, 'w', 'string');
/** @var string */
$endArrowLength = self::getAttribute($sppr->ln->tailEnd, 'len', 'string');
$chartObject->setLineStyleProperties(
$lineWidth,
$compoundType,
$dashType,
$capType,
$joinType,
$headArrowType,
$headArrowSize,
$endArrowType,
$endArrowSize,
$headArrowWidth,
$headArrowLength,
$endArrowWidth,
$endArrowLength
);
$colorArray = $this->readColor($sppr->ln->solidFill);
$chartObject->getLineColor()->setColorPropertiesArray($colorArray);
}
private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void
{
if (!isset($whichAxis)) {
return;
}
if (isset($chartDetail->delete)) {
$whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string'));
}
if (isset($chartDetail->numFmt)) {
$whichAxis->setAxisNumberProperties(
(string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'),
null,
(int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int')
);
}
if (isset($chartDetail->crossBetween)) {
$whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string'));
}
if (isset($chartDetail->majorTickMark)) {
$whichAxis->setAxisOption('major_tick_mark', (string) self::getAttribute($chartDetail->majorTickMark, 'val', 'string'));
}
if (isset($chartDetail->minorTickMark)) {
$whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttribute($chartDetail->minorTickMark, 'val', 'string'));
}
if (isset($chartDetail->tickLblPos)) {
$whichAxis->setAxisOption('axis_labels', (string) self::getAttribute($chartDetail->tickLblPos, 'val', 'string'));
}
if (isset($chartDetail->crosses)) {
$whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttribute($chartDetail->crosses, 'val', 'string'));
}
if (isset($chartDetail->crossesAt)) {
$whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttribute($chartDetail->crossesAt, 'val', 'string'));
}
if (isset($chartDetail->scaling->orientation)) {
$whichAxis->setAxisOption('orientation', (string) self::getAttribute($chartDetail->scaling->orientation, 'val', 'string'));
}
if (isset($chartDetail->scaling->max)) {
$whichAxis->setAxisOption('maximum', (string) self::getAttribute($chartDetail->scaling->max, 'val', 'string'));
}
if (isset($chartDetail->scaling->min)) {
$whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
}
if (isset($chartDetail->scaling->min)) {
$whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
}
if (isset($chartDetail->majorUnit)) {
$whichAxis->setAxisOption('major_unit', (string) self::getAttribute($chartDetail->majorUnit, 'val', 'string'));
}
if (isset($chartDetail->minorUnit)) {
$whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string'));
}
if (isset($chartDetail->baseTimeUnit)) {
$whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string'));
}
if (isset($chartDetail->majorTimeUnit)) {
$whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string'));
}
if (isset($chartDetail->minorTimeUnit)) {
$whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string'));
}
if (isset($chartDetail->txPr)) {
$children = $chartDetail->txPr->children($this->aNamespace);
$addAxisText = false;
$axisText = new AxisText();
if (isset($children->bodyPr)) {
/** @var string */
$textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
if (is_numeric($textRotation)) {
$axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
$addAxisText = true;
}
}
if (isset($children->p->pPr->defRPr)) {
$font = $this->parseFont($children->p);
if ($font !== null) {
$axisText->setFont($font);
$addAxisText = true;
}
}
if (isset($children->p->pPr->defRPr->effectLst)) {
$this->readEffects($children->p->pPr->defRPr, $axisText, false);
$addAxisText = true;
}
if ($addAxisText) {
$whichAxis->setAxisText($axisText);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php 0000644 00000012255 15243417764 0020243 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class Namespaces
{
const SCHEMAS = 'http://schemas.openxmlformats.org';
const RELATIONSHIPS = 'http://schemas.openxmlformats.org/package/2006/relationships';
// This one used in Reader\Xlsx
const CORE_PROPERTIES = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';
// This one used in Reader\Xlsx\Properties
const CORE_PROPERTIES2 = 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties';
const THUMBNAIL = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail';
const THEME = 'http://schemas.openxmlformats.org/package/2006/relationships/theme';
const THEME2 = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme';
const COMPATIBILITY = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
const MAIN = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
const RELATIONSHIPS_DRAWING = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing';
const DRAWINGML = 'http://schemas.openxmlformats.org/drawingml/2006/main';
const CHART = 'http://schemas.openxmlformats.org/drawingml/2006/chart';
const CHART_ALTERNATE = 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart';
const RELATIONSHIPS_CHART = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart';
const SPREADSHEET_DRAWING = 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing';
const SCHEMA_OFFICE_DOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships';
const COMMENTS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments';
const RELATIONSHIPS_CUSTOM_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties';
const RELATIONSHIPS_EXTENDED_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties';
const RELATIONSHIPS_CTRLPROP = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp';
const CUSTOM_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties';
const EXTENDED_PROPERTIES = 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties';
const PROPERTIES_VTYPES = 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes';
const HYPERLINK = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink';
const OFFICE_DOCUMENT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument';
const SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
const STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
const IMAGE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image';
const VML = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing';
const WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
const CHARTSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet';
const SCHEMA_MICROSOFT = 'http://schemas.microsoft.com/office/2006/relationships';
const EXTENSIBILITY = 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility';
const VBA = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
const VBA_SIGNATURE = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject';
const DATA_VALIDATIONS1 = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main';
const DATA_VALIDATIONS2 = 'http://schemas.microsoft.com/office/excel/2006/main';
const CONTENT_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
const RELATIONSHIPS_PRINTER_SETTINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings';
const RELATIONSHIPS_TABLE = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table';
const SPREADSHEETML_AC = 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac';
const DC_ELEMENTS = 'http://purl.org/dc/elements/1.1/';
const DC_TERMS = 'http://purl.org/dc/terms/';
const DC_DCMITYPE = 'http://purl.org/dc/dcmitype/';
const SCHEMA_INSTANCE = 'http://www.w3.org/2001/XMLSchema-instance';
const URN_EXCEL = 'urn:schemas-microsoft-com:office:excel';
const URN_MSOFFICE = 'urn:schemas-microsoft-com:office:office';
const URN_VML = 'urn:schemas-microsoft-com:vml';
const SCHEMA_PURL = 'http://purl.oclc.org/ooxml';
const PURL_OFFICE_DOCUMENT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument';
const PURL_RELATIONSHIPS = 'http://purl.oclc.org/ooxml/officeDocument/relationships';
const PURL_MAIN = 'http://purl.oclc.org/ooxml/spreadsheetml/main';
const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main';
const PURL_CHART = 'http://purl.oclc.org/ooxml/drawingml/chart';
const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet';
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php 0000644 00000015201 15243417764 0020234 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class AutoFilter
{
/**
* @var Table|Worksheet
*/
private $parent;
/**
* @var SimpleXMLElement
*/
private $worksheetXml;
/**
* @param Table|Worksheet $parent
*/
public function __construct($parent, SimpleXMLElement $worksheetXml)
{
$this->parent = $parent;
$this->worksheetXml = $worksheetXml;
}
public function load(): void
{
// Remove all "$" in the auto filter range
$autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
if (strpos($autoFilterRange, ':') !== false) {
$this->readAutoFilter($autoFilterRange, $this->worksheetXml);
}
}
private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void
{
$autoFilter = $this->parent->getAutoFilter();
$autoFilter->setRange($autoFilterRange);
foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
$column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
// Check for standard filters
if ($filterColumn->filters) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
$filters = $filterColumn->filters;
if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Standard filters are always an OR join, so no join rule needs to be set
// Entries can be either filter elements
foreach ($filters->filter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
}
// Or Date Group elements
$this->readDateRangeAutoFilter($filters, $column);
}
// Check for custom filters
$this->readCustomAutoFilter($filterColumn, $column);
// Check for dynamic filters
$this->readDynamicAutoFilter($filterColumn, $column);
// Check for dynamic filters
$this->readTopTenAutoFilter($filterColumn, $column);
}
$autoFilter->setEvaluated(true);
}
private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void
{
foreach ($filters->dateGroupItem as $dateGroupItem) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
'',
[
'year' => (string) $dateGroupItem['year'],
'month' => (string) $dateGroupItem['month'],
'day' => (string) $dateGroupItem['day'],
'hour' => (string) $dateGroupItem['hour'],
'minute' => (string) $dateGroupItem['minute'],
'second' => (string) $dateGroupItem['second'],
],
(string) $dateGroupItem['dateTimeGrouping']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
}
}
private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
{
if (isset($filterColumn, $filterColumn->customFilters)) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
$customFilters = $filterColumn->customFilters;
// Custom filters can an AND or an OR join;
// and there should only ever be one or two entries
if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) {
$column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
}
foreach ($customFilters->customFilter as $filterRule) {
$column->createRule()->setRule(
(string) $filterRule['operator'],
(string) $filterRule['val']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
}
}
}
private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
{
if (isset($filterColumn, $filterColumn->dynamicFilter)) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
// We should only ever have one dynamic filter
foreach ($filterColumn->dynamicFilter as $filterRule) {
// Operator is undefined, but always treated as EQUAL
$column->createRule()->setRule(
'',
(string) $filterRule['val'],
(string) $filterRule['type']
)->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
if (isset($filterRule['val'])) {
$column->setAttribute('val', (string) $filterRule['val']);
}
if (isset($filterRule['maxVal'])) {
$column->setAttribute('maxVal', (string) $filterRule['maxVal']);
}
}
}
}
private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
{
if (isset($filterColumn, $filterColumn->top10)) {
$column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
// We should only ever have one top10 filter
foreach ($filterColumn->top10 as $filterRule) {
$column->createRule()->setRule(
(
((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1'))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
),
(string) $filterRule['val'],
(
((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1'))
? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
: Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
)
)->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php 0000644 00000025721 15243417764 0021635 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
use PhpOffice\PhpSpreadsheet\Style\Style as Style;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
use stdClass;
class ConditionalStyles
{
/** @var Worksheet */
private $worksheet;
/** @var SimpleXMLElement */
private $worksheetXml;
/**
* @var array
*/
private $ns;
/** @var array */
private $dxfs;
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = [])
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
$this->dxfs = $dxfs;
}
public function load(): void
{
$selectedCells = $this->worksheet->getSelectedCells();
$this->setConditionalStyles(
$this->worksheet,
$this->readConditionalStyles($this->worksheetXml),
$this->worksheetXml->extLst
);
$this->worksheet->setSelectedCells($selectedCells);
}
public function loadFromExt(StyleReader $styleReader): void
{
$selectedCells = $this->worksheet->getSelectedCells();
$this->ns = $this->worksheetXml->getNamespaces(true);
$this->setConditionalsFromExt(
$this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader)
);
$this->worksheet->setSelectedCells($selectedCells);
}
private function setConditionalsFromExt(array $conditionals): void
{
foreach ($conditionals as $conditionalRange => $cfRules) {
ksort($cfRules);
// Priority is used as the key for sorting; but may not start at 0,
// so we use array_values to reset the index after sorting.
$this->worksheet->getStyle($conditionalRange)
->setConditionalStyles(array_values($cfRules));
}
}
private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array
{
$conditionals = [];
if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
$conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']);
if (!$conditionalFormattingRuleXml->conditionalFormattings) {
return [];
}
foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) {
$extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']);
if (!$extFormattingRangeXml->sqref) {
continue;
}
$sqref = (string) $extFormattingRangeXml->sqref;
$extCfRuleXml = $extFormattingXml->cfRule;
$attributes = $extCfRuleXml->attributes();
if (!$attributes) {
continue;
}
$conditionType = (string) $attributes->type;
if (
!Conditional::isValidConditionType($conditionType) ||
$conditionType === Conditional::CONDITION_DATABAR
) {
continue;
}
$priority = (int) $attributes->priority;
$conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
$cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader);
$conditional->setStyle($cfStyle);
$conditionals[$sqref][$priority] = $conditional;
}
}
return $conditionals;
}
private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional
{
$conditionType = (string) $attributes->type;
$operatorType = (string) $attributes->operator;
$operands = [];
foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
$operands[] = (string) $cfRuleOperandsXml;
}
$conditional = new Conditional();
$conditional->setConditionType($conditionType);
$conditional->setOperatorType($operatorType);
if (
$conditionType === Conditional::CONDITION_CONTAINSTEXT ||
$conditionType === Conditional::CONDITION_NOTCONTAINSTEXT ||
$conditionType === Conditional::CONDITION_BEGINSWITH ||
$conditionType === Conditional::CONDITION_ENDSWITH ||
$conditionType === Conditional::CONDITION_TIMEPERIOD
) {
$conditional->setText(array_pop($operands) ?? '');
}
$conditional->setConditions($operands);
return $conditional;
}
private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style
{
$cfStyle = new Style(false, true);
if ($extCfRuleXml->dxf) {
$styleXML = $extCfRuleXml->dxf->children();
if ($styleXML->borders) {
$styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
}
if ($styleXML->fill) {
$styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
}
}
return $cfStyle;
}
private function readConditionalStyles(SimpleXMLElement $xmlSheet): array
{
$conditionals = [];
foreach ($xmlSheet->conditionalFormatting as $conditional) {
foreach ($conditional->cfRule as $cfRule) {
if (Conditional::isValidConditionType((string) $cfRule['type']) && (!isset($cfRule['dxfId']) || isset($this->dxfs[(int) ($cfRule['dxfId'])]))) {
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
} elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) {
$conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
}
}
}
return $conditionals;
}
private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void
{
foreach ($conditionals as $cellRangeReference => $cfRules) {
ksort($cfRules);
$conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst);
// Extract all cell references in $cellRangeReference
$cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference)));
foreach ($cellBlocks as $cellBlock) {
$worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
}
}
}
private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
{
$conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
$conditionalStyles = [];
foreach ($cfRules as $cfRule) {
$objConditional = new Conditional();
$objConditional->setConditionType((string) $cfRule['type']);
$objConditional->setOperatorType((string) $cfRule['operator']);
$objConditional->setNoFormatSet(!isset($cfRule['dxfId']));
if ((string) $cfRule['text'] != '') {
$objConditional->setText((string) $cfRule['text']);
} elseif ((string) $cfRule['timePeriod'] != '') {
$objConditional->setText((string) $cfRule['timePeriod']);
}
if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
$objConditional->setStopIfTrue(true);
}
if (count($cfRule->formula) >= 1) {
foreach ($cfRule->formula as $formulax) {
$formula = (string) $formulax;
if ($formula === 'TRUE') {
$objConditional->addCondition(true);
} elseif ($formula === 'FALSE') {
$objConditional->addCondition(false);
} else {
$objConditional->addCondition($formula);
}
}
} else {
$objConditional->addCondition('');
}
if (isset($cfRule->dataBar)) {
$objConditional->setDataBar(
$this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line
);
} elseif (isset($cfRule['dxfId'])) {
$objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
}
$conditionalStyles[] = $objConditional;
}
return $conditionalStyles;
}
/**
* @param SimpleXMLElement|stdClass $cfRule
*/
private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar
{
$dataBar = new ConditionalDataBar();
//dataBar attribute
if (isset($cfRule->dataBar['showValue'])) {
$dataBar->setShowValue((bool) $cfRule->dataBar['showValue']);
}
//dataBar children
//conditionalFormatValueObjects
$cfvoXml = $cfRule->dataBar->cfvo;
$cfvoIndex = 0;
foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) {
if ($cfvoIndex === 0) {
$dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
}
if ($cfvoIndex === 1) {
$dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
}
++$cfvoIndex;
}
//color
if (isset($cfRule->dataBar->color)) {
$dataBar->setColor((string) $cfRule->dataBar->color['rgb']);
}
//extLst
$this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions);
return $dataBar;
}
/**
* @param SimpleXMLElement|stdClass $cfRule
*/
private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void
{
if (isset($cfRule->extLst)) {
$ns = $cfRule->extLst->getNamespaces(true);
foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) {
$extId = (string) $ext->children($ns['x14'])->id;
if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') {
$dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]);
}
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php 0000644 00000015742 15243417764 0020065 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class PageSetup extends BaseParserClass
{
/** @var Worksheet */
private $worksheet;
/** @var ?SimpleXMLElement */
private $worksheetXml;
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load(array $unparsedLoadedData): array
{
$worksheetXml = $this->worksheetXml;
if ($worksheetXml === null) {
return $unparsedLoadedData;
}
$this->margins($worksheetXml, $this->worksheet);
$unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData);
$this->headerFooter($worksheetXml, $this->worksheet);
$this->pageBreaks($worksheetXml, $this->worksheet);
return $unparsedLoadedData;
}
private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
{
if ($xmlSheet->pageMargins) {
$docPageMargins = $worksheet->getPageMargins();
$docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
$docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
$docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
$docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
$docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
$docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
}
}
private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array
{
if ($xmlSheet->pageSetup) {
$docPageSetup = $worksheet->getPageSetup();
if (isset($xmlSheet->pageSetup['orientation'])) {
$docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
}
if (isset($xmlSheet->pageSetup['paperSize'])) {
$docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
}
if (isset($xmlSheet->pageSetup['scale'])) {
$docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
}
if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
$docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
}
if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
$docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
}
if (
isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])
) {
$docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
}
if (isset($xmlSheet->pageSetup['pageOrder'])) {
$docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']);
}
$relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
if (isset($relAttributes['id'])) {
$relid = (string) $relAttributes['id'];
if (substr($relid, -2) !== 'ps') {
$relid .= 'ps';
}
$unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid;
}
}
return $unparsedLoadedData;
}
private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
{
if ($xmlSheet->headerFooter) {
$docHeaderFooter = $worksheet->getHeaderFooter();
if (
isset($xmlSheet->headerFooter['differentOddEven']) &&
self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])
) {
$docHeaderFooter->setDifferentOddEven(true);
} else {
$docHeaderFooter->setDifferentOddEven(false);
}
if (
isset($xmlSheet->headerFooter['differentFirst']) &&
self::boolean((string) $xmlSheet->headerFooter['differentFirst'])
) {
$docHeaderFooter->setDifferentFirst(true);
} else {
$docHeaderFooter->setDifferentFirst(false);
}
if (
isset($xmlSheet->headerFooter['scaleWithDoc']) &&
!self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])
) {
$docHeaderFooter->setScaleWithDocument(false);
} else {
$docHeaderFooter->setScaleWithDocument(true);
}
if (
isset($xmlSheet->headerFooter['alignWithMargins']) &&
!self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])
) {
$docHeaderFooter->setAlignWithMargins(false);
} else {
$docHeaderFooter->setAlignWithMargins(true);
}
$docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
$docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
$docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
$docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
$docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
$docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
}
}
private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
{
if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) {
$this->rowBreaks($xmlSheet, $worksheet);
}
if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) {
$this->columnBreaks($xmlSheet, $worksheet);
}
}
private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
{
foreach ($xmlSheet->rowBreaks->brk as $brk) {
$rowBreakMax = isset($brk['max']) ? ((int) $brk['max']) : -1;
if ($brk['man']) {
$worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW, $rowBreakMax);
}
}
}
private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
{
foreach ($xmlSheet->colBreaks->brk as $brk) {
if ($brk['man']) {
$worksheet->setBreak(
Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1',
Worksheet::BREAK_COLUMN
);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php 0000644 00000021425 15243417764 0022602 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class ColumnAndRowAttributes extends BaseParserClass
{
/** @var Worksheet */
private $worksheet;
/** @var ?SimpleXMLElement */
private $worksheetXml;
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
/**
* Set Worksheet column attributes by attributes array passed.
*
* @param string $columnAddress A, B, ... DX, ...
* @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
*/
private function setColumnAttributes($columnAddress, array $columnAttributes): void
{
if (isset($columnAttributes['xfIndex'])) {
$this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']);
}
if (isset($columnAttributes['visible'])) {
$this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']);
}
if (isset($columnAttributes['collapsed'])) {
$this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']);
}
if (isset($columnAttributes['outlineLevel'])) {
$this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']);
}
if (isset($columnAttributes['width'])) {
$this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']);
}
}
/**
* Set Worksheet row attributes by attributes array passed.
*
* @param int $rowNumber 1, 2, 3, ... 99, ...
* @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
*/
private function setRowAttributes($rowNumber, array $rowAttributes): void
{
if (isset($rowAttributes['xfIndex'])) {
$this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
}
if (isset($rowAttributes['visible'])) {
$this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
}
if (isset($rowAttributes['collapsed'])) {
$this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
}
if (isset($rowAttributes['outlineLevel'])) {
$this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
}
if (isset($rowAttributes['rowHeight'])) {
$this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
}
}
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void
{
if ($this->worksheetXml === null) {
return;
}
$columnsAttributes = [];
$rowsAttributes = [];
if (isset($this->worksheetXml->cols)) {
$columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly);
}
if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) {
$rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
}
if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) {
$readFilter = null;
}
// set columns/rows attributes
$columnsAttributesAreSet = [];
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
if (
$readFilter === null ||
!$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)
) {
if (!isset($columnsAttributesAreSet[$columnCoordinate])) {
$this->setColumnAttributes($columnCoordinate, $columnAttributes);
$columnsAttributesAreSet[$columnCoordinate] = true;
}
}
}
$rowsAttributesAreSet = [];
foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
if (
$readFilter === null ||
!$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)
) {
if (!isset($rowsAttributesAreSet[$rowCoordinate])) {
$this->setRowAttributes($rowCoordinate, $rowAttributes);
$rowsAttributesAreSet[$rowCoordinate] = true;
}
}
}
}
private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool
{
foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
return true;
}
}
return false;
}
private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array
{
$columnAttributes = [];
foreach ($worksheetCols->col as $columnx) {
/** @scrutinizer ignore-call */
$column = $columnx->attributes();
if ($column !== null) {
$startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
$endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
++$endColumn;
for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
$columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
if ((int) ($column['max']) == 16384) {
break;
}
}
}
}
return $columnAttributes;
}
private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array
{
$columnAttributes = [];
if ($column !== null) {
if (isset($column['style']) && !$readDataOnly) {
$columnAttributes['xfIndex'] = (int) $column['style'];
}
if (isset($column['hidden']) && self::boolean($column['hidden'])) {
$columnAttributes['visible'] = false;
}
if (isset($column['collapsed']) && self::boolean($column['collapsed'])) {
$columnAttributes['collapsed'] = true;
}
if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) {
$columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
}
if (isset($column['width'])) {
$columnAttributes['width'] = (float) $column['width'];
}
}
return $columnAttributes;
}
private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool
{
foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
return true;
}
}
return false;
}
private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array
{
$rowAttributes = [];
foreach ($worksheetRow as $rowx) {
/** @scrutinizer ignore-call */
$row = $rowx->attributes();
if ($row !== null) {
if (isset($row['ht']) && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
}
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
$rowAttributes[(int) $row['r']]['visible'] = false;
}
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
$rowAttributes[(int) $row['r']]['collapsed'] = true;
}
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
$rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
}
if (isset($row['s']) && !$readDataOnly) {
$rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
}
}
}
return $rowAttributes;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php 0000644 00000013205 15243417764 0020610 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
class WorkbookView
{
/**
* @var Spreadsheet
*/
private $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
/**
* @param mixed $mainNS
*/
public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void
{
if ($this->spreadsheet->getSheetCount() == 0) {
$this->spreadsheet->createSheet();
}
// Default active sheet index to the first loaded worksheet from the file
$this->spreadsheet->setActiveSheetIndex(0);
$workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView;
if ($readDataOnly !== true && !empty($workbookView)) {
$workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView));
// active sheet index
$activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index
// keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet
if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
$this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]);
}
$this->horizontalScroll($workbookViewAttributes);
$this->verticalScroll($workbookViewAttributes);
$this->sheetTabs($workbookViewAttributes);
$this->minimized($workbookViewAttributes);
$this->autoFilterDateGrouping($workbookViewAttributes);
$this->firstSheet($workbookViewAttributes);
$this->visibility($workbookViewAttributes);
$this->tabRatio($workbookViewAttributes);
}
}
/**
* @param mixed $value
*/
public static function testSimpleXml($value): SimpleXMLElement
{
return ($value instanceof SimpleXMLElement)
? $value
: new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><root></root>');
}
public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement
{
return self::testSimpleXml($value === null ? $value : $value->attributes($ns));
}
/**
* Convert an 'xsd:boolean' XML value to a PHP boolean value.
* A valid 'xsd:boolean' XML value can be one of the following
* four values: 'true', 'false', '1', '0'. It is case sensitive.
*
* Note that just doing '(bool) $xsdBoolean' is not safe,
* since '(bool) "false"' returns true.
*
* @see https://www.w3.org/TR/xmlschema11-2/#boolean
*
* @param string $xsdBoolean An XML string value of type 'xsd:boolean'
*
* @return bool Boolean value
*/
private function castXsdBooleanToBool(string $xsdBoolean): bool
{
if ($xsdBoolean === 'false') {
return false;
}
return (bool) $xsdBoolean;
}
private function horizontalScroll(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->showHorizontalScroll)) {
$showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll;
$this->spreadsheet->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll));
}
}
private function verticalScroll(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->showVerticalScroll)) {
$showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll;
$this->spreadsheet->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll));
}
}
private function sheetTabs(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->showSheetTabs)) {
$showSheetTabs = (string) $workbookViewAttributes->showSheetTabs;
$this->spreadsheet->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs));
}
}
private function minimized(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->minimized)) {
$minimized = (string) $workbookViewAttributes->minimized;
$this->spreadsheet->setMinimized($this->castXsdBooleanToBool($minimized));
}
}
private function autoFilterDateGrouping(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->autoFilterDateGrouping)) {
$autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping;
$this->spreadsheet->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping));
}
}
private function firstSheet(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->firstSheet)) {
$firstSheet = (string) $workbookViewAttributes->firstSheet;
$this->spreadsheet->setFirstSheetIndex((int) $firstSheet);
}
}
private function visibility(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->visibility)) {
$visibility = (string) $workbookViewAttributes->visibility;
$this->spreadsheet->setVisibility($visibility);
}
}
private function tabRatio(SimpleXMLElement $workbookViewAttributes): void
{
if (isset($workbookViewAttributes->tabRatio)) {
$tabRatio = (string) $workbookViewAttributes->tabRatio;
$this->spreadsheet->setTabRatio((int) $tabRatio);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php 0000644 00000011337 15243417764 0021443 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class SheetViewOptions extends BaseParserClass
{
/** @var Worksheet */
private $worksheet;
/** @var ?SimpleXMLElement */
private $worksheetXml;
public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load(bool $readDataOnly, Styles $styleReader): void
{
if ($this->worksheetXml === null) {
return;
}
if (isset($this->worksheetXml->sheetPr)) {
$sheetPr = $this->worksheetXml->sheetPr;
$this->tabColor($sheetPr, $styleReader);
$this->codeName($sheetPr);
$this->outlines($sheetPr);
$this->pageSetup($sheetPr);
}
if (isset($this->worksheetXml->sheetFormatPr)) {
$this->sheetFormat($this->worksheetXml->sheetFormatPr);
}
if (!$readDataOnly && isset($this->worksheetXml->printOptions)) {
$this->printOptions($this->worksheetXml->printOptions);
}
}
private function tabColor(SimpleXMLElement $sheetPr, Styles $styleReader): void
{
if (isset($sheetPr->tabColor)) {
$this->worksheet->getTabColor()->setARGB($styleReader->readColor($sheetPr->tabColor));
}
}
private function codeName(SimpleXMLElement $sheetPrx): void
{
$sheetPr = $sheetPrx->attributes() ?? [];
if (isset($sheetPr['codeName'])) {
$this->worksheet->setCodeName((string) $sheetPr['codeName'], false);
}
}
private function outlines(SimpleXMLElement $sheetPr): void
{
if (isset($sheetPr->outlinePr)) {
$attr = $sheetPr->outlinePr->attributes() ?? [];
if (
isset($attr['summaryRight']) &&
!self::boolean((string) $attr['summaryRight'])
) {
$this->worksheet->setShowSummaryRight(false);
} else {
$this->worksheet->setShowSummaryRight(true);
}
if (
isset($attr['summaryBelow']) &&
!self::boolean((string) $attr['summaryBelow'])
) {
$this->worksheet->setShowSummaryBelow(false);
} else {
$this->worksheet->setShowSummaryBelow(true);
}
}
}
private function pageSetup(SimpleXMLElement $sheetPr): void
{
if (isset($sheetPr->pageSetUpPr)) {
$attr = $sheetPr->pageSetUpPr->attributes() ?? [];
if (
isset($attr['fitToPage']) &&
!self::boolean((string) $attr['fitToPage'])
) {
$this->worksheet->getPageSetup()->setFitToPage(false);
} else {
$this->worksheet->getPageSetup()->setFitToPage(true);
}
}
}
private function sheetFormat(SimpleXMLElement $sheetFormatPrx): void
{
$sheetFormatPr = $sheetFormatPrx->attributes() ?? [];
if (
isset($sheetFormatPr['customHeight']) &&
self::boolean((string) $sheetFormatPr['customHeight']) &&
isset($sheetFormatPr['defaultRowHeight'])
) {
$this->worksheet->getDefaultRowDimension()
->setRowHeight((float) $sheetFormatPr['defaultRowHeight']);
}
if (isset($sheetFormatPr['defaultColWidth'])) {
$this->worksheet->getDefaultColumnDimension()
->setWidth((float) $sheetFormatPr['defaultColWidth']);
}
if (
isset($sheetFormatPr['zeroHeight']) &&
((string) $sheetFormatPr['zeroHeight'] === '1')
) {
$this->worksheet->getDefaultRowDimension()->setZeroHeight(true);
}
}
private function printOptions(SimpleXMLElement $printOptionsx): void
{
$printOptions = $printOptionsx->attributes() ?? [];
if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) {
$this->worksheet->setShowGridlines(true);
}
if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) {
$this->worksheet->setPrintGridlines(true);
}
if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) {
$this->worksheet->getPageSetup()->setHorizontalCentered(true);
}
if (isset($printOptions['verticalCentered']) && self::boolean((string) $printOptions['verticalCentered'])) {
$this->worksheet->getPageSetup()->setVerticalCentered(true);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php 0000644 00000006243 15243417764 0021233 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class DataValidations
{
/** @var Worksheet */
private $worksheet;
/** @var SimpleXMLElement */
private $worksheetXml;
public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml)
{
$this->worksheet = $workSheet;
$this->worksheetXml = $worksheetXml;
}
public function load(): void
{
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
// Uppercase coordinate
$range = strtoupper((string) $dataValidation['sqref']);
$rangeSet = explode(' ', $range);
foreach ($rangeSet as $range) {
if (preg_match('/^[A-Z]{1,3}\\d{1,7}/', $range, $matches) === 1) {
// Ensure left/top row of range exists, thereby
// adjusting high row/column.
$this->worksheet->getCell($matches[0]);
}
}
}
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
// Uppercase coordinate
$range = strtoupper((string) $dataValidation['sqref']);
$rangeSet = explode(' ', $range);
foreach ($rangeSet as $range) {
$stRange = $this->worksheet->shrinkRangeToFit($range);
// Extract all cell references in $range
foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
// Create validation
$docValidation = $this->worksheet->getCell($reference)->getDataValidation();
$docValidation->setType((string) $dataValidation['type']);
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
$docValidation->setOperator((string) $dataValidation['operator']);
$docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN));
// showDropDown is inverted (works as hideDropDown if true)
$docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN));
$docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN));
$docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN));
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
$docValidation->setError((string) $dataValidation['error']);
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
$docValidation->setPrompt((string) $dataValidation['prompt']);
$docValidation->setFormula1((string) $dataValidation->formula1);
$docValidation->setFormula2((string) $dataValidation->formula2);
$docValidation->setSqref($range);
}
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php 0000644 00000002635 15243417764 0017227 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class Theme
{
/**
* Theme Name.
*
* @var string
*/
private $themeName;
/**
* Colour Scheme Name.
*
* @var string
*/
private $colourSchemeName;
/**
* Colour Map.
*
* @var string[]
*/
private $colourMap;
/**
* Create a new Theme.
*
* @param string $themeName
* @param string $colourSchemeName
* @param string[] $colourMap
*/
public function __construct($themeName, $colourSchemeName, $colourMap)
{
// Initialise values
$this->themeName = $themeName;
$this->colourSchemeName = $colourSchemeName;
$this->colourMap = $colourMap;
}
/**
* Not called by Reader, never accessible any other time.
*
* @return string
*
* @codeCoverageIgnore
*/
public function getThemeName()
{
return $this->themeName;
}
/**
* Not called by Reader, never accessible any other time.
*
* @return string
*
* @codeCoverageIgnore
*/
public function getColourSchemeName()
{
return $this->colourSchemeName;
}
/**
* Get colour Map Value by Position.
*
* @param int $index
*
* @return null|string
*/
public function getColourByIndex($index)
{
return $this->colourMap[$index] ?? null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php 0000644 00000011331 15243417764 0020244 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use SimpleXMLElement;
class SheetViews extends BaseParserClass
{
/** @var SimpleXMLElement */
private $sheetViewXml;
/** @var SimpleXMLElement */
private $sheetViewAttributes;
/** @var Worksheet */
private $worksheet;
public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet)
{
$this->sheetViewXml = $sheetViewXml;
$this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes());
$this->worksheet = $workSheet;
}
public function load(): void
{
$this->topLeft();
$this->zoomScale();
$this->view();
$this->gridLines();
$this->headers();
$this->direction();
$this->showZeros();
if (isset($this->sheetViewXml->pane)) {
$this->pane();
}
if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) {
$this->selection();
}
}
private function zoomScale(): void
{
if (isset($this->sheetViewAttributes->zoomScale)) {
$zoomScale = (int) ($this->sheetViewAttributes->zoomScale);
if ($zoomScale <= 0) {
// setZoomScale will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScale = 100;
}
$this->worksheet->getSheetView()->setZoomScale($zoomScale);
}
if (isset($this->sheetViewAttributes->zoomScaleNormal)) {
$zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal);
if ($zoomScaleNormal <= 0) {
// setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
// that is OK when manually creating documents, but we should be able to read all documents
$zoomScaleNormal = 100;
}
$this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
}
}
private function view(): void
{
if (isset($this->sheetViewAttributes->view)) {
$this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view);
}
}
private function topLeft(): void
{
if (isset($this->sheetViewAttributes->topLeftCell)) {
$this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell);
}
}
private function gridLines(): void
{
if (isset($this->sheetViewAttributes->showGridLines)) {
$this->worksheet->setShowGridLines(
self::boolean((string) $this->sheetViewAttributes->showGridLines)
);
}
}
private function headers(): void
{
if (isset($this->sheetViewAttributes->showRowColHeaders)) {
$this->worksheet->setShowRowColHeaders(
self::boolean((string) $this->sheetViewAttributes->showRowColHeaders)
);
}
}
private function direction(): void
{
if (isset($this->sheetViewAttributes->rightToLeft)) {
$this->worksheet->setRightToLeft(
self::boolean((string) $this->sheetViewAttributes->rightToLeft)
);
}
}
private function showZeros(): void
{
if (isset($this->sheetViewAttributes->showZeros)) {
$this->worksheet->getSheetView()->setShowZeros(
self::boolean((string) $this->sheetViewAttributes->showZeros)
);
}
}
private function pane(): void
{
$xSplit = 0;
$ySplit = 0;
$topLeftCell = null;
$paneAttributes = $this->sheetViewXml->pane->attributes();
if (isset($paneAttributes->xSplit)) {
$xSplit = (int) ($paneAttributes->xSplit);
}
if (isset($paneAttributes->ySplit)) {
$ySplit = (int) ($paneAttributes->ySplit);
}
if (isset($paneAttributes->topLeftCell)) {
$topLeftCell = (string) $paneAttributes->topLeftCell;
}
$this->worksheet->freezePane(
Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
$topLeftCell
);
}
private function selection(): void
{
$attributes = $this->sheetViewXml->selection->attributes();
if ($attributes !== null) {
$sqref = (string) $attributes->sqref;
$sqref = explode(' ', $sqref);
$sqref = $sqref[0];
$this->worksheet->setSelectedCells($sqref);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php 0000644 00000010732 15243417764 0020316 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Settings;
use SimpleXMLElement;
class Properties
{
/** @var XmlScanner */
private $securityScanner;
/** @var DocumentProperties */
private $docProps;
public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps)
{
$this->securityScanner = $securityScanner;
$this->docProps = $docProps;
}
/**
* @param mixed $obj
*/
private static function nullOrSimple($obj): ?SimpleXMLElement
{
return ($obj instanceof SimpleXMLElement) ? $obj : null;
}
private function extractPropertyData(string $propertyData): ?SimpleXMLElement
{
// okay to omit namespace because everything will be processed by xpath
$obj = simplexml_load_string(
$this->securityScanner->scan($propertyData),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions()
);
return self::nullOrSimple($obj);
}
public function readCoreProperties(string $propertyData): void
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
$xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS);
$xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS);
$xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2);
$this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
$this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
$this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type
$this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type
$this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
$this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
$this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
$this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords')));
$this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category')));
}
}
public function readExtendedProperties(string $propertyData): void
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
if (isset($xmlCore->Company)) {
$this->docProps->setCompany((string) $xmlCore->Company);
}
if (isset($xmlCore->Manager)) {
$this->docProps->setManager((string) $xmlCore->Manager);
}
if (isset($xmlCore->HyperlinkBase)) {
$this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase);
}
}
}
public function readCustomProperties(string $propertyData): void
{
$xmlCore = $this->extractPropertyData($propertyData);
if (is_object($xmlCore)) {
foreach ($xmlCore as $xmlProperty) {
/** @var SimpleXMLElement $xmlProperty */
$cellDataOfficeAttributes = $xmlProperty->attributes();
if (isset($cellDataOfficeAttributes['name'])) {
$propertyName = (string) $cellDataOfficeAttributes['name'];
$cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
$attributeType = $cellDataOfficeChildren->getName();
$attributeValue = (string) $cellDataOfficeChildren->{$attributeType};
$attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType);
$attributeType = DocumentProperties::convertPropertyType($attributeType);
$this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType);
}
}
}
}
/**
* @param null|array|false $array
* @param mixed $key
*/
private static function getArrayItem($array, $key = 0): ?SimpleXMLElement
{
return is_array($array) ? ($array[$key] ?? null) : null;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php 0000644 00000000676 15243417764 0020724 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
class SharedFormula
{
private string $master;
private string $formula;
public function __construct(string $master, string $formula)
{
$this->master = $master;
$this->formula = $formula;
}
public function master(): string
{
return $this->master;
}
public function formula(): string
{
return $this->formula;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php 0000644 00000012235 15243417764 0020361 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use SimpleXMLElement;
use stdClass;
class PageSettings
{
/**
* @var stdClass
*/
private $printSettings;
public function __construct(SimpleXMLElement $xmlX)
{
$printSettings = $this->pageSetup($xmlX, $this->getPrintDefaults());
$this->printSettings = $this->printSetup($xmlX, $printSettings);
}
public function loadPageSettings(Spreadsheet $spreadsheet): void
{
$spreadsheet->getActiveSheet()->getPageSetup()
->setPaperSize($this->printSettings->paperSize)
->setOrientation($this->printSettings->orientation)
->setScale($this->printSettings->scale)
->setVerticalCentered($this->printSettings->verticalCentered)
->setHorizontalCentered($this->printSettings->horizontalCentered)
->setPageOrder($this->printSettings->printOrder);
$spreadsheet->getActiveSheet()->getPageMargins()
->setTop($this->printSettings->topMargin)
->setHeader($this->printSettings->headerMargin)
->setLeft($this->printSettings->leftMargin)
->setRight($this->printSettings->rightMargin)
->setBottom($this->printSettings->bottomMargin)
->setFooter($this->printSettings->footerMargin);
}
private function getPrintDefaults(): stdClass
{
return (object) [
'paperSize' => 9,
'orientation' => PageSetup::ORIENTATION_DEFAULT,
'scale' => 100,
'horizontalCentered' => false,
'verticalCentered' => false,
'printOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER,
'topMargin' => 0.75,
'headerMargin' => 0.3,
'leftMargin' => 0.7,
'rightMargin' => 0.7,
'bottomMargin' => 0.75,
'footerMargin' => 0.3,
];
}
private function pageSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass
{
if (isset($xmlX->WorksheetOptions->PageSetup)) {
foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) {
foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) {
/** @scrutinizer ignore-call */
$pageSetupAttributes = $pageSetupValue->attributes(Namespaces::URN_EXCEL);
if ($pageSetupAttributes !== null) {
switch ($pageSetupKey) {
case 'Layout':
$this->setLayout($printDefaults, $pageSetupAttributes);
break;
case 'Header':
$printDefaults->headerMargin = (float) $pageSetupAttributes->Margin ?: 1.0;
break;
case 'Footer':
$printDefaults->footerMargin = (float) $pageSetupAttributes->Margin ?: 1.0;
break;
case 'PageMargins':
$this->setMargins($printDefaults, $pageSetupAttributes);
break;
}
}
}
}
}
return $printDefaults;
}
private function printSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass
{
if (isset($xmlX->WorksheetOptions->Print)) {
foreach ($xmlX->WorksheetOptions->Print as $printData) {
foreach ($printData as $printKey => $printValue) {
switch ($printKey) {
case 'LeftToRight':
$printDefaults->printOrder = PageSetup::PAGEORDER_OVER_THEN_DOWN;
break;
case 'PaperSizeIndex':
$printDefaults->paperSize = (int) $printValue ?: 9;
break;
case 'Scale':
$printDefaults->scale = (int) $printValue ?: 100;
break;
}
}
}
}
return $printDefaults;
}
private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void
{
$printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT;
$printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false;
$printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false;
}
private function setMargins(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void
{
$printDefaults->leftMargin = (float) $pageSetupAttributes->Left ?: 1.0;
$printDefaults->rightMargin = (float) $pageSetupAttributes->Right ?: 1.0;
$printDefaults->topMargin = (float) $pageSetupAttributes->Top ?: 1.0;
$printDefaults->bottomMargin = (float) $pageSetupAttributes->Bottom ?: 1.0;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php 0000644 00000004532 15243417764 0017773 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use PhpOffice\PhpSpreadsheet\Style\Font as FontUnderline;
use SimpleXMLElement;
class Font extends StyleBase
{
protected const UNDERLINE_STYLES = [
FontUnderline::UNDERLINE_NONE,
FontUnderline::UNDERLINE_DOUBLE,
FontUnderline::UNDERLINE_DOUBLEACCOUNTING,
FontUnderline::UNDERLINE_SINGLE,
FontUnderline::UNDERLINE_SINGLEACCOUNTING,
];
protected function parseUnderline(array $style, string $styleAttributeValue): array
{
if (self::identifyFixedStyleValue(self::UNDERLINE_STYLES, $styleAttributeValue)) {
$style['font']['underline'] = $styleAttributeValue;
}
return $style;
}
protected function parseVerticalAlign(array $style, string $styleAttributeValue): array
{
if ($styleAttributeValue == 'Superscript') {
$style['font']['superscript'] = true;
}
if ($styleAttributeValue == 'Subscript') {
$style['font']['subscript'] = true;
}
return $style;
}
public function parseStyle(SimpleXMLElement $styleAttributes): array
{
$style = [];
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
$styleAttributeValue = (string) $styleAttributeValue;
switch ($styleAttributeKey) {
case 'FontName':
$style['font']['name'] = $styleAttributeValue;
break;
case 'Size':
$style['font']['size'] = $styleAttributeValue;
break;
case 'Color':
$style['font']['color']['rgb'] = substr($styleAttributeValue, 1);
break;
case 'Bold':
$style['font']['bold'] = $styleAttributeValue === '1';
break;
case 'Italic':
$style['font']['italic'] = $styleAttributeValue === '1';
break;
case 'Underline':
$style = $this->parseUnderline($style, $styleAttributeValue);
break;
case 'VerticalAlign':
$style = $this->parseVerticalAlign($style, $styleAttributeValue);
break;
}
}
return $style;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php 0000644 00000001525 15243417764 0020757 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use SimpleXMLElement;
abstract class StyleBase
{
protected static function identifyFixedStyleValue(array $styleList, string &$styleAttributeValue): bool
{
$returnValue = false;
$styleAttributeValue = strtolower($styleAttributeValue);
foreach ($styleList as $style) {
if ($styleAttributeValue == strtolower($style)) {
$styleAttributeValue = $style;
$returnValue = true;
break;
}
}
return $returnValue;
}
protected static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
{
return ($simple === null) ? new SimpleXMLElement('<xml></xml>') : ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php 0000644 00000001500 15243417764 0021456 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use SimpleXMLElement;
class NumberFormat extends StyleBase
{
public function parseStyle(SimpleXMLElement $styleAttributes): array
{
$style = [];
$fromFormats = ['\-', '\ '];
$toFormats = ['-', ' '];
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
$styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
switch ($styleAttributeValue) {
case 'Short Date':
$styleAttributeValue = 'dd/mm/yyyy';
break;
}
if ($styleAttributeValue > '') {
$style['numberFormat']['formatCode'] = $styleAttributeValue;
}
}
return $style;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php 0000644 00000003563 15243417764 0021006 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use PhpOffice\PhpSpreadsheet\Style\Alignment as AlignmentStyles;
use SimpleXMLElement;
class Alignment extends StyleBase
{
protected const VERTICAL_ALIGNMENT_STYLES = [
AlignmentStyles::VERTICAL_BOTTOM,
AlignmentStyles::VERTICAL_TOP,
AlignmentStyles::VERTICAL_CENTER,
AlignmentStyles::VERTICAL_JUSTIFY,
];
protected const HORIZONTAL_ALIGNMENT_STYLES = [
AlignmentStyles::HORIZONTAL_GENERAL,
AlignmentStyles::HORIZONTAL_LEFT,
AlignmentStyles::HORIZONTAL_RIGHT,
AlignmentStyles::HORIZONTAL_CENTER,
AlignmentStyles::HORIZONTAL_CENTER_CONTINUOUS,
AlignmentStyles::HORIZONTAL_JUSTIFY,
];
public function parseStyle(SimpleXMLElement $styleAttributes): array
{
$style = [];
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) {
$styleAttributeValue = (string) $styleAttributeValue;
switch ($styleAttributeKey) {
case 'Vertical':
if (self::identifyFixedStyleValue(self::VERTICAL_ALIGNMENT_STYLES, $styleAttributeValue)) {
$style['alignment']['vertical'] = $styleAttributeValue;
}
break;
case 'Horizontal':
if (self::identifyFixedStyleValue(self::HORIZONTAL_ALIGNMENT_STYLES, $styleAttributeValue)) {
$style['alignment']['horizontal'] = $styleAttributeValue;
}
break;
case 'WrapText':
$style['alignment']['wrapText'] = true;
break;
case 'Rotate':
$style['alignment']['textRotation'] = $styleAttributeValue;
break;
}
}
return $style;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php 0000644 00000005207 15243417764 0017753 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use PhpOffice\PhpSpreadsheet\Style\Fill as FillStyles;
use SimpleXMLElement;
class Fill extends StyleBase
{
/**
* @var array
*/
public const FILL_MAPPINGS = [
'fillType' => [
'solid' => FillStyles::FILL_SOLID,
'gray75' => FillStyles::FILL_PATTERN_DARKGRAY,
'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY,
'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY,
'gray125' => FillStyles::FILL_PATTERN_GRAY125,
'gray0625' => FillStyles::FILL_PATTERN_GRAY0625,
'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe
'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe
'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe
'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL,
'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL,
'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP,
'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN,
'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
],
];
public function parseStyle(SimpleXMLElement $styleAttributes): array
{
$style = [];
foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) {
$styleAttributeValue = (string) $styleAttributeValuex;
switch ($styleAttributeKey) {
case 'Color':
$style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1);
$style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
break;
case 'PatternColor':
$style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
break;
case 'Pattern':
$lcStyleAttributeValue = strtolower((string) $styleAttributeValue);
$style['fill']['fillType']
= self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE;
break;
}
}
return $style;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php 0000644 00000007235 15243417764 0020305 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
use PhpOffice\PhpSpreadsheet\Style\Border as BorderStyle;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use SimpleXMLElement;
class Border extends StyleBase
{
protected const BORDER_POSITIONS = [
'top',
'left',
'bottom',
'right',
];
/**
* @var array
*/
public const BORDER_MAPPINGS = [
'borderStyle' => [
'1continuous' => BorderStyle::BORDER_THIN,
'1dash' => BorderStyle::BORDER_DASHED,
'1dashdot' => BorderStyle::BORDER_DASHDOT,
'1dashdotdot' => BorderStyle::BORDER_DASHDOTDOT,
'1dot' => BorderStyle::BORDER_DOTTED,
'1double' => BorderStyle::BORDER_DOUBLE,
'2continuous' => BorderStyle::BORDER_MEDIUM,
'2dash' => BorderStyle::BORDER_MEDIUMDASHED,
'2dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT,
'2dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT,
'2dot' => BorderStyle::BORDER_DOTTED,
'2double' => BorderStyle::BORDER_DOUBLE,
'3continuous' => BorderStyle::BORDER_THICK,
'3dash' => BorderStyle::BORDER_MEDIUMDASHED,
'3dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT,
'3dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT,
'3dot' => BorderStyle::BORDER_DOTTED,
'3double' => BorderStyle::BORDER_DOUBLE,
],
];
public function parseStyle(SimpleXMLElement $styleData, array $namespaces): array
{
$style = [];
$diagonalDirection = '';
$borderPosition = '';
foreach ($styleData->Border as $borderStyle) {
$borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']);
$thisBorder = [];
$styleType = (string) $borderAttributes->Weight;
$styleType .= strtolower((string) $borderAttributes->LineStyle);
$thisBorder['borderStyle'] = self::BORDER_MAPPINGS['borderStyle'][$styleType] ?? BorderStyle::BORDER_NONE;
foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) {
$borderStyleValue = (string) $borderStyleValuex;
switch ($borderStyleKey) {
case 'Position':
[$borderPosition, $diagonalDirection] =
$this->parsePosition($borderStyleValue, $diagonalDirection);
break;
case 'Color':
$borderColour = substr($borderStyleValue, 1);
$thisBorder['color']['rgb'] = $borderColour;
break;
}
}
if ($borderPosition) {
$style['borders'][$borderPosition] = $thisBorder;
} elseif ($diagonalDirection) {
$style['borders']['diagonalDirection'] = $diagonalDirection;
$style['borders']['diagonal'] = $thisBorder;
}
}
return $style;
}
protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array
{
$borderStyleValue = strtolower($borderStyleValue);
if (in_array($borderStyleValue, self::BORDER_POSITIONS)) {
$borderPosition = $borderStyleValue;
} elseif ($borderStyleValue === 'diagonalleft') {
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN;
} elseif ($borderStyleValue === 'diagonalright') {
$diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP;
}
return [$borderPosition ?? null, $diagonalDirection];
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php 0000644 00000017011 15243417764 0021030 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Cell\AddressHelper;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
class DataValidations
{
private const OPERATOR_MAPPINGS = [
'between' => DataValidation::OPERATOR_BETWEEN,
'equal' => DataValidation::OPERATOR_EQUAL,
'greater' => DataValidation::OPERATOR_GREATERTHAN,
'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL,
'less' => DataValidation::OPERATOR_LESSTHAN,
'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL,
'notbetween' => DataValidation::OPERATOR_NOTBETWEEN,
'notequal' => DataValidation::OPERATOR_NOTEQUAL,
];
private const TYPE_MAPPINGS = [
'textlength' => DataValidation::TYPE_TEXTLENGTH,
];
private int $thisRow = 0;
private int $thisColumn = 0;
private function replaceR1C1(array $matches): string
{
return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false);
}
public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void
{
$xmlX = $worksheet->children(Namespaces::URN_EXCEL);
$sheet = $spreadsheet->getActiveSheet();
/** @var callable */
$pregCallback = [$this, 'replaceR1C1'];
foreach ($xmlX->DataValidation as $dataValidation) {
$cells = [];
$validation = new DataValidation();
// set defaults
$validation->setShowDropDown(true);
$validation->setShowInputMessage(true);
$validation->setShowErrorMessage(true);
$validation->setShowDropDown(true);
$this->thisRow = 1;
$this->thisColumn = 1;
foreach ($dataValidation as $tagName => $tagValue) {
$tagValue = (string) $tagValue;
$tagValueLower = strtolower($tagValue);
switch ($tagName) {
case 'Range':
foreach (explode(',', $tagValue) as $range) {
$cell = '';
if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
// range
$firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
. $selectionMatches[1];
$cell = $firstCell
. ':'
. Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
. $selectionMatches[3];
$this->thisRow = (int) $selectionMatches[1];
$this->thisColumn = (int) $selectionMatches[2];
$sheet->getCell($firstCell);
} elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
// cell
$cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
. $selectionMatches[1];
$sheet->getCell($cell);
$this->thisRow = (int) $selectionMatches[1];
$this->thisColumn = (int) $selectionMatches[2];
} elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) {
// column
$firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
. '1';
$cell = $firstCell
. ':'
. Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
. ((string) AddressRange::MAX_ROW);
$this->thisColumn = (int) $selectionMatches[1];
$sheet->getCell($firstCell);
} elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) {
// row
$firstCell = 'A'
. $selectionMatches[1];
$cell = $firstCell
. ':'
. AddressRange::MAX_COLUMN
. $selectionMatches[1];
$this->thisRow = (int) $selectionMatches[1];
$sheet->getCell($firstCell);
}
$validation->setSqref($cell);
$stRange = $sheet->shrinkRangeToFit($cell);
$cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange));
}
break;
case 'Type':
$validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower);
break;
case 'Qualifier':
$validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower);
break;
case 'InputTitle':
$validation->setPromptTitle($tagValue);
break;
case 'InputMessage':
$validation->setPrompt($tagValue);
break;
case 'InputHide':
$validation->setShowInputMessage(false);
break;
case 'ErrorStyle':
$validation->setErrorStyle($tagValueLower);
break;
case 'ErrorTitle':
$validation->setErrorTitle($tagValue);
break;
case 'ErrorMessage':
$validation->setError($tagValue);
break;
case 'ErrorHide':
$validation->setShowErrorMessage(false);
break;
case 'ComboHide':
$validation->setShowDropDown(false);
break;
case 'UseBlank':
$validation->setAllowBlank(true);
break;
case 'CellRangeList':
// FIXME missing FIXME
break;
case 'Min':
case 'Value':
$tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
$validation->setFormula1($tagValue);
break;
case 'Max':
$tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
$validation->setFormula2($tagValue);
break;
}
}
foreach ($cells as $cell) {
$sheet->getCell($cell)->setDataValidation(clone $validation);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php 0000644 00000011556 15243417764 0020125 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
class Properties
{
/**
* @var Spreadsheet
*/
protected $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
public function readProperties(SimpleXMLElement $xml, array $namespaces): void
{
$this->readStandardProperties($xml);
$this->readCustomProperties($xml, $namespaces);
}
protected function readStandardProperties(SimpleXMLElement $xml): void
{
if (isset($xml->DocumentProperties[0])) {
$docProps = $this->spreadsheet->getProperties();
foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) {
$propertyValue = (string) $propertyValue;
$this->processStandardProperty($docProps, $propertyName, $propertyValue);
}
}
}
protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void
{
if (isset($xml->CustomDocumentProperties) && is_iterable($xml->CustomDocumentProperties[0])) {
$docProps = $this->spreadsheet->getProperties();
foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) {
$propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']);
$propertyName = (string) preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName);
$this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes);
}
}
}
protected function processStandardProperty(
DocumentProperties $docProps,
string $propertyName,
string $stringValue
): void {
switch ($propertyName) {
case 'Title':
$docProps->setTitle($stringValue);
break;
case 'Subject':
$docProps->setSubject($stringValue);
break;
case 'Author':
$docProps->setCreator($stringValue);
break;
case 'Created':
$docProps->setCreated($stringValue);
break;
case 'LastAuthor':
$docProps->setLastModifiedBy($stringValue);
break;
case 'LastSaved':
$docProps->setModified($stringValue);
break;
case 'Company':
$docProps->setCompany($stringValue);
break;
case 'Category':
$docProps->setCategory($stringValue);
break;
case 'Manager':
$docProps->setManager($stringValue);
break;
case 'HyperlinkBase':
$docProps->setHyperlinkBase($stringValue);
break;
case 'Keywords':
$docProps->setKeywords($stringValue);
break;
case 'Description':
$docProps->setDescription($stringValue);
break;
}
}
protected function processCustomProperty(
DocumentProperties $docProps,
string $propertyName,
?SimpleXMLElement $propertyValue,
SimpleXMLElement $propertyAttributes
): void {
switch ((string) $propertyAttributes) {
case 'boolean':
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
$propertyValue = (bool) (string) $propertyValue;
break;
case 'integer':
$propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER;
$propertyValue = (int) $propertyValue;
break;
case 'float':
$propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT;
$propertyValue = (float) $propertyValue;
break;
case 'dateTime.tz':
case 'dateTime.iso8601tz':
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
$propertyValue = trim((string) $propertyValue);
break;
default:
$propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
$propertyValue = trim((string) $propertyValue);
break;
}
$docProps->setCustomProperty($propertyName, $propertyValue, $propertyType);
}
protected function hex2str(array $hex): string
{
return mb_chr((int) hexdec($hex[1]), 'UTF-8');
}
private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
{
return ($simple === null) ? new SimpleXMLElement('<xml></xml>') : ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php 0000644 00000010064 15243417764 0017062 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use SimpleXMLElement;
class Style
{
/**
* Formats.
*
* @var array
*/
protected $styles = [];
public function parseStyles(SimpleXMLElement $xml, array $namespaces): array
{
if (!isset($xml->Styles) || !is_iterable($xml->Styles[0])) {
return [];
}
$alignmentStyleParser = new Style\Alignment();
$borderStyleParser = new Style\Border();
$fontStyleParser = new Style\Font();
$fillStyleParser = new Style\Fill();
$numberFormatStyleParser = new Style\NumberFormat();
foreach ($xml->Styles[0] as $style) {
$style_ss = self::getAttributes($style, $namespaces['ss']);
$styleID = (string) $style_ss['ID'];
$this->styles[$styleID] = $this->styles['Default'] ?? [];
$alignment = $border = $font = $fill = $numberFormat = $protection = [];
foreach ($style as $styleType => $styleDatax) {
$styleData = self::getSxml($styleDatax);
$styleAttributes = $styleData->attributes($namespaces['ss']);
switch ($styleType) {
case 'Alignment':
if ($styleAttributes) {
$alignment = $alignmentStyleParser->parseStyle($styleAttributes);
}
break;
case 'Borders':
$border = $borderStyleParser->parseStyle($styleData, $namespaces);
break;
case 'Font':
if ($styleAttributes) {
$font = $fontStyleParser->parseStyle($styleAttributes);
}
break;
case 'Interior':
if ($styleAttributes) {
$fill = $fillStyleParser->parseStyle($styleAttributes);
}
break;
case 'NumberFormat':
if ($styleAttributes) {
$numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes);
}
break;
case 'Protection':
$locked = $hidden = null;
$styleAttributesP = $styleData->attributes($namespaces['x']);
if (isset($styleAttributes['Protected'])) {
$locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
}
if (isset($styleAttributesP['HideFormula'])) {
$hidden = ((bool) (string) $styleAttributesP['HideFormula']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
}
if ($locked !== null || $hidden !== null) {
$protection['protection'] = [];
if ($locked !== null) {
$protection['protection']['locked'] = $locked;
}
if ($hidden !== null) {
$protection['protection']['hidden'] = $hidden;
}
}
break;
}
}
$this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat, $protection);
}
return $this->styles;
}
private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement
{
return ($simple === null) ? new SimpleXMLElement('<xml></xml>') : ($simple->attributes($node) ?? new SimpleXMLElement('<xml></xml>'));
}
private static function getSxml(?SimpleXMLElement $simple): SimpleXMLElement
{
return ($simple !== null) ? $simple : new SimpleXMLElement('<xml></xml>');
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php 0000644 00000027341 15243417764 0020264 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use SimpleXMLElement;
class Styles
{
/**
* @var Spreadsheet
*/
private $spreadsheet;
/**
* @var bool
*/
protected $readDataOnly = false;
/** @var array */
public static $mappings = [
'borderStyle' => [
'0' => Border::BORDER_NONE,
'1' => Border::BORDER_THIN,
'2' => Border::BORDER_MEDIUM,
'3' => Border::BORDER_SLANTDASHDOT,
'4' => Border::BORDER_DASHED,
'5' => Border::BORDER_THICK,
'6' => Border::BORDER_DOUBLE,
'7' => Border::BORDER_DOTTED,
'8' => Border::BORDER_MEDIUMDASHED,
'9' => Border::BORDER_DASHDOT,
'10' => Border::BORDER_MEDIUMDASHDOT,
'11' => Border::BORDER_DASHDOTDOT,
'12' => Border::BORDER_MEDIUMDASHDOTDOT,
'13' => Border::BORDER_MEDIUMDASHDOTDOT,
],
'fillType' => [
'1' => Fill::FILL_SOLID,
'2' => Fill::FILL_PATTERN_DARKGRAY,
'3' => Fill::FILL_PATTERN_MEDIUMGRAY,
'4' => Fill::FILL_PATTERN_LIGHTGRAY,
'5' => Fill::FILL_PATTERN_GRAY125,
'6' => Fill::FILL_PATTERN_GRAY0625,
'7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
'8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe
'9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe
'10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe
'11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
'12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
'13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
'14' => Fill::FILL_PATTERN_LIGHTVERTICAL,
'15' => Fill::FILL_PATTERN_LIGHTUP,
'16' => Fill::FILL_PATTERN_LIGHTDOWN,
'17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
'18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
],
'horizontal' => [
'1' => Alignment::HORIZONTAL_GENERAL,
'2' => Alignment::HORIZONTAL_LEFT,
'4' => Alignment::HORIZONTAL_RIGHT,
'8' => Alignment::HORIZONTAL_CENTER,
'16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
'32' => Alignment::HORIZONTAL_JUSTIFY,
'64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS,
],
'underline' => [
'1' => Font::UNDERLINE_SINGLE,
'2' => Font::UNDERLINE_DOUBLE,
'3' => Font::UNDERLINE_SINGLEACCOUNTING,
'4' => Font::UNDERLINE_DOUBLEACCOUNTING,
],
'vertical' => [
'1' => Alignment::VERTICAL_TOP,
'2' => Alignment::VERTICAL_BOTTOM,
'4' => Alignment::VERTICAL_CENTER,
'8' => Alignment::VERTICAL_JUSTIFY,
],
];
public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly)
{
$this->spreadsheet = $spreadsheet;
$this->readDataOnly = $readDataOnly;
}
public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void
{
if ($sheet->Styles->StyleRegion !== null) {
$this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol);
}
}
private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void
{
foreach ($styleRegion as $style) {
/** @scrutinizer ignore-call */
$styleAttributes = $style->attributes();
if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) {
$cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow);
$styleAttributes = $style->Style->attributes();
$styleArray = [];
// We still set the number format mask for date/time values, even if readDataOnly is true
// so that we can identify whether a float is a float or a date value
$formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null;
if ($formatCode && Date::isDateTimeFormatCode($formatCode)) {
$styleArray['numberFormat']['formatCode'] = $formatCode;
}
if ($this->readDataOnly === false && $styleAttributes !== null) {
// If readDataOnly is false, we set all formatting information
$styleArray['numberFormat']['formatCode'] = $formatCode;
$styleArray = $this->readStyle($styleArray, $styleAttributes, /** @scrutinizer ignore-type */ $style);
}
$this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray);
}
}
}
private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void
{
if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) {
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH;
} elseif (isset($srssb->Diagonal)) {
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes());
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP;
} elseif (isset($srssb->{'Rev-Diagonal'})) {
$styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes());
$styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN;
}
}
private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void
{
$ucDirection = ucfirst($direction);
if (isset($srssb->$ucDirection)) {
$styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes());
}
}
private function calcRotation(SimpleXMLElement $styleAttributes): int
{
$rotation = (int) $styleAttributes->Rotation;
if ($rotation >= 270 && $rotation <= 360) {
$rotation -= 360;
}
$rotation = (abs($rotation) > 90) ? 0 : $rotation;
return $rotation;
}
private static function addStyle(array &$styleArray, string $key, string $value): void
{
if (array_key_exists($value, self::$mappings[$key])) {
$styleArray[$key] = self::$mappings[$key][$value];
}
}
private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void
{
if (array_key_exists($value, self::$mappings[$key])) {
$styleArray[$key1][$key] = self::$mappings[$key][$value];
}
}
private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array
{
$styleArray = [];
if ($borderAttributes !== null) {
if (isset($borderAttributes['Color'])) {
$styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']);
}
self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']);
}
return $styleArray;
}
private static function parseGnumericColour(string $gnmColour): string
{
[$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour);
$gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2);
$gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2);
$gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2);
return $gnmR . $gnmG . $gnmB;
}
private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void
{
$RGB = self::parseGnumericColour((string) $styleAttributes['Fore']);
$styleArray['font']['color']['rgb'] = $RGB;
$RGB = self::parseGnumericColour((string) $styleAttributes['Back']);
$shade = (string) $styleAttributes['Shade'];
if (($RGB !== '000000') || ($shade !== '0')) {
$RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']);
if ($shade === '1') {
$styleArray['fill']['startColor']['rgb'] = $RGB;
$styleArray['fill']['endColor']['rgb'] = $RGB2;
} else {
$styleArray['fill']['endColor']['rgb'] = $RGB;
$styleArray['fill']['startColor']['rgb'] = $RGB2;
}
self::addStyle2($styleArray, 'fill', 'fillType', $shade);
}
}
private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string
{
$startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1);
$startRow = $styleAttributes['startRow'] + 1;
$endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol'];
$endColumn = Coordinate::stringFromColumnIndex($endColumn + 1);
$endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']);
$cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow;
return $cellRange;
}
private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array
{
self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']);
self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']);
$styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1';
$styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes);
$styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1';
$styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0;
$this->addColors($styleArray, $styleAttributes);
$fontAttributes = $style->Style->Font->attributes();
if ($fontAttributes !== null) {
$styleArray['font']['name'] = (string) $style->Style->Font;
$styleArray['font']['size'] = (int) ($fontAttributes['Unit']);
$styleArray['font']['bold'] = $fontAttributes['Bold'] == '1';
$styleArray['font']['italic'] = $fontAttributes['Italic'] == '1';
$styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1';
self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']);
switch ($fontAttributes['Script']) {
case '1':
$styleArray['font']['superscript'] = true;
break;
case '-1':
$styleArray['font']['subscript'] = true;
break;
}
}
if (isset($style->Style->StyleBorder)) {
$srssb = $style->Style->StyleBorder;
$this->addBorderStyle($srssb, $styleArray, 'top');
$this->addBorderStyle($srssb, $styleArray, 'bottom');
$this->addBorderStyle($srssb, $styleArray, 'left');
$this->addBorderStyle($srssb, $styleArray, 'right');
$this->addBorderDiagonal($srssb, $styleArray);
}
// TO DO
/*
if (isset($style->Style->HyperLink)) {
$hyperlink = $style->Style->HyperLink->attributes();
}
*/
return $styleArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php 0000644 00000012177 15243417764 0020677 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\PageMargins;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup as WorksheetPageSetup;
use SimpleXMLElement;
class PageSetup
{
/**
* @var Spreadsheet
*/
private $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
public function printInformation(SimpleXMLElement $sheet): self
{
if (isset($sheet->PrintInformation, $sheet->PrintInformation[0])) {
$printInformation = $sheet->PrintInformation[0];
$setup = $this->spreadsheet->getActiveSheet()->getPageSetup();
$attributes = $printInformation->Scale->attributes();
if (isset($attributes['percentage'])) {
$setup->setScale((int) $attributes['percentage']);
}
$pageOrder = (string) $printInformation->order;
if ($pageOrder === 'r_then_d') {
$setup->setPageOrder(WorksheetPageSetup::PAGEORDER_OVER_THEN_DOWN);
} elseif ($pageOrder === 'd_then_r') {
$setup->setPageOrder(WorksheetPageSetup::PAGEORDER_DOWN_THEN_OVER);
}
$orientation = (string) $printInformation->orientation;
if ($orientation !== '') {
$setup->setOrientation($orientation);
}
$attributes = $printInformation->hcenter->attributes();
if (isset($attributes['value'])) {
$setup->setHorizontalCentered((bool) (string) $attributes['value']);
}
$attributes = $printInformation->vcenter->attributes();
if (isset($attributes['value'])) {
$setup->setVerticalCentered((bool) (string) $attributes['value']);
}
}
return $this;
}
public function sheetMargins(SimpleXMLElement $sheet): self
{
if (isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) {
$marginSet = [
// Default Settings
'top' => 0.75,
'header' => 0.3,
'left' => 0.7,
'right' => 0.7,
'bottom' => 0.75,
'footer' => 0.3,
];
$marginSet = $this->buildMarginSet($sheet, $marginSet);
$this->adjustMargins($marginSet);
}
return $this;
}
private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array
{
foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) {
$marginAttributes = $margin->attributes();
$marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt
// Convert value in points to inches
$marginSize = PageMargins::fromPoints((float) $marginSize);
$marginSet[$key] = $marginSize;
}
return $marginSet;
}
private function adjustMargins(array $marginSet): void
{
foreach ($marginSet as $key => $marginSize) {
// Gnumeric is quirky in the way it displays the header/footer values:
// header is actually the sum of top and header; footer is actually the sum of bottom and footer
// then top is actually the header value, and bottom is actually the footer value
switch ($key) {
case 'left':
case 'right':
$this->sheetMargin($key, $marginSize);
break;
case 'top':
$this->sheetMargin($key, $marginSet['header'] ?? 0);
break;
case 'bottom':
$this->sheetMargin($key, $marginSet['footer'] ?? 0);
break;
case 'header':
$this->sheetMargin($key, ($marginSet['top'] ?? 0) - $marginSize);
break;
case 'footer':
$this->sheetMargin($key, ($marginSet['bottom'] ?? 0) - $marginSize);
break;
}
}
}
private function sheetMargin(string $key, float $marginSize): void
{
switch ($key) {
case 'top':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize);
break;
case 'bottom':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize);
break;
case 'left':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize);
break;
case 'right':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize);
break;
case 'header':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize);
break;
case 'footer':
$this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
break;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php 0000644 00000012463 15243417764 0021134 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Reader\Gnumeric;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use SimpleXMLElement;
class Properties
{
/**
* @var Spreadsheet
*/
protected $spreadsheet;
public function __construct(Spreadsheet $spreadsheet)
{
$this->spreadsheet = $spreadsheet;
}
private function docPropertiesOld(SimpleXMLElement $gnmXML): void
{
$docProps = $this->spreadsheet->getProperties();
foreach ($gnmXML->Summary->Item as $summaryItem) {
$propertyName = $summaryItem->name;
$propertyValue = $summaryItem->{'val-string'};
switch ($propertyName) {
case 'title':
$docProps->setTitle(trim($propertyValue));
break;
case 'comments':
$docProps->setDescription(trim($propertyValue));
break;
case 'keywords':
$docProps->setKeywords(trim($propertyValue));
break;
case 'category':
$docProps->setCategory(trim($propertyValue));
break;
case 'manager':
$docProps->setManager(trim($propertyValue));
break;
case 'author':
$docProps->setCreator(trim($propertyValue));
$docProps->setLastModifiedBy(trim($propertyValue));
break;
case 'company':
$docProps->setCompany(trim($propertyValue));
break;
}
}
}
private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void
{
$docProps = $this->spreadsheet->getProperties();
foreach ($officePropertyDC as $propertyName => $propertyValue) {
$propertyValue = trim((string) $propertyValue);
switch ($propertyName) {
case 'title':
$docProps->setTitle($propertyValue);
break;
case 'subject':
$docProps->setSubject($propertyValue);
break;
case 'creator':
$docProps->setCreator($propertyValue);
$docProps->setLastModifiedBy($propertyValue);
break;
case 'date':
$creationDate = $propertyValue;
$docProps->setModified($creationDate);
break;
case 'description':
$docProps->setDescription($propertyValue);
break;
}
}
}
private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void
{
$docProps = $this->spreadsheet->getProperties();
foreach ($officePropertyMeta as $propertyName => $propertyValue) {
if ($propertyValue !== null) {
$attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META);
$propertyValue = trim((string) $propertyValue);
switch ($propertyName) {
case 'keyword':
$docProps->setKeywords($propertyValue);
break;
case 'initial-creator':
$docProps->setCreator($propertyValue);
$docProps->setLastModifiedBy($propertyValue);
break;
case 'creation-date':
$creationDate = $propertyValue;
$docProps->setCreated($creationDate);
break;
case 'user-defined':
if ($attributes) {
[, $attrName] = explode(':', (string) $attributes['name']);
$this->userDefinedProperties($attrName, $propertyValue);
}
break;
}
}
}
}
private function userDefinedProperties(string $attrName, string $propertyValue): void
{
$docProps = $this->spreadsheet->getProperties();
switch ($attrName) {
case 'publisher':
$docProps->setCompany($propertyValue);
break;
case 'category':
$docProps->setCategory($propertyValue);
break;
case 'manager':
$docProps->setManager($propertyValue);
break;
}
}
public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void
{
$officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE);
if (!empty($officeXML)) {
$officeDocXML = $officeXML->{'document-meta'};
$officeDocMetaXML = $officeDocXML->meta;
foreach ($officeDocMetaXML as $officePropertyData) {
$officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC);
$this->docPropertiesDC($officePropertyDC);
$officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META);
$this->docPropertiesMeta($officePropertyMeta);
}
} elseif (isset($gnmXML->Summary)) {
$this->docPropertiesOld($gnmXML);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/DefinedName.php 0000644 00000014647 15243417764 0016172 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
abstract class DefinedName
{
protected const REGEXP_IDENTIFY_FORMULA = '[^_\p{N}\p{L}:, \$\'!]';
/**
* Name.
*
* @var string
*/
protected $name;
/**
* Worksheet on which the defined name can be resolved.
*
* @var ?Worksheet
*/
protected $worksheet;
/**
* Value of the named object.
*
* @var string
*/
protected $value;
/**
* Is the defined named local? (i.e. can only be used on $this->worksheet).
*
* @var bool
*/
protected $localOnly;
/**
* Scope.
*
* @var ?Worksheet
*/
protected $scope;
/**
* Whether this is a named range or a named formula.
*
* @var bool
*/
protected $isFormula;
/**
* Create a new Defined Name.
*/
public function __construct(
string $name,
?Worksheet $worksheet = null,
?string $value = null,
bool $localOnly = false,
?Worksheet $scope = null
) {
if ($worksheet === null) {
$worksheet = $scope;
}
// Set local members
$this->name = $name;
$this->worksheet = $worksheet;
$this->value = (string) $value;
$this->localOnly = $localOnly;
// If local only, then the scope will be set to worksheet unless a scope is explicitly set
$this->scope = ($localOnly === true) ? (($scope === null) ? $worksheet : $scope) : null;
// If the range string contains characters that aren't associated with the range definition (A-Z,1-9
// for cell references, and $, or the range operators (colon comma or space), quotes and ! for
// worksheet names
// then this is treated as a named formula, and not a named range
$this->isFormula = self::testIfFormula($this->value);
}
/**
* Create a new defined name, either a range or a formula.
*/
public static function createInstance(
string $name,
?Worksheet $worksheet = null,
?string $value = null,
bool $localOnly = false,
?Worksheet $scope = null
): self {
$value = (string) $value;
$isFormula = self::testIfFormula($value);
if ($isFormula) {
return new NamedFormula($name, $worksheet, $value, $localOnly, $scope);
}
return new NamedRange($name, $worksheet, $value, $localOnly, $scope);
}
public static function testIfFormula(string $value): bool
{
if (substr($value, 0, 1) === '=') {
$value = substr($value, 1);
}
if (is_numeric($value)) {
return true;
}
$segMatcher = false;
foreach (explode("'", $value) as $subVal) {
// Only test in alternate array entries (the non-quoted blocks)
$segMatcher = $segMatcher === false;
if (
$segMatcher &&
(preg_match('/' . self::REGEXP_IDENTIFY_FORMULA . '/miu', $subVal))
) {
return true;
}
}
return false;
}
/**
* Get name.
*/
public function getName(): string
{
return $this->name;
}
/**
* Set name.
*/
public function setName(string $name): self
{
if (!empty($name)) {
// Old title
$oldTitle = $this->name;
// Re-attach
if ($this->worksheet !== null) {
$this->worksheet->getParentOrThrow()->removeNamedRange($this->name, $this->worksheet);
}
$this->name = $name;
if ($this->worksheet !== null) {
$this->worksheet->getParentOrThrow()->addDefinedName($this);
}
if ($this->worksheet !== null) {
// New title
$newTitle = $this->name;
ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParentOrThrow(), $oldTitle, $newTitle);
}
}
return $this;
}
/**
* Get worksheet.
*/
public function getWorksheet(): ?Worksheet
{
return $this->worksheet;
}
/**
* Set worksheet.
*/
public function setWorksheet(?Worksheet $worksheet): self
{
$this->worksheet = $worksheet;
return $this;
}
/**
* Get range or formula value.
*/
public function getValue(): string
{
return $this->value;
}
/**
* Set range or formula value.
*/
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
/**
* Get localOnly.
*/
public function getLocalOnly(): bool
{
return $this->localOnly;
}
/**
* Set localOnly.
*/
public function setLocalOnly(bool $localScope): self
{
$this->localOnly = $localScope;
$this->scope = $localScope ? $this->worksheet : null;
return $this;
}
/**
* Get scope.
*/
public function getScope(): ?Worksheet
{
return $this->scope;
}
/**
* Set scope.
*/
public function setScope(?Worksheet $worksheet): self
{
$this->scope = $worksheet;
$this->localOnly = $worksheet !== null;
return $this;
}
/**
* Identify whether this is a named range or a named formula.
*/
public function isFormula(): bool
{
return $this->isFormula;
}
/**
* Resolve a named range to a regular cell range or formula.
*/
public static function resolveName(string $definedName, Worksheet $worksheet, string $sheetName = ''): ?self
{
if ($sheetName === '') {
$worksheet2 = $worksheet;
} else {
$worksheet2 = $worksheet->getParentOrThrow()->getSheetByName($sheetName);
if ($worksheet2 === null) {
return null;
}
}
return $worksheet->getParentOrThrow()->getDefinedName($definedName, $worksheet2);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php 0000644 00000004223 15243417764 0016760 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class TimeZone
{
/**
* Default Timezone used for date/time conversions.
*
* @var string
*/
protected static $timezone = 'UTC';
/**
* Validate a Timezone name.
*
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
private static function validateTimeZone(string $timezoneName): bool
{
return in_array($timezoneName, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC), true);
}
/**
* Set the Default Timezone used for date/time conversions.
*
* @param string $timezoneName Time zone (e.g. 'Europe/London')
*
* @return bool Success or failure
*/
public static function setTimeZone(string $timezoneName): bool
{
if (self::validateTimeZone($timezoneName)) {
self::$timezone = $timezoneName;
return true;
}
return false;
}
/**
* Return the Default Timezone used for date/time conversions.
*
* @return string Timezone (e.g. 'Europe/London')
*/
public static function getTimeZone(): string
{
return self::$timezone;
}
/**
* Return the Timezone offset used for date/time conversions to/from UST
* This requires both the timezone and the calculated date/time to allow for local DST.
*
* @param ?string $timezoneName The timezone for finding the adjustment to UST
* @param float|int $timestamp PHP date/time value
*
* @return int Number of seconds for timezone adjustment
*/
public static function getTimeZoneAdjustment(?string $timezoneName, $timestamp): int
{
$timezoneName = $timezoneName ?? self::$timezone;
$dtobj = Date::dateTimeFromTimestamp("$timestamp");
if (!self::validateTimeZone($timezoneName)) {
throw new PhpSpreadsheetException("Invalid timezone $timezoneName");
}
$dtobj->setTimeZone(new DateTimeZone($timezoneName));
return $dtobj->getOffset();
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php 0000644 00000026773 15243417764 0016012 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Xls
{
/**
* Get the width of a column in pixels. We use the relationship y = ceil(7x) where
* x is the width in intrinsic Excel units (measuring width in number of normal characters)
* This holds for Arial 10.
*
* @param Worksheet $worksheet The sheet
* @param string $col The column
*
* @return int The width in pixels
*/
public static function sizeCol(Worksheet $worksheet, $col = 'A')
{
// default font of the workbook
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$columnDimensions = $worksheet->getColumnDimensions();
// first find the true column width in pixels (uncollapsed and unhidden)
if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) {
// then we have column dimension with explicit width
$columnDimension = $columnDimensions[$col];
$width = $columnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} elseif ($worksheet->getDefaultColumnDimension()->getWidth() != -1) {
// then we have default column dimension with explicit width
$defaultColumnDimension = $worksheet->getDefaultColumnDimension();
$width = $defaultColumnDimension->getWidth();
$pixelWidth = Drawing::cellDimensionToPixels($width, $font);
} else {
// we don't even have any default column dimension. Width depends on default font
$pixelWidth = Font::getDefaultColumnWidthByFont($font, true);
}
// now find the effective column width in pixels
if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) {
$effectivePixelWidth = 0;
} else {
$effectivePixelWidth = $pixelWidth;
}
return $effectivePixelWidth;
}
/**
* Convert the height of a cell from user's units to pixels. By interpolation
* the relationship is: y = 4/3x. If the height hasn't been set by the user we
* use the default value. If the row is hidden we use a value of zero.
*
* @param Worksheet $worksheet The sheet
* @param int $row The row index (1-based)
*
* @return int The width in pixels
*/
public static function sizeRow(Worksheet $worksheet, $row = 1)
{
// default font of the workbook
$font = $worksheet->getParentOrThrow()->getDefaultStyle()->getFont();
$rowDimensions = $worksheet->getRowDimensions();
// first find the true row height in pixels (uncollapsed and unhidden)
if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) {
// then we have a row dimension
$rowDimension = $rowDimensions[$row];
$rowHeight = $rowDimension->getRowHeight();
$pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10
} elseif ($worksheet->getDefaultRowDimension()->getRowHeight() != -1) {
// then we have a default row dimension with explicit height
$defaultRowDimension = $worksheet->getDefaultRowDimension();
$pixelRowHeight = $defaultRowDimension->getRowHeight(Dimension::UOM_PIXELS);
} else {
// we don't even have any default row dimension. Height depends on default font
$pointRowHeight = Font::getDefaultRowHeightByFont($font);
$pixelRowHeight = Font::fontSizeToPixels((int) $pointRowHeight);
}
// now find the effective row height in pixels
if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) {
$effectivePixelRowHeight = 0;
} else {
$effectivePixelRowHeight = $pixelRowHeight;
}
return (int) $effectivePixelRowHeight;
}
/**
* Get the horizontal distance in pixels between two anchors
* The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets.
*
* @param string $startColumn
* @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width
* @param string $endColumn
* @param int $endOffsetX Offset within end cell measured in 1/1024 of the cell width
*
* @return int Horizontal measured in pixels
*/
public static function getDistanceX(Worksheet $worksheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0)
{
$distanceX = 0;
// add the widths of the spanning columns
$startColumnIndex = Coordinate::columnIndexFromString($startColumn);
$endColumnIndex = Coordinate::columnIndexFromString($endColumn);
for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) {
$distanceX += self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($i));
}
// correct for offsetX in startcell
$distanceX -= (int) floor(self::sizeCol($worksheet, $startColumn) * $startOffsetX / 1024);
// correct for offsetX in endcell
$distanceX -= (int) floor(self::sizeCol($worksheet, $endColumn) * (1 - $endOffsetX / 1024));
return $distanceX;
}
/**
* Get the vertical distance in pixels between two anchors
* The distanceY is found as sum of all the spanning rows minus two offsets.
*
* @param int $startRow (1-based)
* @param int $startOffsetY Offset within start cell measured in 1/256 of the cell height
* @param int $endRow (1-based)
* @param int $endOffsetY Offset within end cell measured in 1/256 of the cell height
*
* @return int Vertical distance measured in pixels
*/
public static function getDistanceY(Worksheet $worksheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0)
{
$distanceY = 0;
// add the widths of the spanning rows
for ($row = $startRow; $row <= $endRow; ++$row) {
$distanceY += self::sizeRow($worksheet, $row);
}
// correct for offsetX in startcell
$distanceY -= (int) floor(self::sizeRow($worksheet, $startRow) * $startOffsetY / 256);
// correct for offsetX in endcell
$distanceY -= (int) floor(self::sizeRow($worksheet, $endRow) * (1 - $endOffsetY / 256));
return $distanceY;
}
/**
* Convert 1-cell anchor coordinates to 2-cell anchor coordinates
* This function is ported from PEAR Spreadsheet_Writer_Excel with small modifications.
*
* Calculate the vertices that define the position of the image as required by
* the OBJ record.
*
* +------------+------------+
* | A | B |
* +-----+------------+------------+
* | |(x1,y1) | |
* | 1 |(A1)._______|______ |
* | | | | |
* | | | | |
* +-----+----| BITMAP |-----+
* | | | | |
* | 2 | |______________. |
* | | | (B2)|
* | | | (x2,y2)|
* +---- +------------+------------+
*
* Example of a bitmap that covers some of the area from cell A1 to cell B2.
*
* Based on the width and height of the bitmap we need to calculate 8 vars:
* $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
* The width and height of the cells are also variable and have to be taken into
* account.
* The values of $col_start and $row_start are passed in from the calling
* function. The values of $col_end and $row_end are calculated by subtracting
* the width and height of the bitmap from the width and height of the
* underlying cells.
* The vertices are expressed as a percentage of the underlying cell width as
* follows (rhs values are in pixels):
*
* x1 = X / W *1024
* y1 = Y / H *256
* x2 = (X-1) / W *1024
* y2 = (Y-1) / H *256
*
* Where: X is distance from the left side of the underlying cell
* Y is distance from the top of the underlying cell
* W is the width of the cell
* H is the height of the cell
*
* @param string $coordinates E.g. 'A1'
* @param int $offsetX Horizontal offset in pixels
* @param int $offsetY Vertical offset in pixels
* @param int $width Width in pixels
* @param int $height Height in pixels
*
* @return null|array
*/
public static function oneAnchor2twoAnchor(Worksheet $worksheet, $coordinates, $offsetX, $offsetY, $width, $height)
{
[$col_start, $row] = Coordinate::indexesFromString($coordinates);
$row_start = $row - 1;
$x1 = $offsetX;
$y1 = $offsetY;
// Initialise end cell to the same as the start cell
$col_end = $col_start; // Col containing lower right corner of object
$row_end = $row_start; // Row containing bottom right corner of object
// Zero the specified offset if greater than the cell dimensions
if ($x1 >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start))) {
$x1 = 0;
}
if ($y1 >= self::sizeRow($worksheet, $row_start + 1)) {
$y1 = 0;
}
$width = $width + $x1 - 1;
$height = $height + $y1 - 1;
// Subtract the underlying cell widths to find the end cell of the image
while ($width >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end))) {
$width -= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end));
++$col_end;
}
// Subtract the underlying cell heights to find the end cell of the image
while ($height >= self::sizeRow($worksheet, $row_end + 1)) {
$height -= self::sizeRow($worksheet, $row_end + 1);
++$row_end;
}
// Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
// with zero height or width.
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
return null;
}
if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
return null;
}
if (self::sizeRow($worksheet, $row_start + 1) == 0) {
return null;
}
if (self::sizeRow($worksheet, $row_end + 1) == 0) {
return null;
}
// Convert the pixel values to the percentage value expected by Excel
$x1 = $x1 / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
$y1 = $y1 / self::sizeRow($worksheet, $row_start + 1) * 256;
$x2 = ($width + 1) / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
$y2 = ($height + 1) / self::sizeRow($worksheet, $row_end + 1) * 256; // Distance to bottom of object
$startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
$endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);
return [
'startCoordinates' => $startCoordinates,
'startOffsetX' => $x1,
'startOffsetY' => $y1,
'endCoordinates' => $endCoordinates,
'endOffsetX' => $x2,
'endOffsetY' => $y2,
];
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php 0000644 00000002500 15243417764 0020630 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
class DgContainer
{
/**
* Drawing index, 1-based.
*
* @var ?int
*/
private $dgId;
/**
* Last shape index in this drawing.
*
* @var ?int
*/
private $lastSpId;
/** @var ?DgContainer\SpgrContainer */
private $spgrContainer;
public function getDgId(): ?int
{
return $this->dgId;
}
public function setDgId(int $value): void
{
$this->dgId = $value;
}
public function getLastSpId(): ?int
{
return $this->lastSpId;
}
public function setLastSpId(int $value): void
{
$this->lastSpId = $value;
}
public function getSpgrContainer(): ?DgContainer\SpgrContainer
{
return $this->spgrContainer;
}
public function getSpgrContainerOrThrow(): DgContainer\SpgrContainer
{
if ($this->spgrContainer !== null) {
return $this->spgrContainer;
}
throw new SpreadsheetException('spgrContainer is unexpectedly null');
}
/** @param DgContainer\SpgrContainer $spgrContainer */
public function setSpgrContainer($spgrContainer): DgContainer\SpgrContainer
{
return $this->spgrContainer = $spgrContainer;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php 0000644 00000016451 15243417764 0025645 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
class SpContainer
{
/**
* Parent Shape Group Container.
*
* @var SpgrContainer
*/
private $parent;
/**
* Is this a group shape?
*
* @var bool
*/
private $spgr = false;
/**
* Shape type.
*
* @var int
*/
private $spType;
/**
* Shape flag.
*
* @var int
*/
private $spFlag;
/**
* Shape index (usually group shape has index 0, and the rest: 1,2,3...).
*
* @var int
*/
private $spId;
/**
* Array of options.
*
* @var array
*/
private $OPT;
/**
* Cell coordinates of upper-left corner of shape, e.g. 'A1'.
*
* @var string
*/
private $startCoordinates;
/**
* Horizontal offset of upper-left corner of shape measured in 1/1024 of column width.
*
* @var int
*/
private $startOffsetX;
/**
* Vertical offset of upper-left corner of shape measured in 1/256 of row height.
*
* @var int
*/
private $startOffsetY;
/**
* Cell coordinates of bottom-right corner of shape, e.g. 'B2'.
*
* @var string
*/
private $endCoordinates;
/**
* Horizontal offset of bottom-right corner of shape measured in 1/1024 of column width.
*
* @var int
*/
private $endOffsetX;
/**
* Vertical offset of bottom-right corner of shape measured in 1/256 of row height.
*
* @var int
*/
private $endOffsetY;
/**
* Set parent Shape Group Container.
*
* @param SpgrContainer $parent
*/
public function setParent($parent): void
{
$this->parent = $parent;
}
/**
* Get the parent Shape Group Container.
*
* @return SpgrContainer
*/
public function getParent()
{
return $this->parent;
}
/**
* Set whether this is a group shape.
*
* @param bool $value
*/
public function setSpgr($value): void
{
$this->spgr = $value;
}
/**
* Get whether this is a group shape.
*
* @return bool
*/
public function getSpgr()
{
return $this->spgr;
}
/**
* Set the shape type.
*
* @param int $value
*/
public function setSpType($value): void
{
$this->spType = $value;
}
/**
* Get the shape type.
*
* @return int
*/
public function getSpType()
{
return $this->spType;
}
/**
* Set the shape flag.
*
* @param int $value
*/
public function setSpFlag($value): void
{
$this->spFlag = $value;
}
/**
* Get the shape flag.
*
* @return int
*/
public function getSpFlag()
{
return $this->spFlag;
}
/**
* Set the shape index.
*
* @param int $value
*/
public function setSpId($value): void
{
$this->spId = $value;
}
/**
* Get the shape index.
*
* @return int
*/
public function getSpId()
{
return $this->spId;
}
/**
* Set an option for the Shape Group Container.
*
* @param int $property The number specifies the option
* @param mixed $value
*/
public function setOPT($property, $value): void
{
$this->OPT[$property] = $value;
}
/**
* Get an option for the Shape Group Container.
*
* @param int $property The number specifies the option
*
* @return mixed
*/
public function getOPT($property)
{
if (isset($this->OPT[$property])) {
return $this->OPT[$property];
}
return null;
}
/**
* Get the collection of options.
*
* @return array
*/
public function getOPTCollection()
{
return $this->OPT;
}
/**
* Set cell coordinates of upper-left corner of shape.
*
* @param string $value eg: 'A1'
*/
public function setStartCoordinates($value): void
{
$this->startCoordinates = $value;
}
/**
* Get cell coordinates of upper-left corner of shape.
*
* @return string
*/
public function getStartCoordinates()
{
return $this->startCoordinates;
}
/**
* Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
*
* @param int $startOffsetX
*/
public function setStartOffsetX($startOffsetX): void
{
$this->startOffsetX = $startOffsetX;
}
/**
* Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width.
*
* @return int
*/
public function getStartOffsetX()
{
return $this->startOffsetX;
}
/**
* Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
*
* @param int $startOffsetY
*/
public function setStartOffsetY($startOffsetY): void
{
$this->startOffsetY = $startOffsetY;
}
/**
* Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height.
*
* @return int
*/
public function getStartOffsetY()
{
return $this->startOffsetY;
}
/**
* Set cell coordinates of bottom-right corner of shape.
*
* @param string $value eg: 'A1'
*/
public function setEndCoordinates($value): void
{
$this->endCoordinates = $value;
}
/**
* Get cell coordinates of bottom-right corner of shape.
*
* @return string
*/
public function getEndCoordinates()
{
return $this->endCoordinates;
}
/**
* Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
*
* @param int $endOffsetX
*/
public function setEndOffsetX($endOffsetX): void
{
$this->endOffsetX = $endOffsetX;
}
/**
* Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width.
*
* @return int
*/
public function getEndOffsetX()
{
return $this->endOffsetX;
}
/**
* Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
*
* @param int $endOffsetY
*/
public function setEndOffsetY($endOffsetY): void
{
$this->endOffsetY = $endOffsetY;
}
/**
* Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height.
*
* @return int
*/
public function getEndOffsetY()
{
return $this->endOffsetY;
}
/**
* Get the nesting level of this spContainer. This is the number of spgrContainers between this spContainer and
* the dgContainer. A value of 1 = immediately within first spgrContainer
* Higher nesting level occurs if and only if spContainer is part of a shape group.
*
* @return int Nesting level
*/
public function getNestingLevel()
{
$nestingLevel = 0;
$parent = $this->getParent();
while ($parent instanceof SpgrContainer) {
++$nestingLevel;
$parent = $parent->getParent();
}
return $nestingLevel;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php 0000644 00000002775 15243417764 0023424 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
class SpgrContainer
{
/**
* Parent Shape Group Container.
*
* @var null|SpgrContainer
*/
private $parent;
/**
* Shape Container collection.
*
* @var array
*/
private $children = [];
/**
* Set parent Shape Group Container.
*/
public function setParent(?self $parent): void
{
$this->parent = $parent;
}
/**
* Get the parent Shape Group Container if any.
*/
public function getParent(): ?self
{
return $this->parent;
}
/**
* Add a child. This will be either spgrContainer or spContainer.
*
* @param mixed $child
*/
public function addChild($child): void
{
$this->children[] = $child;
$child->setParent($this);
}
/**
* Get collection of Shape Containers.
*/
public function getChildren(): array
{
return $this->children;
}
/**
* Recursively get all spContainers within this spgrContainer.
*
* @return SpgrContainer\SpContainer[]
*/
public function getAllSpContainers()
{
$allSpContainers = [];
foreach ($this->children as $child) {
if ($child instanceof self) {
$allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers());
} else {
$allSpContainers[] = $child;
}
}
return $allSpContainers;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php 0000644 00000006444 15243417764 0021012 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher;
class DggContainer
{
/**
* Maximum shape index of all shapes in all drawings increased by one.
*
* @var int
*/
private $spIdMax;
/**
* Total number of drawings saved.
*
* @var int
*/
private $cDgSaved;
/**
* Total number of shapes saved (including group shapes).
*
* @var int
*/
private $cSpSaved;
/**
* BLIP Store Container.
*
* @var ?DggContainer\BstoreContainer
*/
private $bstoreContainer;
/**
* Array of options for the drawing group.
*
* @var array
*/
private $OPT = [];
/**
* Array of identifier clusters containg information about the maximum shape identifiers.
*
* @var array
*/
private $IDCLs = [];
/**
* Get maximum shape index of all shapes in all drawings (plus one).
*
* @return int
*/
public function getSpIdMax()
{
return $this->spIdMax;
}
/**
* Set maximum shape index of all shapes in all drawings (plus one).
*
* @param int $value
*/
public function setSpIdMax($value): void
{
$this->spIdMax = $value;
}
/**
* Get total number of drawings saved.
*
* @return int
*/
public function getCDgSaved()
{
return $this->cDgSaved;
}
/**
* Set total number of drawings saved.
*
* @param int $value
*/
public function setCDgSaved($value): void
{
$this->cDgSaved = $value;
}
/**
* Get total number of shapes saved (including group shapes).
*
* @return int
*/
public function getCSpSaved()
{
return $this->cSpSaved;
}
/**
* Set total number of shapes saved (including group shapes).
*
* @param int $value
*/
public function setCSpSaved($value): void
{
$this->cSpSaved = $value;
}
/**
* Get BLIP Store Container.
*
* @return ?DggContainer\BstoreContainer
*/
public function getBstoreContainer()
{
return $this->bstoreContainer;
}
/**
* Set BLIP Store Container.
*
* @param DggContainer\BstoreContainer $bstoreContainer
*/
public function setBstoreContainer($bstoreContainer): void
{
$this->bstoreContainer = $bstoreContainer;
}
/**
* Set an option for the drawing group.
*
* @param int $property The number specifies the option
* @param mixed $value
*/
public function setOPT($property, $value): void
{
$this->OPT[$property] = $value;
}
/**
* Get an option for the drawing group.
*
* @param int $property The number specifies the option
*
* @return mixed
*/
public function getOPT($property)
{
if (isset($this->OPT[$property])) {
return $this->OPT[$property];
}
return null;
}
/**
* Get identifier clusters.
*
* @return array
*/
public function getIDCLs()
{
return $this->IDCLs;
}
/**
* Set identifier clusters. [<drawingId> => <max shape id>, ...].
*
* @param array $IDCLs
*/
public function setIDCLs($IDCLs): void
{
$this->IDCLs = $IDCLs;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php 0000644 00000001624 15243417764 0025405 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
class Blip
{
/**
* The parent BSE.
*
* @var BSE
*/
private $parent;
/**
* Raw image data.
*
* @var string
*/
private $data;
/**
* Get the raw image data.
*
* @return string
*/
public function getData()
{
return $this->data;
}
/**
* Set the raw image data.
*
* @param string $data
*/
public function setData($data): void
{
$this->data = $data;
}
/**
* Set parent BSE.
*/
public function setParent(BSE $parent): void
{
$this->parent = $parent;
}
/**
* Get parent BSE.
*/
public function getParent(): BSE
{
return $this->parent;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php 0000644 00000003211 15243417764 0024511 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
class BSE
{
const BLIPTYPE_ERROR = 0x00;
const BLIPTYPE_UNKNOWN = 0x01;
const BLIPTYPE_EMF = 0x02;
const BLIPTYPE_WMF = 0x03;
const BLIPTYPE_PICT = 0x04;
const BLIPTYPE_JPEG = 0x05;
const BLIPTYPE_PNG = 0x06;
const BLIPTYPE_DIB = 0x07;
const BLIPTYPE_TIFF = 0x11;
const BLIPTYPE_CMYKJPEG = 0x12;
/**
* The parent BLIP Store Entry Container.
* Property is never currently read.
*
* @var BstoreContainer
*/
private $parent; // @phpstan-ignore-line
/**
* The BLIP (Big Large Image or Picture).
*
* @var ?BSE\Blip
*/
private $blip;
/**
* The BLIP type.
*
* @var int
*/
private $blipType;
/**
* Set parent BLIP Store Entry Container.
*/
public function setParent(BstoreContainer $parent): void
{
$this->parent = $parent;
}
/**
* Get the BLIP.
*
* @return ?BSE\Blip
*/
public function getBlip()
{
return $this->blip;
}
/**
* Set the BLIP.
*/
public function setBlip(BSE\Blip $blip): void
{
$this->blip = $blip;
$blip->setParent($this);
}
/**
* Get the BLIP type.
*
* @return int
*/
public function getBlipType()
{
return $this->blipType;
}
/**
* Set the BLIP type.
*
* @param int $blipType
*/
public function setBlipType($blipType): void
{
$this->blipType = $blipType;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php 0000644 00000001224 15243417764 0024102 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
class BstoreContainer
{
/**
* BLIP Store Entries. Each of them holds one BLIP (Big Large Image or Picture).
*
* @var BstoreContainer\BSE[]
*/
private $BSECollection = [];
/**
* Add a BLIP Store Entry.
*/
public function addBSE(BstoreContainer\BSE $BSE): void
{
$this->BSECollection[] = $BSE;
$BSE->setParent($this);
}
/**
* Get the collection of BLIP Store Entries.
*
* @return BstoreContainer\BSE[]
*/
public function getBSECollection()
{
return $this->BSECollection;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php 0000644 00000016031 15243417764 0016307 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
/**
* Class for creating PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class PPS
{
/**
* The PPS index.
*
* @var int
*/
public $No;
/**
* The PPS name (in Unicode).
*
* @var string
*/
public $Name;
/**
* The PPS type. Dir, Root or File.
*
* @var int
*/
public $Type;
/**
* The index of the previous PPS.
*
* @var int
*/
public $PrevPps;
/**
* The index of the next PPS.
*
* @var int
*/
public $NextPps;
/**
* The index of it's first child if this is a Dir or Root PPS.
*
* @var int
*/
public $DirPps;
/**
* A timestamp.
*
* @var float|int
*/
public $Time1st;
/**
* A timestamp.
*
* @var float|int
*/
public $Time2nd;
/**
* Starting block (small or big) for this PPS's data inside the container.
*
* @var ?int
*/
public $startBlock;
/**
* The size of the PPS's data (in bytes).
*
* @var int
*/
public $Size;
/**
* The PPS's data (only used if it's not using a temporary file).
*
* @var string
*/
public $_data = '';
/**
* Array of child PPS's (only used by Root and Dir PPS's).
*
* @var array
*/
public $children = [];
/**
* Pointer to OLE container.
*
* @var OLE
*/
public $ole;
/**
* The constructor.
*
* @param ?int $No The PPS index
* @param ?string $name The PPS name
* @param ?int $type The PPS type. Dir, Root or File
* @param ?int $prev The index of the previous PPS
* @param ?int $next The index of the next PPS
* @param ?int $dir The index of it's first child if this is a Dir or Root PPS
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param ?string $data The (usually binary) source data of the PPS
* @param array $children Array containing children PPS for this PPS
*/
public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children)
{
$this->No = (int) $No;
$this->Name = (string) $name;
$this->Type = (int) $type;
$this->PrevPps = (int) $prev;
$this->NextPps = (int) $next;
$this->DirPps = (int) $dir;
$this->Time1st = $time_1st ?? 0;
$this->Time2nd = $time_2nd ?? 0;
$this->_data = (string) $data;
$this->children = $children;
$this->Size = strlen((string) $data);
}
/**
* Returns the amount of data saved for this PPS.
*
* @return int The amount of data (in bytes)
*/
public function getDataLen()
{
//if (!isset($this->_data)) {
// return 0;
//}
return strlen($this->_data);
}
/**
* Returns a string with the PPS's WK (What is a WK?).
*
* @return string The binary string
*/
public function getPpsWk()
{
$ret = str_pad($this->Name, 64, "\x00");
$ret .= pack('v', strlen($this->Name) + 2) // 66
. pack('c', $this->Type) // 67
. pack('c', 0x00) //UK // 68
. pack('V', $this->PrevPps) //Prev // 72
. pack('V', $this->NextPps) //Next // 76
. pack('V', $this->DirPps) //Dir // 80
. "\x00\x09\x02\x00" // 84
. "\x00\x00\x00\x00" // 88
. "\xc0\x00\x00\x00" // 92
. "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root
. "\x00\x00\x00\x00" // 100
. OLE::localDateToOLE($this->Time1st) // 108
. OLE::localDateToOLE($this->Time2nd) // 116
. pack('V', $this->startBlock ?? 0) // 120
. pack('V', $this->Size) // 124
. pack('V', 0); // 128
return $ret;
}
/**
* Updates index and pointers to previous, next and children PPS's for this
* PPS. I don't think it'll work with Dir PPS's.
*
* @param array $raList Reference to the array of PPS's for the whole OLE
* container
* @param mixed $to_save
* @param mixed $depth
*
* @return int The index for this PPS
*/
public static function savePpsSetPnt(&$raList, $to_save, $depth = 0)
{
if (!is_array($to_save) || (empty($to_save))) {
return 0xFFFFFFFF;
} elseif (count($to_save) == 1) {
$cnt = count($raList);
// If the first entry, it's the root... Don't clone it!
$raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0];
$raList[$cnt]->No = $cnt;
$raList[$cnt]->PrevPps = 0xFFFFFFFF;
$raList[$cnt]->NextPps = 0xFFFFFFFF;
$raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
} else {
$iPos = (int) floor(count($to_save) / 2);
$aPrev = array_slice($to_save, 0, $iPos);
$aNext = array_slice($to_save, $iPos + 1);
$cnt = count($raList);
// If the first entry, it's the root... Don't clone it!
$raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos];
$raList[$cnt]->No = $cnt;
$raList[$cnt]->PrevPps = self::savePpsSetPnt($raList, $aPrev, $depth++);
$raList[$cnt]->NextPps = self::savePpsSetPnt($raList, $aNext, $depth++);
$raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
}
return $cnt;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php 0000644 00000035273 15243417764 0017243 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
/**
* Class for creating Root PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class Root extends PPS
{
/**
* @var resource
*/
private $fileHandle;
/**
* @var ?int
*/
private $smallBlockSize;
/**
* @var ?int
*/
private $bigBlockSize;
/**
* @param null|float|int $time_1st A timestamp
* @param null|float|int $time_2nd A timestamp
* @param File[] $raChild
*/
public function __construct($time_1st, $time_2nd, $raChild)
{
parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild);
}
/**
* Method for saving the whole OLE container (including files).
* In fact, if called with an empty argument (or '-'), it saves to a
* temporary file and then outputs it's contents to stdout.
* If a resource pointer to a stream created by fopen() is passed
* it will be used, but you have to close such stream by yourself.
*
* @param resource $fileHandle the name of the file or stream where to save the OLE container
*
* @return bool true on success
*/
public function save($fileHandle)
{
$this->fileHandle = $fileHandle;
// Initial Setting for saving
$this->bigBlockSize = (int) (2 ** (
(isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
));
$this->smallBlockSize = (int) (2 ** (
(isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
));
// Make an array of PPS's (for Save)
$aList = [];
PPS::savePpsSetPnt($aList, [$this]);
// calculate values for header
[$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo);
// Save Header
$this->saveHeader((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
// Make Small Data string (write SBD)
$this->_data = $this->makeSmallData($aList);
// Write BB
$this->saveBigData((int) $iSBDcnt, $aList);
// Write PPS
$this->savePps($aList);
// Write Big Block Depot and BDList and Adding Header informations
$this->saveBbd((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt);
return true;
}
/**
* Calculate some numbers.
*
* @param array $raList Reference to an array of PPS's
*
* @return float[] The array of numbers
*/
private function calcSize(&$raList)
{
// Calculate Basic Setting
[$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
$iSBcnt = 0;
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
$raList[$i]->Size = $raList[$i]->getDataLen();
if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
$iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize)
+ (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
} else {
$iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) +
(($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
}
}
}
$iSmallLen = $iSBcnt * $this->smallBlockSize;
$iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
$iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0);
$iBBcnt += (floor($iSmallLen / $this->bigBlockSize) +
(($iSmallLen % $this->bigBlockSize) ? 1 : 0));
$iCnt = count($raList);
$iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
$iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0));
return [$iSBDcnt, $iBBcnt, $iPPScnt];
}
/**
* Helper function for caculating a magic value for block sizes.
*
* @param int $i2 The argument
*
* @return float
*
* @see save()
*/
private static function adjust2($i2)
{
$iWk = log($i2) / log(2);
return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk;
}
/**
* Save OLE header.
*
* @param int $iSBDcnt
* @param int $iBBcnt
* @param int $iPPScnt
*/
private function saveHeader($iSBDcnt, $iBBcnt, $iPPScnt): void
{
$FILE = $this->fileHandle;
// Calculate Basic Setting
$iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
$i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
$iBdExL = 0;
$iAll = $iBBcnt + $iPPScnt + $iSBDcnt;
$iAllW = $iAll;
$iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
$iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
// Calculate BD count
if ($iBdCnt > $i1stBdL) {
while (1) {
++$iBdExL;
++$iAllW;
$iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
$iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) {
break;
}
}
}
// Save Header
fwrite(
$FILE,
"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. pack('v', 0x3b)
. pack('v', 0x03)
. pack('v', -2)
. pack('v', 9)
. pack('v', 6)
. pack('v', 0)
. "\x00\x00\x00\x00"
. "\x00\x00\x00\x00"
. pack('V', $iBdCnt)
. pack('V', $iBBcnt + $iSBDcnt) //ROOT START
. pack('V', 0)
. pack('V', 0x1000)
. pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot
. pack('V', $iSBDcnt)
);
// Extra BDList Start, Count
if ($iBdCnt < $i1stBdL) {
fwrite(
$FILE,
pack('V', -2) // Extra BDList Start
. pack('V', 0)// Extra BDList Count
);
} else {
fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL));
}
// BDList
for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) {
fwrite($FILE, pack('V', $iAll + $i));
}
if ($i < $i1stBdL) {
$jB = $i1stBdL - $i;
for ($j = 0; $j < $jB; ++$j) {
fwrite($FILE, (pack('V', -1)));
}
}
}
/**
* Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param int $iStBlk
* @param array $raList Reference to array of PPS's
*/
private function saveBigData($iStBlk, &$raList): void
{
$FILE = $this->fileHandle;
// cycle through PPS's
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) {
$raList[$i]->Size = $raList[$i]->getDataLen();
if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) {
fwrite($FILE, $raList[$i]->_data);
if ($raList[$i]->Size % $this->bigBlockSize) {
fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize)));
}
// Set For PPS
$raList[$i]->startBlock = $iStBlk;
$iStBlk +=
(floor($raList[$i]->Size / $this->bigBlockSize) +
(($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
}
}
}
}
/**
* get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
*
* @param array $raList Reference to array of PPS's
*
* @return string
*/
private function makeSmallData(&$raList)
{
$sRes = '';
$FILE = $this->fileHandle;
$iSmBlk = 0;
$iCount = count($raList);
for ($i = 0; $i < $iCount; ++$i) {
// Make SBD, small data string
if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
if ($raList[$i]->Size <= 0) {
continue;
}
if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
$iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize)
+ (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
// Add to SBD
$jB = $iSmbCnt - 1;
for ($j = 0; $j < $jB; ++$j) {
fwrite($FILE, pack('V', $j + $iSmBlk + 1));
}
fwrite($FILE, pack('V', -2));
// Add to Data String(this will be written for RootEntry)
$sRes .= $raList[$i]->_data;
if ($raList[$i]->Size % $this->smallBlockSize) {
$sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize));
}
// Set for PPS
$raList[$i]->startBlock = $iSmBlk;
$iSmBlk += $iSmbCnt;
}
}
}
$iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
if ($iSmBlk % $iSbCnt) {
$iB = $iSbCnt - ($iSmBlk % $iSbCnt);
for ($i = 0; $i < $iB; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
return $sRes;
}
/**
* Saves all the PPS's WKs.
*
* @param array $raList Reference to an array with all PPS's
*/
private function savePps(&$raList): void
{
// Save each PPS WK
$iC = count($raList);
for ($i = 0; $i < $iC; ++$i) {
fwrite($this->fileHandle, $raList[$i]->getPpsWk());
}
// Adjust for Block
$iCnt = count($raList);
$iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
if ($iCnt % $iBCnt) {
fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE));
}
}
/**
* Saving Big Block Depot.
*
* @param int $iSbdSize
* @param int $iBsize
* @param int $iPpsCnt
*/
private function saveBbd($iSbdSize, $iBsize, $iPpsCnt): void
{
$FILE = $this->fileHandle;
// Calculate Basic Setting
$iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
$i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
$iBdExL = 0;
$iAll = $iBsize + $iPpsCnt + $iSbdSize;
$iAllW = $iAll;
$iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
$iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
// Calculate BD count
if ($iBdCnt > $i1stBdL) {
while (1) {
++$iBdExL;
++$iAllW;
$iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
$iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) {
break;
}
}
}
// Making BD
// Set for SBD
if ($iSbdSize > 0) {
for ($i = 0; $i < ($iSbdSize - 1); ++$i) {
fwrite($FILE, pack('V', $i + 1));
}
fwrite($FILE, pack('V', -2));
}
// Set for B
for ($i = 0; $i < ($iBsize - 1); ++$i) {
fwrite($FILE, pack('V', $i + $iSbdSize + 1));
}
fwrite($FILE, pack('V', -2));
// Set for PPS
for ($i = 0; $i < ($iPpsCnt - 1); ++$i) {
fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1));
}
fwrite($FILE, pack('V', -2));
// Set for BBD itself ( 0xFFFFFFFD : BBD)
for ($i = 0; $i < $iBdCnt; ++$i) {
fwrite($FILE, pack('V', 0xFFFFFFFD));
}
// Set for ExtraBDList
for ($i = 0; $i < $iBdExL; ++$i) {
fwrite($FILE, pack('V', 0xFFFFFFFC));
}
// Adjust for Block
if (($iAllW + $iBdCnt) % $iBbCnt) {
$iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt));
for ($i = 0; $i < $iBlock; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
// Extra BDList
if ($iBdCnt > $i1stBdL) {
$iN = 0;
$iNb = 0;
for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) {
if ($iN >= ($iBbCnt - 1)) {
$iN = 0;
++$iNb;
fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb));
}
fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i));
}
if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) {
$iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1));
for ($i = 0; $i < $iB; ++$i) {
fwrite($FILE, pack('V', -1));
}
}
fwrite($FILE, pack('V', -2));
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php 0000644 00000004255 15243417764 0017173 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
/**
* Class for creating File PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
class File extends PPS
{
/**
* The constructor.
*
* @param string $name The name of the file (in Unicode)
*
* @see OLE::ascToUcs()
*/
public function __construct($name)
{
parent::__construct(null, $name, OLE::OLE_PPS_TYPE_FILE, null, null, null, null, null, '', []);
}
/**
* Initialization method. Has to be called right after OLE_PPS_File().
*
* @return mixed true on success
*/
public function init()
{
return true;
}
/**
* Append data to PPS.
*
* @param string $data The data to append
*/
public function append($data): void
{
$this->_data .= $data;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php 0000644 00000013474 15243417764 0021337 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE;
class ChainedBlockStream
{
/** @var mixed */
public $context;
/**
* The OLE container of the file that is being read.
*
* @var null|OLE
*/
public $ole;
/**
* Parameters specified by fopen().
*
* @var array
*/
public $params;
/**
* The binary data of the file.
*
* @var string
*/
public $data;
/**
* The file pointer.
*
* @var int byte offset
*/
public $pos;
/**
* Implements support for fopen().
* For creating streams using this wrapper, use OLE_PPS_File::getStream().
*
* @param string $path resource name including scheme, e.g.
* ole-chainedblockstream://oleInstanceId=1
* @param string $mode only "r" is supported
* @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH
* @param string $openedPath absolute path of the opened stream (out parameter)
*
* @return bool true on success
*/
public function stream_open($path, $mode, $options, &$openedPath) // @codingStandardsIgnoreLine
{
if ($mode[0] !== 'r') {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('Only reading is supported', E_USER_WARNING);
}
return false;
}
// 25 is length of "ole-chainedblockstream://"
parse_str(substr($path, 25), $this->params);
if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('OLE stream not found', E_USER_WARNING);
}
return false;
}
$this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']];
$blockId = $this->params['blockId'];
$this->data = '';
if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) {
// Block id refers to small blocks
$rootPos = $this->ole->getBlockOffset($this->ole->root->startBlock);
while ($blockId != -2) {
$pos = $rootPos + $blockId * $this->ole->bigBlockSize;
$blockId = $this->ole->sbat[$blockId];
fseek($this->ole->_file_handle, $pos);
$this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
}
} else {
// Block id refers to big blocks
while ($blockId != -2) {
$pos = $this->ole->getBlockOffset($blockId);
fseek($this->ole->_file_handle, $pos);
$this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
$blockId = $this->ole->bbat[$blockId];
}
}
if (isset($this->params['size'])) {
$this->data = substr($this->data, 0, $this->params['size']);
}
if ($options & STREAM_USE_PATH) {
$openedPath = $path;
}
return true;
}
/**
* Implements support for fclose().
*/
public function stream_close(): void // @codingStandardsIgnoreLine
{
$this->ole = null;
unset($GLOBALS['_OLE_INSTANCES']);
}
/**
* Implements support for fread(), fgets() etc.
*
* @param int $count maximum number of bytes to read
*
* @return false|string
*/
public function stream_read($count) // @codingStandardsIgnoreLine
{
if ($this->stream_eof()) {
return false;
}
$s = substr($this->data, (int) $this->pos, $count);
$this->pos += $count;
return $s;
}
/**
* Implements support for feof().
*
* @return bool TRUE if the file pointer is at EOF; otherwise FALSE
*/
public function stream_eof() // @codingStandardsIgnoreLine
{
return $this->pos >= strlen($this->data);
}
/**
* Returns the position of the file pointer, i.e. its offset into the file
* stream. Implements support for ftell().
*
* @return int
*/
public function stream_tell() // @codingStandardsIgnoreLine
{
return $this->pos;
}
/**
* Implements support for fseek().
*
* @param int $offset byte offset
* @param int $whence SEEK_SET, SEEK_CUR or SEEK_END
*
* @return bool
*/
public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine
{
if ($whence == SEEK_SET && $offset >= 0) {
$this->pos = $offset;
} elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
$this->pos += $offset;
// @phpstan-ignore-next-line
} elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) {
$this->pos = strlen($this->data) + $offset;
} else {
return false;
}
return true;
}
/**
* Implements support for fstat(). Currently the only supported field is
* "size".
*
* @return array
*/
public function stream_stat() // @codingStandardsIgnoreLine
{
return [
'size' => strlen($this->data),
];
}
// Methods used by stream_wrapper_register() that are not implemented:
// bool stream_flush ( void )
// int stream_write ( string data )
// bool rename ( string path_from, string path_to )
// bool mkdir ( string path, int mode, int options )
// bool rmdir ( string path, int options )
// bool dir_opendir ( string path, int options )
// array url_stat ( string path, int flags )
// string dir_readdir ( void )
// bool dir_rewinddir ( void )
// bool dir_closedir ( void )
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php 0000644 00000024323 15243417764 0016444 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
class OLERead
{
/** @var string */
private $data = '';
// Size of a sector = 512 bytes
const BIG_BLOCK_SIZE = 0x200;
// Size of a short sector = 64 bytes
const SMALL_BLOCK_SIZE = 0x40;
// Size of a directory entry always = 128 bytes
const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
// Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
const SMALL_BLOCK_THRESHOLD = 0x1000;
// header offsets
const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c;
const ROOT_START_BLOCK_POS = 0x30;
const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c;
const EXTENSION_BLOCK_POS = 0x44;
const NUM_EXTENSION_BLOCK_POS = 0x48;
const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c;
// property storage offsets (directory offsets)
const SIZE_OF_NAME_POS = 0x40;
const TYPE_POS = 0x42;
const START_BLOCK_POS = 0x74;
const SIZE_POS = 0x78;
/** @var int */
public $wrkbook;
/** @var int */
public $summaryInformation;
/** @var int */
public $documentSummaryInformation;
/**
* @var int
*/
private $numBigBlockDepotBlocks;
/**
* @var int
*/
private $rootStartBlock;
/**
* @var int
*/
private $sbdStartBlock;
/**
* @var int
*/
private $extensionBlock;
/**
* @var int
*/
private $numExtensionBlocks;
/**
* @var string
*/
private $bigBlockChain;
/**
* @var string
*/
private $smallBlockChain;
/**
* @var string
*/
private $entry;
/**
* @var int
*/
private $rootentry;
/**
* @var array
*/
private $props = [];
/**
* Read the file.
*/
public function read(string $filename): void
{
File::assertFile($filename);
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->data = (string) file_get_contents($filename, false, null, 0, 8);
// Check OLE identifier
$identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
if ($this->data != $identifierOle) {
throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = (string) file_get_contents($filename);
// Total number of sectors used for the SAT
$this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
// SecID of the first sector of the directory stream
$this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
// SecID of the first sector of the SSAT (or -2 if not extant)
$this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
// SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
$this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
// Total number of sectors used by MSAT
$this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
$bigBlockDepotBlocks = [];
$pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
$bbdBlocks = $this->numBigBlockDepotBlocks;
if ($this->numExtensionBlocks !== 0) {
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
}
for ($i = 0; $i < $bbdBlocks; ++$i) {
$bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
$pos += 4;
}
for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
$pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
$blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
$bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
$pos += 4;
}
$bbdBlocks += $blocksToRead;
if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
$this->extensionBlock = self::getInt4d($this->data, $pos);
}
}
$pos = 0;
$this->bigBlockChain = '';
$bbs = self::BIG_BLOCK_SIZE / 4;
for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
$pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
$this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
}
$sbdBlock = $this->sbdStartBlock;
$this->smallBlockChain = '';
while ($sbdBlock != -2) {
$pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
$this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
$pos += 4 * $bbs;
$sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
}
// read the directory stream
$block = $this->rootStartBlock;
$this->entry = $this->readData($block);
$this->readPropertySets();
}
/**
* Extract binary stream data.
*
* @param ?int $stream
*
* @return null|string
*/
public function getStream($stream)
{
if ($stream === null) {
return null;
}
$streamData = '';
if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
$rootdata = $this->readData($this->props[$this->rootentry]['startBlock']);
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = $block * self::SMALL_BLOCK_SIZE;
$streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
$block = self::getInt4d($this->smallBlockChain, $block * 4);
}
return $streamData;
}
$numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
++$numBlocks;
}
if ($numBlocks == 0) {
return '';
}
$block = $this->props[$stream]['startBlock'];
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = self::getInt4d($this->bigBlockChain, $block * 4);
}
return $streamData;
}
/**
* Read a standard stream (by joining sectors using information from SAT).
*
* @param int $block Sector ID where the stream starts
*
* @return string Data for standard stream
*/
private function readData($block)
{
$data = '';
while ($block != -2) {
$pos = ($block + 1) * self::BIG_BLOCK_SIZE;
$data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
$block = self::getInt4d($this->bigBlockChain, $block * 4);
}
return $data;
}
/**
* Read entries in the directory stream.
*/
private function readPropertySets(): void
{
$offset = 0;
// loop through entires, each entry is 128 bytes
$entryLen = strlen($this->entry);
while ($offset < $entryLen) {
// entry data (128 bytes)
$d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
// size in bytes of name
$nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
// type of entry
$type = ord($d[self::TYPE_POS]);
// sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
// sectorID of first sector of the short-stream container stream, if this entry is root entry
$startBlock = self::getInt4d($d, self::START_BLOCK_POS);
$size = self::getInt4d($d, self::SIZE_POS);
$name = str_replace("\x00", '', substr($d, 0, $nameSize));
$this->props[] = [
'name' => $name,
'type' => $type,
'startBlock' => $startBlock,
'size' => $size,
];
// tmp helper to simplify checks
$upName = strtoupper($name);
// Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
$this->wrkbook = count($this->props) - 1;
} elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
// Root entry
$this->rootentry = count($this->props) - 1;
}
// Summary information
if ($name == chr(5) . 'SummaryInformation') {
$this->summaryInformation = count($this->props) - 1;
}
// Additional Document Summary information
if ($name == chr(5) . 'DocumentSummaryInformation') {
$this->documentSummaryInformation = count($this->props) - 1;
}
$offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
}
}
/**
* Read 4 bytes of data at specified position.
*
* @param string $data
* @param int $pos
*
* @return int
*/
private static function getInt4d($data, $pos)
{
if ($pos < 0) {
// Invalid position
throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
}
$len = strlen($data);
if ($len < $pos + 4) {
$data .= str_repeat("\0", $pos + 4 - $len);
}
// FIX: represent numbers correctly on 64-bit system
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
// Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
$_or_24 = ord($data[$pos + 3]);
if ($_or_24 >= 128) {
// negative number
$_ord_24 = -abs((256 - $_or_24) << 24);
} else {
$_ord_24 = ($_or_24 & 127) << 24;
}
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php 0000644 00000045512 15243417764 0016111 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Date
{
/** constants */
const CALENDAR_WINDOWS_1900 = 1900; // Base date of 1st Jan 1900 = 1.0
const CALENDAR_MAC_1904 = 1904; // Base date of 2nd Jan 1904 = 1.0
/**
* Names of the months of the year, indexed by shortname
* Planned usage for locale settings.
*
* @var string[]
*/
public static $monthNames = [
'Jan' => 'January',
'Feb' => 'February',
'Mar' => 'March',
'Apr' => 'April',
'May' => 'May',
'Jun' => 'June',
'Jul' => 'July',
'Aug' => 'August',
'Sep' => 'September',
'Oct' => 'October',
'Nov' => 'November',
'Dec' => 'December',
];
/**
* @var string[]
*/
public static $numberSuffixes = [
'st',
'nd',
'rd',
'th',
];
/**
* Base calendar year to use for calculations
* Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904).
*
* @var int
*/
protected static $excelCalendar = self::CALENDAR_WINDOWS_1900;
/**
* Default timezone to use for DateTime objects.
*
* @var null|DateTimeZone
*/
protected static $defaultTimeZone;
/**
* Set the Excel calendar (Windows 1900 or Mac 1904).
*
* @param int $baseYear Excel base date (1900 or 1904)
*
* @return bool Success or failure
*/
public static function setExcelCalendar($baseYear)
{
if (
($baseYear == self::CALENDAR_WINDOWS_1900) ||
($baseYear == self::CALENDAR_MAC_1904)
) {
self::$excelCalendar = $baseYear;
return true;
}
return false;
}
/**
* Return the Excel calendar (Windows 1900 or Mac 1904).
*
* @return int Excel base date (1900 or 1904)
*/
public static function getExcelCalendar()
{
return self::$excelCalendar;
}
/**
* Set the Default timezone to use for dates.
*
* @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions
*
* @return bool Success or failure
*/
public static function setDefaultTimezone($timeZone)
{
try {
$timeZone = self::validateTimeZone($timeZone);
self::$defaultTimeZone = $timeZone;
$retval = true;
} catch (PhpSpreadsheetException $e) {
$retval = false;
}
return $retval;
}
/**
* Return the Default timezone, or UTC if default not set.
*/
public static function getDefaultTimezone(): DateTimeZone
{
return self::$defaultTimeZone ?? new DateTimeZone('UTC');
}
/**
* Return the Default timezone, or local timezone if default is not set.
*/
public static function getDefaultOrLocalTimezone(): DateTimeZone
{
return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get());
}
/**
* Return the Default timezone even if null.
*/
public static function getDefaultTimezoneOrNull(): ?DateTimeZone
{
return self::$defaultTimeZone;
}
/**
* Validate a timezone.
*
* @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object
*
* @return ?DateTimeZone The timezone as a timezone object
*/
private static function validateTimeZone($timeZone)
{
if ($timeZone instanceof DateTimeZone || $timeZone === null) {
return $timeZone;
}
if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) {
return new DateTimeZone($timeZone);
}
throw new PhpSpreadsheetException('Invalid timezone');
}
/**
* @param mixed $value Converts a date/time in ISO-8601 standard format date string to an Excel
* serialized timestamp.
* See https://en.wikipedia.org/wiki/ISO_8601 for details of the ISO-8601 standard format.
*
* @return float|int
*/
public static function convertIsoDate($value)
{
if (!is_string($value)) {
throw new Exception('Non-string value supplied for Iso Date conversion');
}
$date = new DateTime($value);
$dateErrors = DateTime::getLastErrors();
if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
$newValue = SharedDate::PHPToExcel($date);
if ($newValue === false) {
throw new Exception("Invalid string $value supplied for datatype Date");
}
if (preg_match('/^\\s*\\d?\\d:\\d\\d(:\\d\\d([.]\\d+)?)?\\s*(am|pm)?\\s*$/i', $value) == 1) {
$newValue = fmod($newValue, 1.0);
}
return $newValue;
}
/**
* Convert a MS serialized datetime value from Excel to a PHP Date/Time object.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UTC) unless you absolutely need a conversion
*
* @return DateTime PHP date/time object
*/
public static function excelToDateTimeObject($excelTimestamp, $timeZone = null)
{
$timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone);
if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) {
// Unix timestamp base date
$baseDate = new DateTime('1970-01-01', $timeZone);
} else {
// MS Excel calendar base dates
if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
// Allow adjustment for 1900 Leap Year in MS Excel
$baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone);
} else {
$baseDate = new DateTime('1904-01-01', $timeZone);
}
}
} else {
$baseDate = new DateTime('1899-12-30', $timeZone);
}
$days = floor($excelTimestamp);
$partDay = $excelTimestamp - $days;
$hours = floor($partDay * 24);
$partDay = $partDay * 24 - $hours;
$minutes = floor($partDay * 60);
$partDay = $partDay * 60 - $minutes;
$seconds = round($partDay * 60);
if ($days >= 0) {
$days = '+' . $days;
}
$interval = $days . ' days';
return $baseDate->modify($interval)
->setTime((int) $hours, (int) $minutes, (int) $seconds);
}
/**
* Convert a MS serialized datetime value from Excel to a unix timestamp.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param float|int $excelTimestamp MS Excel serialized date/time value
* @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp,
* if you don't want to treat it as a UTC value
* Use the default (UTC) unless you absolutely need a conversion
*
* @return int Unix timetamp for this date/time
*/
public static function excelToTimestamp($excelTimestamp, $timeZone = null)
{
return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone)
->format('U');
}
/**
* Convert a date from PHP to an MS Excel serialized date/time value.
*
* @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged;
* not Y2038-safe on a 32-bit system, and no timezone info
*
* @return false|float Excel date/time value
* or boolean FALSE on failure
*/
public static function PHPToExcel($dateValue)
{
if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) {
return self::dateTimeToExcel($dateValue);
} elseif (is_numeric($dateValue)) {
return self::timestampToExcel($dateValue);
} elseif (is_string($dateValue)) {
return self::stringToExcel($dateValue);
}
return false;
}
/**
* Convert a PHP DateTime object to an MS Excel serialized date/time value.
*
* @param DateTimeInterface $dateValue PHP DateTime object
*
* @return float MS Excel serialized date/time value
*/
public static function dateTimeToExcel(DateTimeInterface $dateValue)
{
return self::formattedPHPToExcel(
(int) $dateValue->format('Y'),
(int) $dateValue->format('m'),
(int) $dateValue->format('d'),
(int) $dateValue->format('H'),
(int) $dateValue->format('i'),
(int) $dateValue->format('s')
);
}
/**
* Convert a Unix timestamp to an MS Excel serialized date/time value.
* The use of Unix timestamps, and therefore this function, is discouraged.
* They are not Y2038-safe on a 32-bit system, and have no timezone info.
*
* @param float|int|string $unixTimestamp Unix Timestamp
*
* @return false|float MS Excel serialized date/time value
*/
public static function timestampToExcel($unixTimestamp)
{
if (!is_numeric($unixTimestamp)) {
return false;
}
return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp));
}
/**
* formattedPHPToExcel.
*
* @param int $year
* @param int $month
* @param int $day
* @param int $hours
* @param int $minutes
* @param int $seconds
*
* @return float Excel date/time value
*/
public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0)
{
if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) {
//
// Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel
// This affects every date following 28th February 1900
//
$excel1900isLeapYear = true;
if (($year == 1900) && ($month <= 2)) {
$excel1900isLeapYear = false;
}
$myexcelBaseDate = 2415020;
} else {
$myexcelBaseDate = 2416481;
$excel1900isLeapYear = false;
}
// Julian base date Adjustment
if ($month > 2) {
$month -= 3;
} else {
$month += 9;
--$year;
}
// Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0)
$century = (int) substr((string) $year, 0, 2);
$decade = (int) substr((string) $year, 2, 2);
$excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear;
$excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400;
return (float) $excelDate + $excelTime;
}
/**
* Is a given cell a date/time?
*
* @param mixed $value
*
* @return bool
*/
public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true)
{
$result = false;
$worksheet = $cell->getWorksheetOrNull();
$spreadsheet = ($worksheet === null) ? null : $worksheet->getParent();
if ($worksheet !== null && $spreadsheet !== null) {
$index = $spreadsheet->getActiveSheetIndex();
$selected = $worksheet->getSelectedCells();
try {
$result = is_numeric($value ?? $cell->getCalculatedValue()) &&
self::isDateTimeFormat(
$worksheet->getStyle(
$cell->getCoordinate()
)->getNumberFormat(),
$dateWithoutTimeOkay
);
} catch (Exception $e) {
// Result is already false, so no need to actually do anything here
}
$worksheet->setSelectedCells($selected);
$spreadsheet->setActiveSheetIndex($index);
}
return $result;
}
/**
* Is a given NumberFormat code a date/time format code?
*
* @return bool
*/
public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true)
{
return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay);
}
private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs';
private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity
/**
* Is a given number format code a date/time?
*
* @param string $excelFormatCode
*
* @return bool
*/
public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true)
{
if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) {
// "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check)
return false;
}
if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) {
// Scientific format
return false;
}
// Switch on formatcode
if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) {
return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY);
}
// Typically number, currency or accounting (or occasionally fraction) formats
if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) {
return false;
}
// Some "special formats" provided in German Excel versions were detected as date time value,
// so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany).
if (\strpos($excelFormatCode, '-00000') !== false) {
return false;
}
$possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS;
// Try checking for any of the date formatting characters that don't appear within square braces
if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) {
// We might also have a format mask containing quoted strings...
// we don't want to test for any of our characters within the quoted blocks
if (strpos($excelFormatCode, '"') !== false) {
$segMatcher = false;
foreach (explode('"', $excelFormatCode) as $subVal) {
// Only test in alternate array entries (the non-quoted blocks)
$segMatcher = $segMatcher === false;
if (
$segMatcher &&
(preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal))
) {
return true;
}
}
return false;
}
return true;
}
// No date...
return false;
}
/**
* Convert a date/time string to Excel time.
*
* @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10'
*
* @return false|float Excel date/time serial value
*/
public static function stringToExcel($dateValue)
{
if (strlen($dateValue) < 2) {
return false;
}
if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) {
return false;
}
$dateValueNew = DateTimeExcel\DateValue::fromString($dateValue);
if (!is_float($dateValueNew)) {
return false;
}
if (strpos($dateValue, ':') !== false) {
$timeValue = DateTimeExcel\TimeValue::fromString($dateValue);
if (!is_float($timeValue)) {
return false;
}
$dateValueNew += $timeValue;
}
return $dateValueNew;
}
/**
* Converts a month name (either a long or a short name) to a month number.
*
* @param string $monthName Month name or abbreviation
*
* @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name
*/
public static function monthStringToNumber($monthName)
{
$monthIndex = 1;
foreach (self::$monthNames as $shortMonthName => $longMonthName) {
if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) {
return $monthIndex;
}
++$monthIndex;
}
return $monthName;
}
/**
* Strips an ordinal from a numeric value.
*
* @param string $day Day number with an ordinal
*
* @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric
*/
public static function dayStringToNumber($day)
{
$strippedDayValue = (str_replace(self::$numberSuffixes, '', $day));
if (is_numeric($strippedDayValue)) {
return (int) $strippedDayValue;
}
return $day;
}
public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime
{
$dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime();
$dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone());
return $dtobj;
}
public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string
{
$dtobj = self::dateTimeFromTimestamp($date, $timeZone);
return $dtobj->format($format);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php 0000644 00000011115 15243417764 0016673 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class CodePage
{
public const DEFAULT_CODE_PAGE = 'CP1252';
/** @var array */
private static $pageArray = [
0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program
367 => 'ASCII', // ASCII
437 => 'CP437', // OEM US
//720 => 'notsupported', // OEM Arabic
737 => 'CP737', // OEM Greek
775 => 'CP775', // OEM Baltic
850 => 'CP850', // OEM Latin I
852 => 'CP852', // OEM Latin II (Central European)
855 => 'CP855', // OEM Cyrillic
857 => 'CP857', // OEM Turkish
858 => 'CP858', // OEM Multilingual Latin I with Euro
860 => 'CP860', // OEM Portugese
861 => 'CP861', // OEM Icelandic
862 => 'CP862', // OEM Hebrew
863 => 'CP863', // OEM Canadian (French)
864 => 'CP864', // OEM Arabic
865 => 'CP865', // OEM Nordic
866 => 'CP866', // OEM Cyrillic (Russian)
869 => 'CP869', // OEM Greek (Modern)
874 => 'CP874', // ANSI Thai
932 => 'CP932', // ANSI Japanese Shift-JIS
936 => 'CP936', // ANSI Chinese Simplified GBK
949 => 'CP949', // ANSI Korean (Wansung)
950 => 'CP950', // ANSI Chinese Traditional BIG5
1200 => 'UTF-16LE', // UTF-16 (BIFF8)
1250 => 'CP1250', // ANSI Latin II (Central European)
1251 => 'CP1251', // ANSI Cyrillic
1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7)
1253 => 'CP1253', // ANSI Greek
1254 => 'CP1254', // ANSI Turkish
1255 => 'CP1255', // ANSI Hebrew
1256 => 'CP1256', // ANSI Arabic
1257 => 'CP1257', // ANSI Baltic
1258 => 'CP1258', // ANSI Vietnamese
1361 => 'CP1361', // ANSI Korean (Johab)
10000 => 'MAC', // Apple Roman
10001 => 'CP932', // Macintosh Japanese
10002 => 'CP950', // Macintosh Chinese Traditional
10003 => 'CP1361', // Macintosh Korean
10004 => 'MACARABIC', // Apple Arabic
10005 => 'MACHEBREW', // Apple Hebrew
10006 => 'MACGREEK', // Macintosh Greek
10007 => 'MACCYRILLIC', // Macintosh Cyrillic
10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312)
10010 => 'MACROMANIA', // Macintosh Romania
10017 => 'MACUKRAINE', // Macintosh Ukraine
10021 => 'MACTHAI', // Macintosh Thai
10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe
10079 => 'MACICELAND', // Macintosh Icelandic
10081 => 'MACTURKISH', // Macintosh Turkish
10082 => 'MACCROATIAN', // Macintosh Croatian
21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE
32768 => 'MAC', // Apple Roman
//32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3)
65000 => 'UTF-7', // Unicode (UTF-7)
65001 => 'UTF-8', // Unicode (UTF-8)
99999 => ['unsupported'], // Unicode (UTF-8)
];
public static function validate(string $codePage): bool
{
return in_array($codePage, self::$pageArray, true);
}
/**
* Convert Microsoft Code Page Identifier to Code Page Name which iconv
* and mbstring understands.
*
* @param int $codePage Microsoft Code Page Indentifier
*
* @return string Code Page Name
*/
public static function numberToName(int $codePage): string
{
if (array_key_exists($codePage, self::$pageArray)) {
$value = self::$pageArray[$codePage];
if (is_array($value)) {
foreach ($value as $encoding) {
if (@iconv('UTF-8', $encoding, ' ') !== false) {
self::$pageArray[$codePage] = $encoding;
return $encoding;
}
}
throw new PhpSpreadsheetException("Code page $codePage not implemented on this system.");
} else {
return $value;
}
}
if ($codePage == 720 || $codePage == 32769) {
throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic
}
throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage);
}
public static function getEncodings(): array
{
return self::$pageArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php 0000644 00000042743 15243417764 0015656 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
/*
* Array for storing OLE instances that are accessed from
* OLE_ChainedBlockStream::stream_open().
*
* @var array
*/
$GLOBALS['_OLE_INSTANCES'] = [];
/**
* OLE package base class.
*
* @author Xavier Noguer <xnoguer@php.net>
* @author Christian Schmidt <schmidt@php.net>
*/
class OLE
{
const OLE_PPS_TYPE_ROOT = 5;
const OLE_PPS_TYPE_DIR = 1;
const OLE_PPS_TYPE_FILE = 2;
const OLE_DATA_SIZE_SMALL = 0x1000;
const OLE_LONG_INT_SIZE = 4;
const OLE_PPS_SIZE = 0x80;
/**
* The file handle for reading an OLE container.
*
* @var resource
*/
public $_file_handle;
/**
* Array of PPS's found on the OLE container.
*
* @var array
*/
public $_list = [];
/**
* Root directory of OLE container.
*
* @var Root
*/
public $root;
/**
* Big Block Allocation Table.
*
* @var array (blockId => nextBlockId)
*/
public $bbat;
/**
* Short Block Allocation Table.
*
* @var array (blockId => nextBlockId)
*/
public $sbat;
/**
* Size of big blocks. This is usually 512.
*
* @var int number of octets per block
*/
public $bigBlockSize;
/**
* Size of small blocks. This is usually 64.
*
* @var int number of octets per block
*/
public $smallBlockSize;
/**
* Threshold for big blocks.
*
* @var int
*/
public $bigBlockThreshold;
/**
* Reads an OLE container from the contents of the file given.
*
* @acces public
*
* @param string $filename
*
* @return bool true on success, PEAR_Error on failure
*/
public function read($filename)
{
$fh = @fopen($filename, 'rb');
if ($fh === false) {
throw new ReaderException("Can't open file $filename");
}
$this->_file_handle = $fh;
$signature = fread($fh, 8);
if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
throw new ReaderException("File doesn't seem to be an OLE container.");
}
fseek($fh, 28);
if (fread($fh, 2) != "\xFE\xFF") {
// This shouldn't be a problem in practice
throw new ReaderException('Only Little-Endian encoding is supported.');
}
// Size of blocks and short blocks in bytes
$this->bigBlockSize = 2 ** self::readInt2($fh);
$this->smallBlockSize = 2 ** self::readInt2($fh);
// Skip UID, revision number and version number
fseek($fh, 44);
// Number of blocks in Big Block Allocation Table
$bbatBlockCount = self::readInt4($fh);
// Root chain 1st block
$directoryFirstBlockId = self::readInt4($fh);
// Skip unused bytes
fseek($fh, 56);
// Streams shorter than this are stored using small blocks
$this->bigBlockThreshold = self::readInt4($fh);
// Block id of first sector in Short Block Allocation Table
$sbatFirstBlockId = self::readInt4($fh);
// Number of blocks in Short Block Allocation Table
$sbbatBlockCount = self::readInt4($fh);
// Block id of first sector in Master Block Allocation Table
$mbatFirstBlockId = self::readInt4($fh);
// Number of blocks in Master Block Allocation Table
$mbbatBlockCount = self::readInt4($fh);
$this->bbat = [];
// Remaining 4 * 109 bytes of current block is beginning of Master
// Block Allocation Table
$mbatBlocks = [];
for ($i = 0; $i < 109; ++$i) {
$mbatBlocks[] = self::readInt4($fh);
}
// Read rest of Master Block Allocation Table (if any is left)
$pos = $this->getBlockOffset($mbatFirstBlockId);
for ($i = 0; $i < $mbbatBlockCount; ++$i) {
fseek($fh, $pos);
for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
$mbatBlocks[] = self::readInt4($fh);
}
// Last block id in each block points to next block
$pos = $this->getBlockOffset(self::readInt4($fh));
}
// Read Big Block Allocation Table according to chain specified by $mbatBlocks
for ($i = 0; $i < $bbatBlockCount; ++$i) {
$pos = $this->getBlockOffset($mbatBlocks[$i]);
fseek($fh, $pos);
for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
$this->bbat[] = self::readInt4($fh);
}
}
// Read short block allocation table (SBAT)
$this->sbat = [];
$shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
$sbatFh = $this->getStream($sbatFirstBlockId);
for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
$this->sbat[$blockId] = self::readInt4($sbatFh);
}
fclose($sbatFh);
$this->readPpsWks($directoryFirstBlockId);
return true;
}
/**
* @param int $blockId byte offset from beginning of file
*
* @return int
*/
public function getBlockOffset($blockId)
{
return 512 + $blockId * $this->bigBlockSize;
}
/**
* Returns a stream for use with fread() etc. External callers should
* use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
*
* @param int|OLE\PPS $blockIdOrPps block id or PPS
*
* @return resource read-only stream
*/
public function getStream($blockIdOrPps)
{
static $isRegistered = false;
if (!$isRegistered) {
stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
$isRegistered = true;
}
// Store current instance in global array, so that it can be accessed
// in OLE_ChainedBlockStream::stream_open().
// Object is removed from self::$instances in OLE_Stream::close().
$GLOBALS['_OLE_INSTANCES'][] = $this;
$keys = array_keys($GLOBALS['_OLE_INSTANCES']);
$instanceId = end($keys);
$path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
if ($blockIdOrPps instanceof OLE\PPS) {
$path .= '&blockId=' . $blockIdOrPps->startBlock;
$path .= '&size=' . $blockIdOrPps->Size;
} else {
$path .= '&blockId=' . $blockIdOrPps;
}
$resource = fopen($path, 'rb');
if ($resource === false) {
throw new Exception("Unable to open stream $path");
}
return $resource;
}
/**
* Reads a signed char.
*
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt1($fileHandle)
{
[, $tmp] = unpack('c', fread($fileHandle, 1) ?: '') ?: [0, 0];
return $tmp;
}
/**
* Reads an unsigned short (2 octets).
*
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt2($fileHandle)
{
[, $tmp] = unpack('v', fread($fileHandle, 2) ?: '') ?: [0, 0];
return $tmp;
}
private const SIGNED_4OCTET_LIMIT = 2147483648;
private const SIGNED_4OCTET_SUBTRACT = 2 * self::SIGNED_4OCTET_LIMIT;
/**
* Reads long (4 octets), interpreted as if signed on 32-bit system.
*
* @param resource $fileHandle file handle
*
* @return int
*/
private static function readInt4($fileHandle)
{
[, $tmp] = unpack('V', fread($fileHandle, 4) ?: '') ?: [0, 0];
if ($tmp >= self::SIGNED_4OCTET_LIMIT) {
$tmp -= self::SIGNED_4OCTET_SUBTRACT;
}
return $tmp;
}
/**
* Gets information about all PPS's on the OLE container from the PPS WK's
* creates an OLE_PPS object for each one.
*
* @param int $blockId the block id of the first block
*
* @return bool true on success, PEAR_Error on failure
*/
public function readPpsWks($blockId)
{
$fh = $this->getStream($blockId);
for ($pos = 0; true; $pos += 128) {
fseek($fh, $pos, SEEK_SET);
$nameUtf16 = (string) fread($fh, 64);
$nameLength = self::readInt2($fh);
$nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
// Simple conversion from UTF-16LE to ISO-8859-1
$name = str_replace("\x00", '', $nameUtf16);
$type = self::readInt1($fh);
switch ($type) {
case self::OLE_PPS_TYPE_ROOT:
$pps = new OLE\PPS\Root(null, null, []);
$this->root = $pps;
break;
case self::OLE_PPS_TYPE_DIR:
$pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
break;
case self::OLE_PPS_TYPE_FILE:
$pps = new OLE\PPS\File($name);
break;
default:
throw new Exception('Unsupported PPS type');
}
fseek($fh, 1, SEEK_CUR);
$pps->Type = $type;
$pps->Name = $name;
$pps->PrevPps = self::readInt4($fh);
$pps->NextPps = self::readInt4($fh);
$pps->DirPps = self::readInt4($fh);
fseek($fh, 20, SEEK_CUR);
$pps->Time1st = self::OLE2LocalDate((string) fread($fh, 8));
$pps->Time2nd = self::OLE2LocalDate((string) fread($fh, 8));
$pps->startBlock = self::readInt4($fh);
$pps->Size = self::readInt4($fh);
$pps->No = count($this->_list);
$this->_list[] = $pps;
// check if the PPS tree (starting from root) is complete
if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) { //* @phpstan-ignore-line
break;
}
}
fclose($fh);
// Initialize $pps->children on directories
foreach ($this->_list as $pps) {
if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
$nos = [$pps->DirPps];
$pps->children = [];
while (!empty($nos)) {
$no = array_pop($nos);
if ($no != -1) {
$childPps = $this->_list[$no];
$nos[] = $childPps->PrevPps;
$nos[] = $childPps->NextPps;
$pps->children[] = $childPps;
}
}
}
}
return true;
}
/**
* It checks whether the PPS tree is complete (all PPS's read)
* starting with the given PPS (not necessarily root).
*
* @param int $index The index of the PPS from which we are checking
*
* @return bool Whether the PPS tree for the given PPS is complete
*/
private function ppsTreeComplete($index)
{
return isset($this->_list[$index]) &&
($pps = $this->_list[$index]) &&
($pps->PrevPps == -1 ||
$this->ppsTreeComplete($pps->PrevPps)) &&
($pps->NextPps == -1 ||
$this->ppsTreeComplete($pps->NextPps)) &&
($pps->DirPps == -1 ||
$this->ppsTreeComplete($pps->DirPps));
}
/**
* Checks whether a PPS is a File PPS or not.
* If there is no PPS for the index given, it will return false.
*
* @param int $index The index for the PPS
*
* @return bool true if it's a File PPS, false otherwise
*/
public function isFile($index)
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
}
return false;
}
/**
* Checks whether a PPS is a Root PPS or not.
* If there is no PPS for the index given, it will return false.
*
* @param int $index the index for the PPS
*
* @return bool true if it's a Root PPS, false otherwise
*/
public function isRoot($index)
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
}
return false;
}
/**
* Gives the total number of PPS's found in the OLE container.
*
* @return int The total number of PPS's found in the OLE container
*/
public function ppsTotal()
{
return count($this->_list);
}
/**
* Gets data from a PPS
* If there is no PPS for the index given, it will return an empty string.
*
* @param int $index The index for the PPS
* @param int $position The position from which to start reading
* (relative to the PPS)
* @param int $length The amount of bytes to read (at most)
*
* @return string The binary string containing the data requested
*
* @see OLE_PPS_File::getStream()
*/
public function getData($index, $position, $length)
{
// if position is not valid return empty string
if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
return '';
}
$fh = $this->getStream($this->_list[$index]);
$data = (string) stream_get_contents($fh, $length, $position);
fclose($fh);
return $data;
}
/**
* Gets the data length from a PPS
* If there is no PPS for the index given, it will return 0.
*
* @param int $index The index for the PPS
*
* @return int The amount of bytes in data the PPS has
*/
public function getDataLength($index)
{
if (isset($this->_list[$index])) {
return $this->_list[$index]->Size;
}
return 0;
}
/**
* Utility function to transform ASCII text to Unicode.
*
* @param string $ascii The ASCII string to transform
*
* @return string The string in Unicode
*/
public static function ascToUcs($ascii)
{
$rawname = '';
$iMax = strlen($ascii);
for ($i = 0; $i < $iMax; ++$i) {
$rawname .= $ascii[$i]
. "\x00";
}
return $rawname;
}
/**
* Utility function
* Returns a string for the OLE container with the date given.
*
* @param float|int $date A timestamp
*
* @return string The string for the OLE container
*/
public static function localDateToOLE($date)
{
if (!$date) {
return "\x00\x00\x00\x00\x00\x00\x00\x00";
}
$dateTime = Date::dateTimeFromTimestamp("$date");
// days from 1-1-1601 until the beggining of UNIX era
$days = 134774;
// calculate seconds
$big_date = $days * 24 * 3600 + (float) $dateTime->format('U');
// multiply just to make MS happy
$big_date *= 10000000;
// Make HEX string
$res = '';
$factor = 2 ** 56;
while ($factor >= 1) {
$hex = (int) floor($big_date / $factor);
$res = pack('c', $hex) . $res;
$big_date = fmod($big_date, $factor);
$factor /= 256;
}
return $res;
}
/**
* Returns a timestamp from an OLE container's date.
*
* @param string $oleTimestamp A binary string with the encoded date
*
* @return float|int The Unix timestamp corresponding to the string
*/
public static function OLE2LocalDate($oleTimestamp)
{
if (strlen($oleTimestamp) != 8) {
throw new ReaderException('Expecting 8 byte string');
}
// convert to units of 100 ns since 1601:
$unpackedTimestamp = unpack('v4', $oleTimestamp) ?: [];
$timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
$timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
// translate to seconds since 1601:
$timestampHigh /= 10000000;
$timestampLow /= 10000000;
// days from 1601 to 1970:
$days = 134774;
// translate to seconds since 1970:
$unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
return IntOrFloat::evaluate($unixTimestamp);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php 0000644 00000002213 15243417764 0016434 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class Escher
{
/**
* Drawing Group Container.
*
* @var ?Escher\DggContainer
*/
private $dggContainer;
/**
* Drawing Container.
*
* @var ?Escher\DgContainer
*/
private $dgContainer;
/**
* Get Drawing Group Container.
*
* @return ?Escher\DggContainer
*/
public function getDggContainer()
{
return $this->dggContainer;
}
/**
* Set Drawing Group Container.
*
* @param Escher\DggContainer $dggContainer
*
* @return Escher\DggContainer
*/
public function setDggContainer($dggContainer)
{
return $this->dggContainer = $dggContainer;
}
/**
* Get Drawing Container.
*
* @return ?Escher\DgContainer
*/
public function getDgContainer()
{
return $this->dgContainer;
}
/**
* Set Drawing Container.
*
* @param Escher\DgContainer $dgContainer
*
* @return Escher\DgContainer
*/
public function setDgContainer($dgContainer)
{
return $this->dgContainer = $dgContainer;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php 0000644 00000056157 15243417764 0017651 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class StringHelper
{
/**
* Control characters array.
*
* @var string[]
*/
private static $controlCharacters = [];
/**
* SYLK Characters array.
*
* @var array
*/
private static $SYLKCharacters = [];
/**
* Decimal separator.
*
* @var ?string
*/
private static $decimalSeparator;
/**
* Thousands separator.
*
* @var ?string
*/
private static $thousandsSeparator;
/**
* Currency code.
*
* @var string
*/
private static $currencyCode;
/**
* Is iconv extension avalable?
*
* @var ?bool
*/
private static $isIconvEnabled;
/**
* iconv options.
*
* @var string
*/
private static $iconvOptions = '//IGNORE//TRANSLIT';
/**
* Build control characters array.
*/
private static function buildControlCharacters(): void
{
for ($i = 0; $i <= 31; ++$i) {
if ($i != 9 && $i != 10 && $i != 13) {
$find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_';
$replace = chr($i);
self::$controlCharacters[$find] = $replace;
}
}
}
/**
* Build SYLK characters array.
*/
private static function buildSYLKCharacters(): void
{
self::$SYLKCharacters = [
"\x1B 0" => chr(0),
"\x1B 1" => chr(1),
"\x1B 2" => chr(2),
"\x1B 3" => chr(3),
"\x1B 4" => chr(4),
"\x1B 5" => chr(5),
"\x1B 6" => chr(6),
"\x1B 7" => chr(7),
"\x1B 8" => chr(8),
"\x1B 9" => chr(9),
"\x1B :" => chr(10),
"\x1B ;" => chr(11),
"\x1B <" => chr(12),
"\x1B =" => chr(13),
"\x1B >" => chr(14),
"\x1B ?" => chr(15),
"\x1B!0" => chr(16),
"\x1B!1" => chr(17),
"\x1B!2" => chr(18),
"\x1B!3" => chr(19),
"\x1B!4" => chr(20),
"\x1B!5" => chr(21),
"\x1B!6" => chr(22),
"\x1B!7" => chr(23),
"\x1B!8" => chr(24),
"\x1B!9" => chr(25),
"\x1B!:" => chr(26),
"\x1B!;" => chr(27),
"\x1B!<" => chr(28),
"\x1B!=" => chr(29),
"\x1B!>" => chr(30),
"\x1B!?" => chr(31),
"\x1B'?" => chr(127),
"\x1B(0" => '€', // 128 in CP1252
"\x1B(2" => '‚', // 130 in CP1252
"\x1B(3" => 'ƒ', // 131 in CP1252
"\x1B(4" => '„', // 132 in CP1252
"\x1B(5" => '…', // 133 in CP1252
"\x1B(6" => '†', // 134 in CP1252
"\x1B(7" => '‡', // 135 in CP1252
"\x1B(8" => 'ˆ', // 136 in CP1252
"\x1B(9" => '‰', // 137 in CP1252
"\x1B(:" => 'Š', // 138 in CP1252
"\x1B(;" => '‹', // 139 in CP1252
"\x1BNj" => 'Œ', // 140 in CP1252
"\x1B(>" => 'Ž', // 142 in CP1252
"\x1B)1" => '‘', // 145 in CP1252
"\x1B)2" => '’', // 146 in CP1252
"\x1B)3" => '“', // 147 in CP1252
"\x1B)4" => '”', // 148 in CP1252
"\x1B)5" => '•', // 149 in CP1252
"\x1B)6" => '–', // 150 in CP1252
"\x1B)7" => '—', // 151 in CP1252
"\x1B)8" => '˜', // 152 in CP1252
"\x1B)9" => '™', // 153 in CP1252
"\x1B):" => 'š', // 154 in CP1252
"\x1B);" => '›', // 155 in CP1252
"\x1BNz" => 'œ', // 156 in CP1252
"\x1B)>" => 'ž', // 158 in CP1252
"\x1B)?" => 'Ÿ', // 159 in CP1252
"\x1B*0" => ' ', // 160 in CP1252
"\x1BN!" => '¡', // 161 in CP1252
"\x1BN\"" => '¢', // 162 in CP1252
"\x1BN#" => '£', // 163 in CP1252
"\x1BN(" => '¤', // 164 in CP1252
"\x1BN%" => '¥', // 165 in CP1252
"\x1B*6" => '¦', // 166 in CP1252
"\x1BN'" => '§', // 167 in CP1252
"\x1BNH " => '¨', // 168 in CP1252
"\x1BNS" => '©', // 169 in CP1252
"\x1BNc" => 'ª', // 170 in CP1252
"\x1BN+" => '«', // 171 in CP1252
"\x1B*<" => '¬', // 172 in CP1252
"\x1B*=" => '', // 173 in CP1252
"\x1BNR" => '®', // 174 in CP1252
"\x1B*?" => '¯', // 175 in CP1252
"\x1BN0" => '°', // 176 in CP1252
"\x1BN1" => '±', // 177 in CP1252
"\x1BN2" => '²', // 178 in CP1252
"\x1BN3" => '³', // 179 in CP1252
"\x1BNB " => '´', // 180 in CP1252
"\x1BN5" => 'µ', // 181 in CP1252
"\x1BN6" => '¶', // 182 in CP1252
"\x1BN7" => '·', // 183 in CP1252
"\x1B+8" => '¸', // 184 in CP1252
"\x1BNQ" => '¹', // 185 in CP1252
"\x1BNk" => 'º', // 186 in CP1252
"\x1BN;" => '»', // 187 in CP1252
"\x1BN<" => '¼', // 188 in CP1252
"\x1BN=" => '½', // 189 in CP1252
"\x1BN>" => '¾', // 190 in CP1252
"\x1BN?" => '¿', // 191 in CP1252
"\x1BNAA" => 'À', // 192 in CP1252
"\x1BNBA" => 'Á', // 193 in CP1252
"\x1BNCA" => 'Â', // 194 in CP1252
"\x1BNDA" => 'Ã', // 195 in CP1252
"\x1BNHA" => 'Ä', // 196 in CP1252
"\x1BNJA" => 'Å', // 197 in CP1252
"\x1BNa" => 'Æ', // 198 in CP1252
"\x1BNKC" => 'Ç', // 199 in CP1252
"\x1BNAE" => 'È', // 200 in CP1252
"\x1BNBE" => 'É', // 201 in CP1252
"\x1BNCE" => 'Ê', // 202 in CP1252
"\x1BNHE" => 'Ë', // 203 in CP1252
"\x1BNAI" => 'Ì', // 204 in CP1252
"\x1BNBI" => 'Í', // 205 in CP1252
"\x1BNCI" => 'Î', // 206 in CP1252
"\x1BNHI" => 'Ï', // 207 in CP1252
"\x1BNb" => 'Ð', // 208 in CP1252
"\x1BNDN" => 'Ñ', // 209 in CP1252
"\x1BNAO" => 'Ò', // 210 in CP1252
"\x1BNBO" => 'Ó', // 211 in CP1252
"\x1BNCO" => 'Ô', // 212 in CP1252
"\x1BNDO" => 'Õ', // 213 in CP1252
"\x1BNHO" => 'Ö', // 214 in CP1252
"\x1B-7" => '×', // 215 in CP1252
"\x1BNi" => 'Ø', // 216 in CP1252
"\x1BNAU" => 'Ù', // 217 in CP1252
"\x1BNBU" => 'Ú', // 218 in CP1252
"\x1BNCU" => 'Û', // 219 in CP1252
"\x1BNHU" => 'Ü', // 220 in CP1252
"\x1B-=" => 'Ý', // 221 in CP1252
"\x1BNl" => 'Þ', // 222 in CP1252
"\x1BN{" => 'ß', // 223 in CP1252
"\x1BNAa" => 'à', // 224 in CP1252
"\x1BNBa" => 'á', // 225 in CP1252
"\x1BNCa" => 'â', // 226 in CP1252
"\x1BNDa" => 'ã', // 227 in CP1252
"\x1BNHa" => 'ä', // 228 in CP1252
"\x1BNJa" => 'å', // 229 in CP1252
"\x1BNq" => 'æ', // 230 in CP1252
"\x1BNKc" => 'ç', // 231 in CP1252
"\x1BNAe" => 'è', // 232 in CP1252
"\x1BNBe" => 'é', // 233 in CP1252
"\x1BNCe" => 'ê', // 234 in CP1252
"\x1BNHe" => 'ë', // 235 in CP1252
"\x1BNAi" => 'ì', // 236 in CP1252
"\x1BNBi" => 'í', // 237 in CP1252
"\x1BNCi" => 'î', // 238 in CP1252
"\x1BNHi" => 'ï', // 239 in CP1252
"\x1BNs" => 'ð', // 240 in CP1252
"\x1BNDn" => 'ñ', // 241 in CP1252
"\x1BNAo" => 'ò', // 242 in CP1252
"\x1BNBo" => 'ó', // 243 in CP1252
"\x1BNCo" => 'ô', // 244 in CP1252
"\x1BNDo" => 'õ', // 245 in CP1252
"\x1BNHo" => 'ö', // 246 in CP1252
"\x1B/7" => '÷', // 247 in CP1252
"\x1BNy" => 'ø', // 248 in CP1252
"\x1BNAu" => 'ù', // 249 in CP1252
"\x1BNBu" => 'ú', // 250 in CP1252
"\x1BNCu" => 'û', // 251 in CP1252
"\x1BNHu" => 'ü', // 252 in CP1252
"\x1B/=" => 'ý', // 253 in CP1252
"\x1BN|" => 'þ', // 254 in CP1252
"\x1BNHy" => 'ÿ', // 255 in CP1252
];
}
/**
* Get whether iconv extension is available.
*
* @return bool
*/
public static function getIsIconvEnabled()
{
if (isset(self::$isIconvEnabled)) {
return self::$isIconvEnabled;
}
// Assume no problems with iconv
self::$isIconvEnabled = true;
// Fail if iconv doesn't exist
if (!function_exists('iconv')) {
self::$isIconvEnabled = false;
} elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
// Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
self::$isIconvEnabled = false;
} elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
// CUSTOM: IBM AIX iconv() does not work
self::$isIconvEnabled = false;
}
// Deactivate iconv default options if they fail (as seen on IMB i)
if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
self::$iconvOptions = '';
}
return self::$isIconvEnabled;
}
private static function buildCharacterSets(): void
{
if (empty(self::$controlCharacters)) {
self::buildControlCharacters();
}
if (empty(self::$SYLKCharacters)) {
self::buildSYLKCharacters();
}
}
/**
* Convert from OpenXML escaped control character to PHP control character.
*
* Excel 2007 team:
* ----------------
* That's correct, control characters are stored directly in the shared-strings table.
* We do encode characters that cannot be represented in XML using the following escape sequence:
* _xHHHH_ where H represents a hexadecimal character in the character's value...
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $textValue Value to unescape
*
* @return string
*/
public static function controlCharacterOOXML2PHP($textValue)
{
self::buildCharacterSets();
return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue);
}
/**
* Convert from PHP control character to OpenXML escaped control character.
*
* Excel 2007 team:
* ----------------
* That's correct, control characters are stored directly in the shared-strings table.
* We do encode characters that cannot be represented in XML using the following escape sequence:
* _xHHHH_ where H represents a hexadecimal character in the character's value...
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $textValue Value to escape
*
* @return string
*/
public static function controlCharacterPHP2OOXML($textValue)
{
self::buildCharacterSets();
return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue);
}
/**
* Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters.
*/
public static function sanitizeUTF8(string $textValue): string
{
$textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue);
$subst = mb_substitute_character(); // default is question mark
mb_substitute_character(65533); // Unicode substitution character
// Phpstan does not think this can return false.
$returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8');
mb_substitute_character(/** @scrutinizer ignore-type */ $subst);
return self::returnString($returnValue);
}
/**
* Strictly to satisfy Scrutinizer.
*
* @param mixed $value
*/
private static function returnString($value): string
{
return is_string($value) ? $value : '';
}
/**
* Check if a string contains UTF8 data.
*/
public static function isUTF8(string $textValue): bool
{
return $textValue === self::sanitizeUTF8($textValue);
}
/**
* Formats a numeric value as a string for output in various output writers forcing
* point as decimal separator in case locale is other than English.
*
* @param float|int|string $numericValue
*/
public static function formatNumber($numericValue): string
{
if (is_float($numericValue)) {
return str_replace(',', '.', (string) $numericValue);
}
return (string) $numericValue;
}
/**
* Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length)
* Writes the string using uncompressed notation, no rich text, no Asian phonetics
* If mbstring extension is not available, ASCII is assumed, and compressed notation is used
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $textValue UTF-8 encoded string
* @param mixed[] $arrcRuns Details of rich text runs in $value
*/
public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string
{
// character count
$ln = self::countCharacters($textValue, 'UTF-8');
// option flags
if (empty($arrcRuns)) {
$data = pack('CC', $ln, 0x0001);
// characters
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
} else {
$data = pack('vC', $ln, 0x09);
$data .= pack('v', count($arrcRuns));
// characters
$data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
foreach ($arrcRuns as $cRun) {
$data .= pack('v', $cRun['strlen']);
$data .= pack('v', $cRun['fontidx']);
}
}
return $data;
}
/**
* Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length)
* Writes the string using uncompressed notation, no rich text, no Asian phonetics
* If mbstring extension is not available, ASCII is assumed, and compressed notation is used
* although this will give wrong results for non-ASCII strings
* see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
*
* @param string $textValue UTF-8 encoded string
*/
public static function UTF8toBIFF8UnicodeLong(string $textValue): string
{
// character count
$ln = self::countCharacters($textValue, 'UTF-8');
// characters
$chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8');
return pack('vC', $ln, 0x0001) . $chars;
}
/**
* Convert string from one encoding to another.
*
* @param string $to Encoding to convert to, e.g. 'UTF-8'
* @param string $from Encoding to convert from, e.g. 'UTF-16LE'
*/
public static function convertEncoding(string $textValue, string $to, string $from): string
{
if (self::getIsIconvEnabled()) {
$result = iconv($from, $to . self::$iconvOptions, $textValue);
if (false !== $result) {
return $result;
}
}
return self::returnString(mb_convert_encoding($textValue, $to, $from));
}
/**
* Get character count.
*
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strlen($textValue, $encoding);
}
/**
* Get character count using mb_strwidth rather than mb_strlen.
*
* @param string $encoding Encoding
*
* @return int Character count
*/
public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
{
return mb_strwidth($textValue, $encoding);
}
/**
* Get a substring of a UTF-8 encoded string.
*
* @param string $textValue UTF-8 encoded string
* @param int $offset Start offset
* @param ?int $length Maximum number of characters in substring
*/
public static function substring(string $textValue, int $offset, ?int $length = 0): string
{
return mb_substr($textValue, $offset, $length, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to upper case.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToUpper(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to lower case.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToLower(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8');
}
/**
* Convert a UTF-8 encoded string to title/proper case
* (uppercase every first character in each word, lower case all other characters).
*
* @param string $textValue UTF-8 encoded string
*/
public static function strToTitle(string $textValue): string
{
return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8');
}
public static function mbIsUpper(string $character): bool
{
return mb_strtolower($character, 'UTF-8') !== $character;
}
/**
* Splits a UTF-8 string into an array of individual characters.
*/
public static function mbStrSplit(string $string): array
{
// Split at all position not after the start: ^
// and not before the end: $
$split = preg_split('/(?<!^)(?!$)/u', $string);
return ($split === false) ? [] : $split;
}
/**
* Reverse the case of a string, so that all uppercase characters become lowercase
* and all lowercase characters become uppercase.
*
* @param string $textValue UTF-8 encoded string
*/
public static function strCaseReverse(string $textValue): string
{
$characters = self::mbStrSplit($textValue);
foreach ($characters as &$character) {
if (self::mbIsUpper($character)) {
$character = mb_strtolower($character, 'UTF-8');
} else {
$character = mb_strtoupper($character, 'UTF-8');
}
}
return implode('', $characters);
}
/**
* Get the decimal separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*/
public static function getDecimalSeparator(): string
{
if (!isset(self::$decimalSeparator)) {
$localeconv = localeconv();
self::$decimalSeparator = ($localeconv['decimal_point'] != '')
? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
if (self::$decimalSeparator == '') {
// Default to .
self::$decimalSeparator = '.';
}
}
return self::$decimalSeparator;
}
/**
* Set the decimal separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $separator Character for decimal separator
*/
public static function setDecimalSeparator(string $separator): void
{
self::$decimalSeparator = $separator;
}
/**
* Get the thousands separator. If it has not yet been set explicitly, try to obtain number
* formatting information from locale.
*/
public static function getThousandsSeparator(): string
{
if (!isset(self::$thousandsSeparator)) {
$localeconv = localeconv();
self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
if (self::$thousandsSeparator == '') {
// Default to .
self::$thousandsSeparator = ',';
}
}
return self::$thousandsSeparator;
}
/**
* Set the thousands separator. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $separator Character for thousands separator
*/
public static function setThousandsSeparator(string $separator): void
{
self::$thousandsSeparator = $separator;
}
/**
* Get the currency code. If it has not yet been set explicitly, try to obtain the
* symbol information from locale.
*/
public static function getCurrencyCode(): string
{
if (!empty(self::$currencyCode)) {
return self::$currencyCode;
}
self::$currencyCode = '$';
$localeconv = localeconv();
if (!empty($localeconv['currency_symbol'])) {
self::$currencyCode = $localeconv['currency_symbol'];
return self::$currencyCode;
}
if (!empty($localeconv['int_curr_symbol'])) {
self::$currencyCode = $localeconv['int_curr_symbol'];
return self::$currencyCode;
}
return self::$currencyCode;
}
/**
* Set the currency code. Only used by NumberFormat::toFormattedString()
* to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
*
* @param string $currencyCode Character for currency code
*/
public static function setCurrencyCode(string $currencyCode): void
{
self::$currencyCode = $currencyCode;
}
/**
* Convert SYLK encoded string to UTF-8.
*
* @param string $textValue SYLK encoded string
*
* @return string UTF-8 encoded string
*/
public static function SYLKtoUTF8(string $textValue): string
{
self::buildCharacterSets();
// If there is no escape character in the string there is nothing to do
if (strpos($textValue, '') === false) {
return $textValue;
}
foreach (self::$SYLKCharacters as $k => $v) {
$textValue = str_replace($k, $v, $textValue);
}
return $textValue;
}
/**
* Retrieve any leading numeric part of a string, or return the full string if no leading numeric
* (handles basic integer or float, but not exponent or non decimal).
*
* @param string $textValue
*
* @return mixed string or only the leading numeric part of the string
*/
public static function testStringAsNumeric($textValue)
{
if (is_numeric($textValue)) {
return $textValue;
}
$v = (float) $textValue;
return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php 0000644 00000007654 15243417764 0020176 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as SpException;
use PhpOffice\PhpSpreadsheet\Worksheet\Protection;
class PasswordHasher
{
const MAX_PASSWORD_LENGTH = 255;
/**
* Get algorithm name for PHP.
*/
private static function getAlgorithm(string $algorithmName): string
{
if (!$algorithmName) {
return '';
}
// Mapping between algorithm name in Excel and algorithm name in PHP
$mapping = [
Protection::ALGORITHM_MD2 => 'md2',
Protection::ALGORITHM_MD4 => 'md4',
Protection::ALGORITHM_MD5 => 'md5',
Protection::ALGORITHM_SHA_1 => 'sha1',
Protection::ALGORITHM_SHA_256 => 'sha256',
Protection::ALGORITHM_SHA_384 => 'sha384',
Protection::ALGORITHM_SHA_512 => 'sha512',
Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
];
if (array_key_exists($algorithmName, $mapping)) {
return $mapping[$algorithmName];
}
throw new SpException('Unsupported password algorithm: ' . $algorithmName);
}
/**
* Create a password hash from a given string.
*
* This method is based on the spec at:
* https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf
* 2.3.7.1 Binary Document Password Verifier Derivation Method 1
*
* It replaces a method based on the algorithm provided by
* Daniel Rentz of OpenOffice and the PEAR package
* Spreadsheet_Excel_Writer by Xavier Noguer <xnoguer@rezebra.com>.
*
* Scrutinizer will squawk at the use of bitwise operations here,
* but it should ultimately pass.
*
* @param string $password Password to hash
*/
private static function defaultHashPassword(string $password): string
{
$verifier = 0;
$pwlen = strlen($password);
$passwordArray = pack('c', $pwlen) . $password;
for ($i = $pwlen; $i >= 0; --$i) {
$intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1;
$intermediate2 = 2 * $verifier;
$intermediate2 = $intermediate2 & 0x7fff;
$intermediate3 = $intermediate1 | $intermediate2;
$verifier = $intermediate3 ^ ord($passwordArray[$i]);
}
$verifier ^= 0xCE4B;
return strtoupper(dechex($verifier));
}
/**
* Create a password hash from a given string by a specific algorithm.
*
* 2.4.2.4 ISO Write Protection Method
*
* @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
*
* @param string $password Password to hash
* @param string $algorithm Hash algorithm used to compute the password hash value
* @param string $salt Pseudorandom string
* @param int $spinCount Number of times to iterate on a hash of a password
*
* @return string Hashed password
*/
public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
{
if (strlen($password) > self::MAX_PASSWORD_LENGTH) {
throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters');
}
$phpAlgorithm = self::getAlgorithm($algorithm);
if (!$phpAlgorithm) {
return self::defaultHashPassword($password);
}
$saltValue = base64_decode($salt);
$encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
$hashValue = hash($phpAlgorithm, $saltValue . /** @scrutinizer ignore-type */ $encodedPassword, true);
for ($i = 0; $i < $spinCount; ++$i) {
$hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
}
return base64_encode($hashValue);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php 0000644 00000060066 15243417764 0016143 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Font as FontStyle;
class Font
{
// Methods for resolving autosize value
const AUTOSIZE_METHOD_APPROX = 'approx';
const AUTOSIZE_METHOD_EXACT = 'exact';
private const AUTOSIZE_METHODS = [
self::AUTOSIZE_METHOD_APPROX,
self::AUTOSIZE_METHOD_EXACT,
];
/** Character set codes used by BIFF5-8 in Font records */
const CHARSET_ANSI_LATIN = 0x00;
const CHARSET_SYSTEM_DEFAULT = 0x01;
const CHARSET_SYMBOL = 0x02;
const CHARSET_APPLE_ROMAN = 0x4D;
const CHARSET_ANSI_JAPANESE_SHIFTJIS = 0x80;
const CHARSET_ANSI_KOREAN_HANGUL = 0x81;
const CHARSET_ANSI_KOREAN_JOHAB = 0x82;
const CHARSET_ANSI_CHINESE_SIMIPLIFIED = 0x86; // gb2312
const CHARSET_ANSI_CHINESE_TRADITIONAL = 0x88; // big5
const CHARSET_ANSI_GREEK = 0xA1;
const CHARSET_ANSI_TURKISH = 0xA2;
const CHARSET_ANSI_VIETNAMESE = 0xA3;
const CHARSET_ANSI_HEBREW = 0xB1;
const CHARSET_ANSI_ARABIC = 0xB2;
const CHARSET_ANSI_BALTIC = 0xBA;
const CHARSET_ANSI_CYRILLIC = 0xCC;
const CHARSET_ANSI_THAI = 0xDD;
const CHARSET_ANSI_LATIN_II = 0xEE;
const CHARSET_OEM_LATIN_I = 0xFF;
// XXX: Constants created!
/** Font filenames */
const ARIAL = 'arial.ttf';
const ARIAL_BOLD = 'arialbd.ttf';
const ARIAL_ITALIC = 'ariali.ttf';
const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
const CALIBRI = 'calibri.ttf';
const CALIBRI_BOLD = 'calibrib.ttf';
const CALIBRI_ITALIC = 'calibrii.ttf';
const CALIBRI_BOLD_ITALIC = 'calibriz.ttf';
const COMIC_SANS_MS = 'comic.ttf';
const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
const COURIER_NEW = 'cour.ttf';
const COURIER_NEW_BOLD = 'courbd.ttf';
const COURIER_NEW_ITALIC = 'couri.ttf';
const COURIER_NEW_BOLD_ITALIC = 'courbi.ttf';
const GEORGIA = 'georgia.ttf';
const GEORGIA_BOLD = 'georgiab.ttf';
const GEORGIA_ITALIC = 'georgiai.ttf';
const GEORGIA_BOLD_ITALIC = 'georgiaz.ttf';
const IMPACT = 'impact.ttf';
const LIBERATION_SANS = 'LiberationSans-Regular.ttf';
const LIBERATION_SANS_BOLD = 'LiberationSans-Bold.ttf';
const LIBERATION_SANS_ITALIC = 'LiberationSans-Italic.ttf';
const LIBERATION_SANS_BOLD_ITALIC = 'LiberationSans-BoldItalic.ttf';
const LUCIDA_CONSOLE = 'lucon.ttf';
const LUCIDA_SANS_UNICODE = 'l_10646.ttf';
const MICROSOFT_SANS_SERIF = 'micross.ttf';
const PALATINO_LINOTYPE = 'pala.ttf';
const PALATINO_LINOTYPE_BOLD = 'palab.ttf';
const PALATINO_LINOTYPE_ITALIC = 'palai.ttf';
const PALATINO_LINOTYPE_BOLD_ITALIC = 'palabi.ttf';
const SYMBOL = 'symbol.ttf';
const TAHOMA = 'tahoma.ttf';
const TAHOMA_BOLD = 'tahomabd.ttf';
const TIMES_NEW_ROMAN = 'times.ttf';
const TIMES_NEW_ROMAN_BOLD = 'timesbd.ttf';
const TIMES_NEW_ROMAN_ITALIC = 'timesi.ttf';
const TIMES_NEW_ROMAN_BOLD_ITALIC = 'timesbi.ttf';
const TREBUCHET_MS = 'trebuc.ttf';
const TREBUCHET_MS_BOLD = 'trebucbd.ttf';
const TREBUCHET_MS_ITALIC = 'trebucit.ttf';
const TREBUCHET_MS_BOLD_ITALIC = 'trebucbi.ttf';
const VERDANA = 'verdana.ttf';
const VERDANA_BOLD = 'verdanab.ttf';
const VERDANA_ITALIC = 'verdanai.ttf';
const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
const FONT_FILE_NAMES = [
'Arial' => [
'x' => self::ARIAL,
'xb' => self::ARIAL_BOLD,
'xi' => self::ARIAL_ITALIC,
'xbi' => self::ARIAL_BOLD_ITALIC,
],
'Calibri' => [
'x' => self::CALIBRI,
'xb' => self::CALIBRI_BOLD,
'xi' => self::CALIBRI_ITALIC,
'xbi' => self::CALIBRI_BOLD_ITALIC,
],
'Comic Sans MS' => [
'x' => self::COMIC_SANS_MS,
'xb' => self::COMIC_SANS_MS_BOLD,
'xi' => self::COMIC_SANS_MS,
'xbi' => self::COMIC_SANS_MS_BOLD,
],
'Courier New' => [
'x' => self::COURIER_NEW,
'xb' => self::COURIER_NEW_BOLD,
'xi' => self::COURIER_NEW_ITALIC,
'xbi' => self::COURIER_NEW_BOLD_ITALIC,
],
'Georgia' => [
'x' => self::GEORGIA,
'xb' => self::GEORGIA_BOLD,
'xi' => self::GEORGIA_ITALIC,
'xbi' => self::GEORGIA_BOLD_ITALIC,
],
'Impact' => [
'x' => self::IMPACT,
'xb' => self::IMPACT,
'xi' => self::IMPACT,
'xbi' => self::IMPACT,
],
'Liberation Sans' => [
'x' => self::LIBERATION_SANS,
'xb' => self::LIBERATION_SANS_BOLD,
'xi' => self::LIBERATION_SANS_ITALIC,
'xbi' => self::LIBERATION_SANS_BOLD_ITALIC,
],
'Lucida Console' => [
'x' => self::LUCIDA_CONSOLE,
'xb' => self::LUCIDA_CONSOLE,
'xi' => self::LUCIDA_CONSOLE,
'xbi' => self::LUCIDA_CONSOLE,
],
'Lucida Sans Unicode' => [
'x' => self::LUCIDA_SANS_UNICODE,
'xb' => self::LUCIDA_SANS_UNICODE,
'xi' => self::LUCIDA_SANS_UNICODE,
'xbi' => self::LUCIDA_SANS_UNICODE,
],
'Microsoft Sans Serif' => [
'x' => self::MICROSOFT_SANS_SERIF,
'xb' => self::MICROSOFT_SANS_SERIF,
'xi' => self::MICROSOFT_SANS_SERIF,
'xbi' => self::MICROSOFT_SANS_SERIF,
],
'Palatino Linotype' => [
'x' => self::PALATINO_LINOTYPE,
'xb' => self::PALATINO_LINOTYPE_BOLD,
'xi' => self::PALATINO_LINOTYPE_ITALIC,
'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC,
],
'Symbol' => [
'x' => self::SYMBOL,
'xb' => self::SYMBOL,
'xi' => self::SYMBOL,
'xbi' => self::SYMBOL,
],
'Tahoma' => [
'x' => self::TAHOMA,
'xb' => self::TAHOMA_BOLD,
'xi' => self::TAHOMA,
'xbi' => self::TAHOMA_BOLD,
],
'Times New Roman' => [
'x' => self::TIMES_NEW_ROMAN,
'xb' => self::TIMES_NEW_ROMAN_BOLD,
'xi' => self::TIMES_NEW_ROMAN_ITALIC,
'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC,
],
'Trebuchet MS' => [
'x' => self::TREBUCHET_MS,
'xb' => self::TREBUCHET_MS_BOLD,
'xi' => self::TREBUCHET_MS_ITALIC,
'xbi' => self::TREBUCHET_MS_BOLD_ITALIC,
],
'Verdana' => [
'x' => self::VERDANA,
'xb' => self::VERDANA_BOLD,
'xi' => self::VERDANA_ITALIC,
'xbi' => self::VERDANA_BOLD_ITALIC,
],
];
/**
* Array that can be used to supplement FONT_FILE_NAMES for calculating exact width.
*
* @var array
*/
private static $extraFontArray = [];
public static function setExtraFontArray(array $extraFontArray): void
{
self::$extraFontArray = $extraFontArray;
}
public static function getExtraFontArray(): array
{
return self::$extraFontArray;
}
/**
* AutoSize method.
*
* @var string
*/
private static $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX;
/**
* Path to folder containing TrueType font .ttf files.
*
* @var string
*/
private static $trueTypeFontPath = '';
/**
* How wide is a default column for a given default font and size?
* Empirical data found by inspecting real Excel files and reading off the pixel width
* in Microsoft Office Excel 2007.
* Added height in points.
*/
public const DEFAULT_COLUMN_WIDTHS = [
'Arial' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
],
'Calibri' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0],
10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0],
],
'Verdana' => [
1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5],
9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25],
10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75],
],
];
/**
* List of column widths. Replaced by constant;
* previously it was public and updateable, allowing
* user to make inappropriate alterations.
*
* @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead.
*
* @var array
*/
public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS;
/**
* Set autoSize method.
*
* @param string $method see self::AUTOSIZE_METHOD_*
*
* @return bool Success or failure
*/
public static function setAutoSizeMethod($method)
{
if (!in_array($method, self::AUTOSIZE_METHODS)) {
return false;
}
self::$autoSizeMethod = $method;
return true;
}
/**
* Get autoSize method.
*
* @return string
*/
public static function getAutoSizeMethod()
{
return self::$autoSizeMethod;
}
/**
* Set the path to the folder containing .ttf files. There should be a trailing slash.
* Typical locations on variout some platforms:
* <ul>
* <li>C:/Windows/Fonts/</li>
* <li>/usr/share/fonts/truetype/</li>
* <li>~/.fonts/</li>
* </ul>.
*
* @param string $folderPath
*/
public static function setTrueTypeFontPath($folderPath): void
{
self::$trueTypeFontPath = $folderPath;
}
/**
* Get the path to the folder containing .ttf files.
*
* @return string
*/
public static function getTrueTypeFontPath()
{
return self::$trueTypeFontPath;
}
/**
* Calculate an (approximate) OpenXML column width, based on font size and text contained.
*
* @param FontStyle $font Font object
* @param null|RichText|string $cellText Text to calculate width
* @param int $rotation Rotation angle
* @param null|FontStyle $defaultFont Font object
* @param bool $filterAdjustment Add space for Autofilter or Table dropdown
*/
public static function calculateColumnWidth(
FontStyle $font,
$cellText = '',
$rotation = 0,
?FontStyle $defaultFont = null,
bool $filterAdjustment = false,
int $indentAdjustment = 0
): float {
// If it is rich text, use plain text
if ($cellText instanceof RichText) {
$cellText = $cellText->getPlainText();
}
// Special case if there are one or more newline characters ("\n")
$cellText = (string) $cellText;
if (strpos($cellText, "\n") !== false) {
$lineTexts = explode("\n", $cellText);
$lineWidths = [];
foreach ($lineTexts as $lineText) {
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
}
return max($lineWidths); // width of longest line in cell
}
// Try to get the exact text width in pixels
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
$columnWidth = 0;
if (!$approximate) {
try {
$columnWidthAdjust = ceil(
self::getTextWidthPixelsExact(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
) * 1.07
);
// Width of text in pixels excl. padding
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
} catch (PhpSpreadsheetException $e) {
$approximate = true;
}
}
if ($approximate) {
$columnWidthAdjust = self::getTextWidthPixelsApprox(
str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
$font,
0
);
// Width of text in pixels excl. padding, approximation
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
}
// Convert from pixel width to column width
$columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
// Return
return round($columnWidth, 4);
}
/**
* Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
*/
public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float
{
// font size should really be supplied in pixels in GD2,
// but since GD2 seems to assume 72dpi, pixels and points are the same
$fontFile = self::getTrueTypeFontFileFromFont($font);
$textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
if ($textBox === false) {
// @codeCoverageIgnoreStart
throw new PhpSpreadsheetException('imagettfbbox failed');
// @codeCoverageIgnoreEnd
}
// Get corners positions
$lowerLeftCornerX = $textBox[0];
$lowerRightCornerX = $textBox[2];
$upperRightCornerX = $textBox[4];
$upperLeftCornerX = $textBox[6];
// Consider the rotation when calculating the width
return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
}
/**
* Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
*
* @param string $columnText
* @param int $rotation
*
* @return int Text width in pixels (no padding added)
*/
public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
{
$fontName = $font->getName();
$fontSize = $font->getSize();
// Calculate column width in pixels.
// We assume fixed glyph width, but count double for "fullwidth" characters.
// Result varies with font name and size.
switch ($fontName) {
case 'Arial':
// value 8 was set because of experience in different exports at Arial 10 font.
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
case 'Verdana':
// value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
$columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
default:
// just assume Calibri
// value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
$columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
break;
}
// Calculate approximate rotated column width
if ($rotation !== 0) {
if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
// stacked text
$columnWidth = 4; // approximation
} else {
// rotated text
$columnWidth = $columnWidth * cos(deg2rad($rotation))
+ $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
}
}
// pixel width is an integer
return (int) $columnWidth;
}
/**
* Calculate an (approximate) pixel size, based on a font points size.
*
* @param int $fontSizeInPoints Font size (in points)
*
* @return int Font size (in pixels)
*/
public static function fontSizeToPixels($fontSizeInPoints)
{
return (int) ((4 / 3) * $fontSizeInPoints);
}
/**
* Calculate an (approximate) pixel size, based on inch size.
*
* @param int $sizeInInch Font size (in inch)
*
* @return int Size (in pixels)
*/
public static function inchSizeToPixels($sizeInInch)
{
return $sizeInInch * 96;
}
/**
* Calculate an (approximate) pixel size, based on centimeter size.
*
* @param int $sizeInCm Font size (in centimeters)
*
* @return float Size (in pixels)
*/
public static function centimeterSizeToPixels($sizeInCm)
{
return $sizeInCm * 37.795275591;
}
/**
* Returns the font path given the font.
*
* @return string Path to TrueType font file
*/
public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true)
{
if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
}
$name = $font->getName();
$fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
if (!isset($fontArray[$name])) {
throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
}
$bold = $font->getBold();
$italic = $font->getItalic();
$index = 'x';
if ($bold) {
$index .= 'b';
}
if ($italic) {
$index .= 'i';
}
$fontFile = $fontArray[$name][$index];
$separator = '';
if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
$separator = DIRECTORY_SEPARATOR;
}
$fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
if (!$fontFileAbsolute) {
$fontFile = self::$trueTypeFontPath . $separator . $fontFile;
}
// Check if file actually exists
if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
$alternateName = $name;
if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
// Bold but no italic:
// Comic Sans
// Tahoma
// Neither bold nor italic:
// Impact
// Lucida Console
// Lucida Sans Unicode
// Microsoft Sans Serif
// Symbol
if ($index === 'xb') {
$alternateName .= ' Bold';
} elseif ($index === 'xi') {
$alternateName .= ' Italic';
} elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
$alternateName .= ' Bold';
} else {
$alternateName .= ' Bold Italic';
}
}
$fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
if (!file_exists($fontFile)) {
throw new PhpSpreadsheetException('TrueType Font file not found');
}
}
return $fontFile;
}
public const CHARSET_FROM_FONT_NAME = [
'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
'Wingdings' => self::CHARSET_SYMBOL,
'Wingdings 2' => self::CHARSET_SYMBOL,
'Wingdings 3' => self::CHARSET_SYMBOL,
];
/**
* Returns the associated charset for the font name.
*
* @param string $fontName Font name
*
* @return int Character set code
*/
public static function getCharsetFromFontName($fontName)
{
return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
}
/**
* Get the effective column width for columns without a column dimension or column with width -1
* For example, for Calibri 11 this is 9.140625 (64 px).
*
* @param FontStyle $font The workbooks default font
* @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
*
* @return mixed Column width
*/
public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
{
if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
// Exact width can be determined
$columnWidth = $returnAsPixels ?
self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
: self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$columnWidth = $returnAsPixels ?
self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
: self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
$columnWidth = $columnWidth * $font->getSize() / 11;
// Round pixels to closest integer
if ($returnAsPixels) {
$columnWidth = (int) round($columnWidth);
}
}
return $columnWidth;
}
/**
* Get the effective row height for rows without a row dimension or rows with height -1
* For example, for Calibri 11 this is 15 points.
*
* @param FontStyle $font The workbooks default font
*
* @return float Row height in points
*/
public static function getDefaultRowHeightByFont(FontStyle $font)
{
$name = $font->getName();
$size = $font->getSize();
if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
} elseif ($name === 'Arial' || $name === 'Verdana') {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
} else {
$rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
}
return $rowHeight;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php 0000644 00000000673 15243417764 0017254 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
class IntOrFloat
{
/**
* Help some functions with large results operate correctly on 32-bit,
* by returning result as int when possible, float otherwise.
*
* @param float|int $value
*
* @return float|int
*/
public static function evaluate($value)
{
$iValue = (int) $value;
return ($value == $iValue) ? $iValue : $value;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/File.php 0000644 00000014003 15243417764 0016102 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use ZipArchive;
class File
{
/**
* Use Temp or File Upload Temp for temporary files.
*
* @var bool
*/
protected static $useUploadTempDirectory = false;
/**
* Set the flag indicating whether the File Upload Temp directory should be used for temporary files.
*/
public static function setUseUploadTempDirectory(bool $useUploadTempDir): void
{
self::$useUploadTempDirectory = (bool) $useUploadTempDir;
}
/**
* Get the flag indicating whether the File Upload Temp directory should be used for temporary files.
*/
public static function getUseUploadTempDirectory(): bool
{
return self::$useUploadTempDirectory;
}
// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
// Section 4.3.7
// Looks like there might be endian-ness considerations
private const ZIP_FIRST_4 = [
"\x50\x4b\x03\x04", // what it looks like on my system
"\x04\x03\x4b\x50", // what it says in documentation
];
private static function validateZipFirst4(string $zipFile): bool
{
$contents = @file_get_contents($zipFile, false, null, 0, 4);
return in_array($contents, self::ZIP_FIRST_4, true);
}
/**
* Verify if a file exists.
*/
public static function fileExists(string $filename): bool
{
// Sick construction, but it seems that
// file_exists returns strange values when
// doing the original file_exists on ZIP archives...
if (strtolower(substr($filename, 0, 6)) == 'zip://') {
// Open ZIP file and verify if the file exists
$zipFile = substr($filename, 6, strrpos($filename, '#') - 6);
$archiveFile = substr($filename, strrpos($filename, '#') + 1);
if (self::validateZipFirst4($zipFile)) {
$zip = new ZipArchive();
$res = $zip->open($zipFile);
if ($res === true) {
$returnValue = ($zip->getFromName($archiveFile) !== false);
$zip->close();
return $returnValue;
}
}
return false;
}
return file_exists($filename);
}
/**
* Returns canonicalized absolute pathname, also for ZIP archives.
*/
public static function realpath(string $filename): string
{
// Returnvalue
$returnValue = '';
// Try using realpath()
if (file_exists($filename)) {
$returnValue = realpath($filename) ?: '';
}
// Found something?
if ($returnValue === '') {
$pathArray = explode('/', $filename);
while (in_array('..', $pathArray) && $pathArray[0] != '..') {
$iMax = count($pathArray);
for ($i = 0; $i < $iMax; ++$i) {
if ($pathArray[$i] == '..' && $i > 0) {
unset($pathArray[$i], $pathArray[$i - 1]);
break;
}
}
}
$returnValue = implode('/', $pathArray);
}
// Return
return $returnValue;
}
/**
* Get the systems temporary directory.
*/
public static function sysGetTempDir(): string
{
$path = sys_get_temp_dir();
if (self::$useUploadTempDirectory) {
// use upload-directory when defined to allow running on environments having very restricted
// open_basedir configs
if (ini_get('upload_tmp_dir') !== false) {
if ($temp = ini_get('upload_tmp_dir')) {
if (file_exists($temp)) {
$path = $temp;
}
}
}
}
return realpath($path) ?: '';
}
public static function temporaryFilename(): string
{
$filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet');
if ($filename === false) {
throw new Exception('Could not create temporary file');
}
return $filename;
}
/**
* Assert that given path is an existing file and is readable, otherwise throw exception.
*/
public static function assertFile(string $filename, string $zipMember = ''): void
{
if (!is_file($filename)) {
throw new ReaderException('File "' . $filename . '" does not exist.');
}
if (!is_readable($filename)) {
throw new ReaderException('Could not open "' . $filename . '" for reading.');
}
if ($zipMember !== '') {
$zipfile = "zip://$filename#$zipMember";
if (!self::fileExists($zipfile)) {
// Has the file been saved with Windoze directory separators rather than unix?
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
if (!self::fileExists($zipfile)) {
throw new ReaderException("Could not find zip member $zipfile");
}
}
}
}
/**
* Same as assertFile, except return true/false and don't throw Exception.
*/
public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
{
if (!is_file($filename)) {
return false;
}
if (!is_readable($filename)) {
return false;
}
if ($zipMember === null) {
return true;
}
// validate zip, but don't check specific member
if ($zipMember === '') {
return self::validateZipFirst4($filename);
}
$zipfile = "zip://$filename#$zipMember";
if (self::fileExists($zipfile)) {
return true;
}
// Has the file been saved with Windoze directory separators rather than unix?
$zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
return self::fileExists($zipfile);
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php 0000644 00000005053 15243417764 0017065 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
class XMLWriter extends \XMLWriter
{
/** @var bool */
public static $debugEnabled = false;
/** Temporary storage method */
const STORAGE_MEMORY = 1;
const STORAGE_DISK = 2;
/**
* Temporary filename.
*
* @var string
*/
private $tempFileName = '';
/**
* Create a new XMLWriter instance.
*
* @param int $temporaryStorage Temporary storage location
* @param string $temporaryStorageFolder Temporary storage folder
*/
public function __construct($temporaryStorage = self::STORAGE_MEMORY, $temporaryStorageFolder = null)
{
// Open temporary storage
if ($temporaryStorage == self::STORAGE_MEMORY) {
$this->openMemory();
} else {
// Create temporary filename
if ($temporaryStorageFolder === null) {
$temporaryStorageFolder = File::sysGetTempDir();
}
$this->tempFileName = (string) @tempnam($temporaryStorageFolder, 'xml');
// Open storage
if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) {
// Fallback to memory...
$this->openMemory();
}
}
// Set default values
if (self::$debugEnabled) {
$this->setIndent(true);
}
}
/**
* Destructor.
*/
public function __destruct()
{
// Unlink temporary files
// There is nothing reasonable to do if unlink fails.
if ($this->tempFileName != '') {
/** @scrutinizer ignore-unhandled */
@unlink($this->tempFileName);
}
}
public function __wakeup(): void
{
$this->tempFileName = '';
throw new SpreadsheetException('Unserialize not permitted');
}
/**
* Get written data.
*
* @return string
*/
public function getData()
{
if ($this->tempFileName == '') {
return $this->outputMemory(true);
}
$this->flush();
return file_get_contents($this->tempFileName) ?: '';
}
/**
* Wrapper method for writeRaw.
*
* @param null|string|string[] $rawTextData
*
* @return bool
*/
public function writeRawData($rawTextData)
{
if (is_array($rawTextData)) {
$rawTextData = implode("\n", $rawTextData);
}
return $this->writeRaw(htmlspecialchars($rawTextData ?? ''));
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php 0000644 00000011572 15243417764 0016626 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use GdImage;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use SimpleXMLElement;
class Drawing
{
/**
* Convert pixels to EMU.
*
* @param int $pixelValue Value in pixels
*
* @return int Value in EMU
*/
public static function pixelsToEMU($pixelValue)
{
return $pixelValue * 9525;
}
/**
* Convert EMU to pixels.
*
* @param int|SimpleXMLElement $emuValue Value in EMU
*
* @return int Value in pixels
*/
public static function EMUToPixels($emuValue)
{
$emuValue = (int) $emuValue;
if ($emuValue != 0) {
return (int) round($emuValue / 9525);
}
return 0;
}
/**
* Convert pixels to column width. Exact algorithm not known.
* By inspection of a real Excel file using Calibri 11, one finds 1000px ~ 142.85546875
* This gives a conversion factor of 7. Also, we assume that pixels and font size are proportional.
*
* @param int $pixelValue Value in pixels
*
* @return float|int Value in cell dimension
*/
public static function pixelsToCellDimension($pixelValue, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont)
{
// Font name and size
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::$defaultColumnWidths[$name][$size])) {
// Exact width can be determined
return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width']
/ Font::$defaultColumnWidths[$name][$size]['px'];
}
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width']
/ Font::$defaultColumnWidths['Calibri'][11]['px'] / $size;
}
/**
* Convert column width from (intrinsic) Excel units to pixels.
*
* @param float $cellWidth Value in cell dimension
* @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook
*
* @return int Value in pixels
*/
public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont)
{
// Font name and size
$name = $defaultFont->getName();
$size = $defaultFont->getSize();
if (isset(Font::$defaultColumnWidths[$name][$size])) {
// Exact width can be determined
$colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px']
/ Font::$defaultColumnWidths[$name][$size]['width'];
} else {
// We don't have data for this particular font and size, use approximation by
// extrapolating from Calibri 11
$colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px']
/ Font::$defaultColumnWidths['Calibri'][11]['width'] / 11;
}
// Round pixels to closest integer
$colWidth = (int) round($colWidth);
return $colWidth;
}
/**
* Convert pixels to points.
*
* @param int $pixelValue Value in pixels
*
* @return float Value in points
*/
public static function pixelsToPoints($pixelValue)
{
return $pixelValue * 0.75;
}
/**
* Convert points to pixels.
*
* @param int $pointValue Value in points
*
* @return int Value in pixels
*/
public static function pointsToPixels($pointValue)
{
if ($pointValue != 0) {
return (int) ceil($pointValue / 0.75);
}
return 0;
}
/**
* Convert degrees to angle.
*
* @param int $degrees Degrees
*
* @return int Angle
*/
public static function degreesToAngle($degrees)
{
return (int) round($degrees * 60000);
}
/**
* Convert angle to degrees.
*
* @param int|SimpleXMLElement $angle Angle
*
* @return int Degrees
*/
public static function angleToDegrees($angle)
{
$angle = (int) $angle;
if ($angle != 0) {
return (int) round($angle / 60000);
}
return 0;
}
/**
* Create a new image from file. By alexander at alexauto dot nl.
*
* @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
*
* @param string $bmpFilename Path to Windows DIB (BMP) image
*
* @return GdImage|resource
*
* @deprecated 1.26 use Php function imagecreatefrombmp instead
*
* @codeCoverageIgnore
*/
public static function imagecreatefrombmp($bmpFilename)
{
$retVal = @imagecreatefrombmp($bmpFilename);
if ($retVal === false) {
throw new ReaderException("Unable to create image from $bmpFilename");
}
return $retVal;
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php 0000644 00000005572 15243417764 0020667 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class PowerBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*
* @var string
*/
protected $bestFitType = 'power';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
return $this->getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope();
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
public function getEquation($dp = 0)
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' * X^' . $slope;
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getIntersect($dp = 0)
{
if ($dp != 0) {
return round(exp($this->intersect), $dp);
}
return exp($this->intersect);
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function powerRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$adjustedXValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$xValues
);
$this->leastSquareFit($adjustedYValues, $adjustedXValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($yValues, $xValues = [], $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->powerRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php 0000644 00000006030 15243417764 0022047 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class ExponentialBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*
* @var string
*/
protected $bestFitType = 'exponential';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
return $this->getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset);
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
public function getEquation($dp = 0)
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' * ' . $slope . '^X';
}
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getSlope($dp = 0)
{
if ($dp != 0) {
return round(exp($this->slope), $dp);
}
return exp($this->slope);
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getIntersect($dp = 0)
{
if ($dp != 0) {
return round(exp($this->intersect), $dp);
}
return exp($this->intersect);
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function exponentialRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($yValues, $xValues = [], $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->exponentialRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php 0000644 00000011624 15243417764 0017361 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class Trend
{
const TREND_LINEAR = 'Linear';
const TREND_LOGARITHMIC = 'Logarithmic';
const TREND_EXPONENTIAL = 'Exponential';
const TREND_POWER = 'Power';
const TREND_POLYNOMIAL_2 = 'Polynomial_2';
const TREND_POLYNOMIAL_3 = 'Polynomial_3';
const TREND_POLYNOMIAL_4 = 'Polynomial_4';
const TREND_POLYNOMIAL_5 = 'Polynomial_5';
const TREND_POLYNOMIAL_6 = 'Polynomial_6';
const TREND_BEST_FIT = 'Bestfit';
const TREND_BEST_FIT_NO_POLY = 'Bestfit_no_Polynomials';
/**
* Names of the best-fit Trend analysis methods.
*
* @var string[]
*/
private static $trendTypes = [
self::TREND_LINEAR,
self::TREND_LOGARITHMIC,
self::TREND_EXPONENTIAL,
self::TREND_POWER,
];
/**
* Names of the best-fit Trend polynomial orders.
*
* @var string[]
*/
private static $trendTypePolynomialOrders = [
self::TREND_POLYNOMIAL_2,
self::TREND_POLYNOMIAL_3,
self::TREND_POLYNOMIAL_4,
self::TREND_POLYNOMIAL_5,
self::TREND_POLYNOMIAL_6,
];
/**
* Cached results for each method when trying to identify which provides the best fit.
*
* @var BestFit[]
*/
private static $trendCache = [];
/**
* @param string $trendType
* @param array $yValues
* @param array $xValues
* @param bool $const
*
* @return mixed
*/
public static function calculate($trendType = self::TREND_BEST_FIT, $yValues = [], $xValues = [], $const = true)
{
// Calculate number of points in each dataset
$nY = count($yValues);
$nX = count($xValues);
// Define X Values if necessary
if ($nX === 0) {
$xValues = range(1, $nY);
} elseif ($nY !== $nX) {
// Ensure both arrays of points are the same size
trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR);
}
$key = md5($trendType . $const . serialize($yValues) . serialize($xValues));
// Determine which Trend method has been requested
switch ($trendType) {
// Instantiate and return the class for the requested Trend method
case self::TREND_LINEAR:
case self::TREND_LOGARITHMIC:
case self::TREND_EXPONENTIAL:
case self::TREND_POWER:
if (!isset(self::$trendCache[$key])) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
self::$trendCache[$key] = new $className($yValues, $xValues, $const);
}
return self::$trendCache[$key];
case self::TREND_POLYNOMIAL_2:
case self::TREND_POLYNOMIAL_3:
case self::TREND_POLYNOMIAL_4:
case self::TREND_POLYNOMIAL_5:
case self::TREND_POLYNOMIAL_6:
if (!isset(self::$trendCache[$key])) {
$order = (int) substr($trendType, -1);
self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues);
}
return self::$trendCache[$key];
case self::TREND_BEST_FIT:
case self::TREND_BEST_FIT_NO_POLY:
// If the request is to determine the best fit regression, then we test each Trend line in turn
// Start by generating an instance of each available Trend method
$bestFit = [];
$bestFitValue = [];
foreach (self::$trendTypes as $trendMethod) {
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
//* @phpstan-ignore-next-line
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
}
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
foreach (self::$trendTypePolynomialOrders as $trendMethod) {
$order = (int) substr($trendMethod, -1);
$bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues);
if ($bestFit[$trendMethod]->getError()) {
unset($bestFit[$trendMethod]);
} else {
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
}
}
}
// Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class
arsort($bestFitValue);
$bestFitType = key($bestFitValue);
return $bestFit[$bestFitType];
default:
return false;
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php 0000644 00000004134 15243417764 0020776 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class LinearBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*
* @var string
*/
protected $bestFitType = 'linear';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
return $this->getIntersect() + $this->getSlope() * $xValue;
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return ($yValue - $this->getIntersect()) / $this->getSlope();
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
public function getEquation($dp = 0)
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $intersect . ' + ' . $slope . ' * X';
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function linearRegression(array $yValues, array $xValues, bool $const): void
{
$this->leastSquareFit($yValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($yValues, $xValues = [], $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->linearRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php 0000644 00000014555 15243417764 0021717 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
use Matrix\Matrix;
// Phpstan and Scrutinizer seem to have legitimate complaints.
// $this->slope is specified where an array is expected in several places.
// But it seems that it should always be float.
// This code is probably not exercised at all in unit tests.
class PolynomialBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*
* @var string
*/
protected $bestFitType = 'polynomial';
/**
* Polynomial order.
*
* @var int
*/
protected $order = 0;
/**
* Return the order of this polynomial.
*
* @return int
*/
public function getOrder()
{
return $this->order;
}
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
$retVal = $this->getIntersect();
$slope = $this->getSlope();
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$retVal += $value * $xValue ** ($key + 1);
}
}
return $retVal;
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return ($yValue - $this->getIntersect()) / $this->getSlope();
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
public function getEquation($dp = 0)
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
$equation = 'Y = ' . $intersect;
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
foreach ($slope as $key => $value) {
if ($value != 0.0) {
$equation .= ' + ' . $value . ' * X';
if ($key > 0) {
$equation .= '^' . ($key + 1);
}
}
}
return $equation;
}
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getSlope($dp = 0)
{
if ($dp != 0) {
$coefficients = [];
// Scrutinizer is correct - $this->slope is float, not array.
//* @phpstan-ignore-next-line
foreach ($this->slope as $coefficient) {
$coefficients[] = round($coefficient, $dp);
}
// @phpstan-ignore-next-line
return $coefficients;
}
return $this->slope;
}
/**
* @param int $dp
*
* @return array
*/
public function getCoefficients($dp = 0)
{
// Phpstan and Scrutinizer are both correct - getSlope returns float, not array.
// @phpstan-ignore-next-line
return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param int $order Order of Polynomial for this regression
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function polynomialRegression($order, $yValues, $xValues): void
{
// calculate sums
$x_sum = array_sum($xValues);
$y_sum = array_sum($yValues);
$xx_sum = $xy_sum = $yy_sum = 0;
for ($i = 0; $i < $this->valueCount; ++$i) {
$xy_sum += $xValues[$i] * $yValues[$i];
$xx_sum += $xValues[$i] * $xValues[$i];
$yy_sum += $yValues[$i] * $yValues[$i];
}
/*
* This routine uses logic from the PHP port of polyfit version 0.1
* written by Michael Bommarito and Paul Meagher
*
* The function fits a polynomial function of order $order through
* a series of x-y data points using least squares.
*
*/
$A = [];
$B = [];
for ($i = 0; $i < $this->valueCount; ++$i) {
for ($j = 0; $j <= $order; ++$j) {
$A[$i][$j] = $xValues[$i] ** $j;
}
}
for ($i = 0; $i < $this->valueCount; ++$i) {
$B[$i] = [$yValues[$i]];
}
$matrixA = new Matrix($A);
$matrixB = new Matrix($B);
$C = $matrixA->solve($matrixB);
$coefficients = [];
for ($i = 0; $i < $C->rows; ++$i) {
$r = $C->getValue($i + 1, 1); // row and column are origin-1
if (abs($r) <= 10 ** (-9)) {
$r = 0;
}
$coefficients[] = $r;
}
$this->intersect = array_shift($coefficients);
// Phpstan (and maybe Scrutinizer) are correct
//* @phpstan-ignore-next-line
$this->slope = $coefficients;
$this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
foreach ($this->xValues as $xKey => $xValue) {
$this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
}
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param int $order Order of Polynomial for this regression
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct($order, $yValues, $xValues = [])
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
if ($order < $this->valueCount) {
$this->bestFitType .= '_' . $order;
$this->order = $order;
$this->polynomialRegression($order, $yValues, $xValues);
if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
$this->error = true;
}
} else {
$this->error = true;
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php 0000644 00000004532 15243417764 0022030 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
class LogarithmicBestFit extends BestFit
{
/**
* Algorithm type to use for best-fit
* (Name of this Trend class).
*
* @var string
*/
protected $bestFitType = 'logarithmic';
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
public function getValueOfYForX($xValue)
{
return $this->getIntersect() + $this->getSlope() * log($xValue - $this->xOffset);
}
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
public function getValueOfXForY($yValue)
{
return exp(($yValue - $this->getIntersect()) / $this->getSlope());
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
public function getEquation($dp = 0)
{
$slope = $this->getSlope($dp);
$intersect = $this->getIntersect($dp);
return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)';
}
/**
* Execute the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
private function logarithmicRegression(array $yValues, array $xValues, bool $const): void
{
$adjustedYValues = array_map(
function ($value) {
return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
},
$yValues
);
$this->leastSquareFit($adjustedYValues, $xValues, $const);
}
/**
* Define the regression and calculate the goodness of fit for a set of X and Y data values.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
* @param bool $const
*/
public function __construct($yValues, $xValues = [], $const = true)
{
parent::__construct($yValues, $xValues);
if (!$this->error) {
$this->logarithmicRegression($yValues, $xValues, (bool) $const);
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php 0000644 00000030306 15243417764 0017643 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Shared\Trend;
abstract class BestFit
{
/**
* Indicator flag for a calculation error.
*
* @var bool
*/
protected $error = false;
/**
* Algorithm type to use for best-fit.
*
* @var string
*/
protected $bestFitType = 'undetermined';
/**
* Number of entries in the sets of x- and y-value arrays.
*
* @var int
*/
protected $valueCount = 0;
/**
* X-value dataseries of values.
*
* @var float[]
*/
protected $xValues = [];
/**
* Y-value dataseries of values.
*
* @var float[]
*/
protected $yValues = [];
/**
* Flag indicating whether values should be adjusted to Y=0.
*
* @var bool
*/
protected $adjustToZero = false;
/**
* Y-value series of best-fit values.
*
* @var float[]
*/
protected $yBestFitValues = [];
/** @var float */
protected $goodnessOfFit = 1;
/** @var float */
protected $stdevOfResiduals = 0;
/** @var float */
protected $covariance = 0;
/** @var float */
protected $correlation = 0;
/** @var float */
protected $SSRegression = 0;
/** @var float */
protected $SSResiduals = 0;
/** @var float */
protected $DFResiduals = 0;
/** @var float */
protected $f = 0;
/** @var float */
protected $slope = 0;
/** @var float */
protected $slopeSE = 0;
/** @var float */
protected $intersect = 0;
/** @var float */
protected $intersectSE = 0;
/** @var float */
protected $xOffset = 0;
/** @var float */
protected $yOffset = 0;
/** @return bool */
public function getError()
{
return $this->error;
}
/** @return string */
public function getBestFitType()
{
return $this->bestFitType;
}
/**
* Return the Y-Value for a specified value of X.
*
* @param float $xValue X-Value
*
* @return float Y-Value
*/
abstract public function getValueOfYForX($xValue);
/**
* Return the X-Value for a specified value of Y.
*
* @param float $yValue Y-Value
*
* @return float X-Value
*/
abstract public function getValueOfXForY($yValue);
/**
* Return the original set of X-Values.
*
* @return float[] X-Values
*/
public function getXValues()
{
return $this->xValues;
}
/**
* Return the Equation of the best-fit line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return string
*/
abstract public function getEquation($dp = 0);
/**
* Return the Slope of the line.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getSlope($dp = 0)
{
if ($dp != 0) {
return round($this->slope, $dp);
}
return $this->slope;
}
/**
* Return the standard error of the Slope.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getSlopeSE($dp = 0)
{
if ($dp != 0) {
return round($this->slopeSE, $dp);
}
return $this->slopeSE;
}
/**
* Return the Value of X where it intersects Y = 0.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getIntersect($dp = 0)
{
if ($dp != 0) {
return round($this->intersect, $dp);
}
return $this->intersect;
}
/**
* Return the standard error of the Intersect.
*
* @param int $dp Number of places of decimal precision to display
*
* @return float
*/
public function getIntersectSE($dp = 0)
{
if ($dp != 0) {
return round($this->intersectSE, $dp);
}
return $this->intersectSE;
}
/**
* Return the goodness of fit for this regression.
*
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getGoodnessOfFit($dp = 0)
{
if ($dp != 0) {
return round($this->goodnessOfFit, $dp);
}
return $this->goodnessOfFit;
}
/**
* Return the goodness of fit for this regression.
*
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getGoodnessOfFitPercent($dp = 0)
{
if ($dp != 0) {
return round($this->goodnessOfFit * 100, $dp);
}
return $this->goodnessOfFit * 100;
}
/**
* Return the standard deviation of the residuals for this regression.
*
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getStdevOfResiduals($dp = 0)
{
if ($dp != 0) {
return round($this->stdevOfResiduals, $dp);
}
return $this->stdevOfResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getSSRegression($dp = 0)
{
if ($dp != 0) {
return round($this->SSRegression, $dp);
}
return $this->SSRegression;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getSSResiduals($dp = 0)
{
if ($dp != 0) {
return round($this->SSResiduals, $dp);
}
return $this->SSResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getDFResiduals($dp = 0)
{
if ($dp != 0) {
return round($this->DFResiduals, $dp);
}
return $this->DFResiduals;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getF($dp = 0)
{
if ($dp != 0) {
return round($this->f, $dp);
}
return $this->f;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getCovariance($dp = 0)
{
if ($dp != 0) {
return round($this->covariance, $dp);
}
return $this->covariance;
}
/**
* @param int $dp Number of places of decimal precision to return
*
* @return float
*/
public function getCorrelation($dp = 0)
{
if ($dp != 0) {
return round($this->correlation, $dp);
}
return $this->correlation;
}
/**
* @return float[]
*/
public function getYBestFitValues()
{
return $this->yBestFitValues;
}
/** @var mixed */
private static $scrutinizerZeroPointZero = 0.0;
/**
* @param mixed $x
* @param mixed $y
*/
private static function scrutinizerLooseCompare($x, $y): bool
{
return $x == $y;
}
/**
* @param float $sumX
* @param float $sumY
* @param float $sumX2
* @param float $sumY2
* @param float $sumXY
* @param float $meanX
* @param float $meanY
* @param bool|int $const
*/
protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
{
$SSres = $SScov = $SStot = $SSsex = 0.0;
foreach ($this->xValues as $xKey => $xValue) {
$bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
$SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
if ($const === true) {
$SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
} else {
$SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
}
$SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
if ($const === true) {
$SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
} else {
$SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
}
}
$this->SSResiduals = $SSres;
$this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0);
if ($this->DFResiduals == 0.0) {
$this->stdevOfResiduals = 0.0;
} else {
$this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
}
// Scrutinizer thinks $SSres == $SStot is always true. It is wrong.
if ($SStot == self::$scrutinizerZeroPointZero || self::scrutinizerLooseCompare($SSres, $SStot)) {
$this->goodnessOfFit = 1;
} else {
$this->goodnessOfFit = 1 - ($SSres / $SStot);
}
$this->SSRegression = $this->goodnessOfFit * $SStot;
$this->covariance = $SScov / $this->valueCount;
$this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
$this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
$this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
if ($this->SSResiduals != 0.0) {
if ($this->DFResiduals == 0.0) {
$this->f = 0.0;
} else {
$this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
}
} else {
if ($this->DFResiduals == 0.0) {
$this->f = 0.0;
} else {
$this->f = $this->SSRegression / $this->DFResiduals;
}
}
}
/** @return float|int */
private function sumSquares(array $values)
{
return array_sum(
array_map(
function ($value) {
return $value ** 2;
},
$values
)
);
}
/**
* @param float[] $yValues
* @param float[] $xValues
*/
protected function leastSquareFit(array $yValues, array $xValues, bool $const): void
{
// calculate sums
$sumValuesX = array_sum($xValues);
$sumValuesY = array_sum($yValues);
$meanValueX = $sumValuesX / $this->valueCount;
$meanValueY = $sumValuesY / $this->valueCount;
$sumSquaresX = $this->sumSquares($xValues);
$sumSquaresY = $this->sumSquares($yValues);
$mBase = $mDivisor = 0.0;
$xy_sum = 0.0;
for ($i = 0; $i < $this->valueCount; ++$i) {
$xy_sum += $xValues[$i] * $yValues[$i];
if ($const === true) {
$mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY);
$mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX);
} else {
$mBase += $xValues[$i] * $yValues[$i];
$mDivisor += $xValues[$i] * $xValues[$i];
}
}
// calculate slope
$this->slope = $mBase / $mDivisor;
// calculate intersect
$this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0;
$this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const);
}
/**
* Define the regression.
*
* @param float[] $yValues The set of Y-values for this regression
* @param float[] $xValues The set of X-values for this regression
*/
public function __construct($yValues, $xValues = [])
{
// Calculate number of points
$yValueCount = count($yValues);
$xValueCount = count($xValues);
// Define X Values if necessary
if ($xValueCount === 0) {
$xValues = range(1, $yValueCount);
} elseif ($yValueCount !== $xValueCount) {
// Ensure both arrays of points are the same size
$this->error = true;
}
$this->valueCount = $yValueCount;
$this->xValues = $xValues;
$this->yValues = $yValues;
}
}
phpspreadsheet/src/PhpSpreadsheet/IComparable.php 0000644 00000000266 15243417764 0016201 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
interface IComparable
{
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode();
}
phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php 0000644 00000154046 15243417764 0017067 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class ReferenceHelper
{
/** Constants */
/** Regular Expressions */
const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
/**
* Instance of this class.
*
* @var ?ReferenceHelper
*/
private static $instance;
/**
* @var CellReferenceHelper
*/
private $cellReferenceHelper;
/**
* Get an instance of this class.
*
* @return ReferenceHelper
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Create a new ReferenceHelper.
*/
protected function __construct()
{
}
/**
* Compare two column addresses
* Intended for use as a Callback function for sorting column addresses by column.
*
* @param string $a First column to test (e.g. 'AA')
* @param string $b Second column to test (e.g. 'Z')
*
* @return int
*/
public static function columnSort($a, $b)
{
return strcasecmp(strlen($a) . $a, strlen($b) . $b);
}
/**
* Compare two column addresses
* Intended for use as a Callback function for reverse sorting column addresses by column.
*
* @param string $a First column to test (e.g. 'AA')
* @param string $b Second column to test (e.g. 'Z')
*
* @return int
*/
public static function columnReverseSort(string $a, string $b)
{
return -strcasecmp(strlen($a) . $a, strlen($b) . $b);
}
/**
* Compare two cell addresses
* Intended for use as a Callback function for sorting cell addresses by column and row.
*
* @param string $a First cell to test (e.g. 'AA1')
* @param string $b Second cell to test (e.g. 'Z1')
*
* @return int
*/
public static function cellSort(string $a, string $b)
{
/** @scrutinizer be-damned */
sscanf($a, '%[A-Z]%d', $ac, $ar);
/** @var int $ar */
/** @var string $ac */
/** @scrutinizer be-damned */
sscanf($b, '%[A-Z]%d', $bc, $br);
/** @var int $br */
/** @var string $bc */
if ($ar === $br) {
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
}
return ($ar < $br) ? -1 : 1;
}
/**
* Compare two cell addresses
* Intended for use as a Callback function for sorting cell addresses by column and row.
*
* @param string $a First cell to test (e.g. 'AA1')
* @param string $b Second cell to test (e.g. 'Z1')
*
* @return int
*/
public static function cellReverseSort(string $a, string $b)
{
/** @scrutinizer be-damned */
sscanf($a, '%[A-Z]%d', $ac, $ar);
/** @var int $ar */
/** @var string $ac */
/** @scrutinizer be-damned */
sscanf($b, '%[A-Z]%d', $bc, $br);
/** @var int $br */
/** @var string $bc */
if ($ar === $br) {
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
}
return ($ar < $br) ? 1 : -1;
}
/**
* Update page breaks when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustPageBreaks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aBreaks = $worksheet->getBreaks();
($numberOfColumns > 0 || $numberOfRows > 0)
? uksort($aBreaks, [self::class, 'cellReverseSort'])
: uksort($aBreaks, [self::class, 'cellSort']);
foreach ($aBreaks as $cellAddress => $value) {
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
// If we're deleting, then clear any defined breaks that are within the range
// of rows/columns that we're deleting
$worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE);
} else {
// Otherwise update any affected breaks by inserting a new break at the appropriate point
// and removing the old affected break
$newReference = $this->updateCellReference($cellAddress);
if ($cellAddress !== $newReference) {
$worksheet->setBreak($newReference, $value)
->setBreak($cellAddress, Worksheet::BREAK_NONE);
}
}
}
}
/**
* Update cell comments when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
*/
protected function adjustComments(Worksheet $worksheet): void
{
$aComments = $worksheet->getComments();
$aNewComments = []; // the new array of all comments
foreach ($aComments as $cellAddress => &$value) {
// Any comments inside a deleted range will be ignored
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) {
// Otherwise build a new array of comments indexed by the adjusted cell reference
$newReference = $this->updateCellReference($cellAddress);
$aNewComments[$newReference] = $value;
}
}
// Replace the comments array with the new set of comments
$worksheet->setComments($aNewComments);
}
/**
* Update hyperlinks when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aHyperlinkCollection = $worksheet->getHyperlinkCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort'])
: uksort($aHyperlinkCollection, [self::class, 'cellSort']);
foreach ($aHyperlinkCollection as $cellAddress => $value) {
$newReference = $this->updateCellReference($cellAddress);
if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
$worksheet->setHyperlink($cellAddress, null);
} elseif ($cellAddress !== $newReference) {
$worksheet->setHyperlink($newReference, $value);
$worksheet->setHyperlink($cellAddress, null);
}
}
}
/**
* Update conditional formatting styles when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustConditionalFormatting(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aStyles = $worksheet->getConditionalStylesCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
? uksort($aStyles, [self::class, 'cellReverseSort'])
: uksort($aStyles, [self::class, 'cellSort']);
foreach ($aStyles as $cellAddress => $cfRules) {
$worksheet->removeConditionalStyles($cellAddress);
$newReference = $this->updateCellReference($cellAddress);
foreach ($cfRules as &$cfRule) {
/** @var Conditional $cfRule */
$conditions = $cfRule->getConditions();
foreach ($conditions as &$condition) {
if (is_string($condition)) {
$condition = $this->updateFormulaReferences(
$condition,
$this->cellReferenceHelper->beforeCellAddress(),
$numberOfColumns,
$numberOfRows,
$worksheet->getTitle(),
true
);
}
}
$cfRule->setConditions($conditions);
}
$worksheet->setConditionalStyles($newReference, $cfRules);
}
}
/**
* Update data validations when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aDataValidationCollection = $worksheet->getDataValidationCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
? uksort($aDataValidationCollection, [self::class, 'cellReverseSort'])
: uksort($aDataValidationCollection, [self::class, 'cellSort']);
foreach ($aDataValidationCollection as $cellAddress => $dataValidation) {
$newReference = $this->updateCellReference($cellAddress);
if ($cellAddress !== $newReference) {
$dataValidation->setSqref($newReference);
$worksheet->setDataValidation($newReference, $dataValidation);
$worksheet->setDataValidation($cellAddress, null);
}
}
}
/**
* Update merged cells when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
*/
protected function adjustMergeCells(Worksheet $worksheet): void
{
$aMergeCells = $worksheet->getMergeCells();
$aNewMergeCells = []; // the new array of all merge cells
foreach ($aMergeCells as $cellAddress => &$value) {
$newReference = $this->updateCellReference($cellAddress);
$aNewMergeCells[$newReference] = $newReference;
}
$worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array
}
/**
* Update protected cells when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aProtectedCells = $worksheet->getProtectedCells();
($numberOfColumns > 0 || $numberOfRows > 0)
? uksort($aProtectedCells, [self::class, 'cellReverseSort'])
: uksort($aProtectedCells, [self::class, 'cellSort']);
foreach ($aProtectedCells as $cellAddress => $value) {
$newReference = $this->updateCellReference($cellAddress);
if ($cellAddress !== $newReference) {
$worksheet->protectCells($newReference, $value, true);
$worksheet->unprotectCells($cellAddress);
}
}
}
/**
* Update column dimensions when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
*/
protected function adjustColumnDimensions(Worksheet $worksheet): void
{
$aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true);
if (!empty($aColumnDimensions)) {
foreach ($aColumnDimensions as $objColumnDimension) {
$newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1');
[$newReference] = Coordinate::coordinateFromString($newReference);
if ($objColumnDimension->getColumnIndex() !== $newReference) {
$objColumnDimension->setColumnIndex($newReference);
}
}
$worksheet->refreshColumnDimensions();
}
}
/**
* Update row dimensions when inserting/deleting rows/columns.
*
* @param Worksheet $worksheet The worksheet that we're editing
* @param int $beforeRow Number of the row we're inserting/deleting before
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
protected function adjustRowDimensions(Worksheet $worksheet, $beforeRow, $numberOfRows): void
{
$aRowDimensions = array_reverse($worksheet->getRowDimensions(), true);
if (!empty($aRowDimensions)) {
foreach ($aRowDimensions as $objRowDimension) {
$newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex());
[, $newReference] = Coordinate::coordinateFromString($newReference);
$newRoweference = (int) $newReference;
if ($objRowDimension->getRowIndex() !== $newRoweference) {
$objRowDimension->setRowIndex($newRoweference);
}
}
$worksheet->refreshRowDimensions();
$copyDimension = $worksheet->getRowDimension($beforeRow - 1);
for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) {
$newDimension = $worksheet->getRowDimension($i);
$newDimension->setRowHeight($copyDimension->getRowHeight());
$newDimension->setVisible($copyDimension->getVisible());
$newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
$newDimension->setCollapsed($copyDimension->getCollapsed());
}
}
}
/**
* Insert a new column or row, updating all possible related data.
*
* @param string $beforeCellAddress Insert before this cell address (e.g. 'A1')
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
* @param Worksheet $worksheet The worksheet that we're editing
*/
public function insertNewBefore(
string $beforeCellAddress,
int $numberOfColumns,
int $numberOfRows,
Worksheet $worksheet
): void {
$remove = ($numberOfColumns < 0 || $numberOfRows < 0);
if (
$this->cellReferenceHelper === null ||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
) {
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
}
// Get coordinate of $beforeCellAddress
[$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress);
// Clear cells if we are removing columns or rows
$highestColumn = $worksheet->getHighestColumn();
$highestRow = $worksheet->getHighestRow();
// 1. Clear column strips if we are removing columns
if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) {
$this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet);
}
// 2. Clear row strips if we are removing rows
if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) {
$this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet);
}
// Find missing coordinates. This is important when inserting column before the last column
$cellCollection = $worksheet->getCellCollection();
$missingCoordinates = array_filter(
array_map(function ($row) use ($highestColumn) {
return "{$highestColumn}{$row}";
}, range(1, $highestRow)),
function ($coordinate) use ($cellCollection) {
return $cellCollection->has($coordinate) === false;
}
);
// Create missing cells with null values
if (!empty($missingCoordinates)) {
foreach ($missingCoordinates as $coordinate) {
$worksheet->createNewCell($coordinate);
}
}
$allCoordinates = $worksheet->getCoordinates();
if ($remove) {
// It's faster to reverse and pop than to use unshift, especially with large cell collections
$allCoordinates = array_reverse($allCoordinates);
}
// Loop through cells, bottom-up, and change cell coordinate
while ($coordinate = array_pop($allCoordinates)) {
$cell = $worksheet->getCell($coordinate);
$cellIndex = Coordinate::columnIndexFromString($cell->getColumn());
if ($cellIndex - 1 + $numberOfColumns < 0) {
continue;
}
// New coordinate
$newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows);
// Should the cell be updated? Move value and cellXf index from one cell to another.
if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) {
// Update cell styles
$worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());
// Insert this cell at its new location
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted
$worksheet->getCell($newCoordinate)
->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
} else {
// Cell value should not be adjusted
$worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType());
}
// Clear the original cell
$worksheet->getCellCollection()->delete($coordinate);
} else {
/* We don't need to update styles for rows/columns before our insertion position,
but we do still need to adjust any formulae in those cells */
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted
$cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
}
}
}
// Duplicate styles for the newly inserted cells
$highestColumn = $worksheet->getHighestColumn();
$highestRow = $worksheet->getHighestRow();
if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
$this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
}
if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
$this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
}
// Update worksheet: column dimensions
$this->adjustColumnDimensions($worksheet);
// Update worksheet: row dimensions
$this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows);
// Update worksheet: page breaks
$this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: comments
$this->adjustComments($worksheet);
// Update worksheet: hyperlinks
$this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: conditional formatting styles
$this->adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: data validations
$this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: merge cells
$this->adjustMergeCells($worksheet);
// Update worksheet: protected cells
$this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows);
// Update worksheet: autofilter
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);
// Update worksheet: table
$this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);
// Update worksheet: freeze pane
if ($worksheet->getFreezePane()) {
$splitCell = $worksheet->getFreezePane();
$topLeftCell = $worksheet->getTopLeftCell() ?? '';
$splitCell = $this->updateCellReference($splitCell);
$topLeftCell = $this->updateCellReference($topLeftCell);
$worksheet->freezePane($splitCell, $topLeftCell);
}
// Page setup
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
$worksheet->getPageSetup()->setPrintArea(
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea())
);
}
// Update worksheet: drawings
$aDrawings = $worksheet->getDrawingCollection();
foreach ($aDrawings as $objDrawing) {
$newReference = $this->updateCellReference($objDrawing->getCoordinates());
if ($objDrawing->getCoordinates() != $newReference) {
$objDrawing->setCoordinates($newReference);
}
if ($objDrawing->getCoordinates2() !== '') {
$newReference = $this->updateCellReference($objDrawing->getCoordinates2());
if ($objDrawing->getCoordinates2() != $newReference) {
$objDrawing->setCoordinates2($newReference);
}
}
}
// Update workbook: define names
if (count($worksheet->getParentOrThrow()->getDefinedNames()) > 0) {
$this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
}
// Garbage collect
$worksheet->garbageCollect();
}
/**
* Update references within formulas.
*
* @param string $formula Formula to update
* @param string $beforeCellAddress Insert before this one
* @param int $numberOfColumns Number of columns to insert
* @param int $numberOfRows Number of rows to insert
* @param string $worksheetName Worksheet name/title
*
* @return string Updated formula
*/
public function updateFormulaReferences(
$formula = '',
$beforeCellAddress = 'A1',
$numberOfColumns = 0,
$numberOfRows = 0,
$worksheetName = '',
bool $includeAbsoluteReferences = false
) {
if (
$this->cellReferenceHelper === null ||
$this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
) {
$this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
}
// Update cell references in the formula
$formulaBlocks = explode('"', $formula);
$i = false;
foreach ($formulaBlocks as &$formulaBlock) {
// Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
$i = $i === false;
if ($i) {
$adjustCount = 0;
$newCellTokens = $cellTokens = [];
// Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) {
foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4];
$modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences), 2);
$modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences), 2);
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3 . ':' . $modified4;
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = 100000;
$row = 10000000 + (int) trim($match[3], '$');
$cellIndex = "{$column}{$row}";
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
++$adjustCount;
}
}
}
}
// Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) {
foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4];
$modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences), 0, -2);
$modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences), 0, -2);
if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3 . ':' . $modified4;
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
$row = 10000000;
$cellIndex = "{$column}{$row}";
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?![A-Z])/i';
++$adjustCount;
}
}
}
}
// Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) {
foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3] . ':' . $match[4];
$modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
$modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences);
if ($match[3] . $match[4] !== $modified3 . $modified4) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3 . ':' . $modified4;
[$column, $row] = Coordinate::coordinateFromString($match[3]);
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
$row = (int) trim($row, '$') + 10000000;
$cellIndex = "{$column}{$row}";
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
++$adjustCount;
}
}
}
}
// Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
$matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
if ($matchCount > 0) {
foreach ($matches as $match) {
$fromString = ($match[2] > '') ? $match[2] . '!' : '';
$fromString .= $match[3];
$modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
if ($match[3] !== $modified3) {
if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
$toString = ($match[2] > '') ? $match[2] . '!' : '';
$toString .= $modified3;
[$column, $row] = Coordinate::coordinateFromString($match[3]);
$columnAdditionalIndex = $column[0] === '$' ? 1 : 0;
$rowAdditionalIndex = $row[0] === '$' ? 1 : 0;
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
$row = (int) trim($row, '$') + 10000000;
$cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex;
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?!\d)/i';
++$adjustCount;
}
}
}
}
if ($adjustCount > 0) {
if ($numberOfColumns > 0 || $numberOfRows > 0) {
krsort($cellTokens);
krsort($newCellTokens);
} else {
ksort($cellTokens);
ksort($newCellTokens);
} // Update cell references in the formula
$formulaBlock = str_replace('\\', '', (string) preg_replace($cellTokens, $newCellTokens, $formulaBlock));
}
}
}
unset($formulaBlock);
// Then rebuild the formula string
return implode('"', $formulaBlocks);
}
/**
* Update all cell references within a formula, irrespective of worksheet.
*/
public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string
{
$formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows);
if ($numberOfColumns !== 0) {
$formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns);
}
if ($numberOfRows !== 0) {
$formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows);
}
return $formula;
}
private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string
{
$splitCount = preg_match_all(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
$formula,
$splitRanges,
PREG_OFFSET_CAPTURE
);
$columnLengths = array_map('strlen', array_column($splitRanges[6], 0));
$rowLengths = array_map('strlen', array_column($splitRanges[7], 0));
$columnOffsets = array_column($splitRanges[6], 1);
$rowOffsets = array_column($splitRanges[7], 1);
$columns = $splitRanges[6];
$rows = $splitRanges[7];
while ($splitCount > 0) {
--$splitCount;
$columnLength = $columnLengths[$splitCount];
$rowLength = $rowLengths[$splitCount];
$columnOffset = $columnOffsets[$splitCount];
$rowOffset = $rowOffsets[$splitCount];
$column = $columns[$splitCount][0];
$row = $rows[$splitCount][0];
if (!empty($column) && $column[0] !== '$') {
$column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns);
$formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength);
}
if (!empty($row) && $row[0] !== '$') {
$row = (int) $row + $numberOfRows;
$formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength);
}
}
return $formula;
}
private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string
{
$splitCount = preg_match_all(
'/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui',
$formula,
$splitRanges,
PREG_OFFSET_CAPTURE
);
$fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0));
$fromColumnOffsets = array_column($splitRanges[1], 1);
$toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0));
$toColumnOffsets = array_column($splitRanges[2], 1);
$fromColumns = $splitRanges[1];
$toColumns = $splitRanges[2];
while ($splitCount > 0) {
--$splitCount;
$fromColumnLength = $fromColumnLengths[$splitCount];
$toColumnLength = $toColumnLengths[$splitCount];
$fromColumnOffset = $fromColumnOffsets[$splitCount];
$toColumnOffset = $toColumnOffsets[$splitCount];
$fromColumn = $fromColumns[$splitCount][0];
$toColumn = $toColumns[$splitCount][0];
if (!empty($fromColumn) && $fromColumn[0] !== '$') {
$fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns);
$formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength);
}
if (!empty($toColumn) && $toColumn[0] !== '$') {
$toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns);
$formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength);
}
}
return $formula;
}
private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string
{
$splitCount = preg_match_all(
'/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui',
$formula,
$splitRanges,
PREG_OFFSET_CAPTURE
);
$fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0));
$fromRowOffsets = array_column($splitRanges[1], 1);
$toRowLengths = array_map('strlen', array_column($splitRanges[2], 0));
$toRowOffsets = array_column($splitRanges[2], 1);
$fromRows = $splitRanges[1];
$toRows = $splitRanges[2];
while ($splitCount > 0) {
--$splitCount;
$fromRowLength = $fromRowLengths[$splitCount];
$toRowLength = $toRowLengths[$splitCount];
$fromRowOffset = $fromRowOffsets[$splitCount];
$toRowOffset = $toRowOffsets[$splitCount];
$fromRow = $fromRows[$splitCount][0];
$toRow = $toRows[$splitCount][0];
if (!empty($fromRow) && $fromRow[0] !== '$') {
$fromRow = (int) $fromRow + $numberOfRows;
$formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength);
}
if (!empty($toRow) && $toRow[0] !== '$') {
$toRow = (int) $toRow + $numberOfRows;
$formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength);
}
}
return $formula;
}
/**
* Update cell reference.
*
* @param string $cellReference Cell address or range of addresses
*
* @return string Updated cell range
*/
private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false)
{
// Is it in another worksheet? Will not have to update anything.
if (strpos($cellReference, '!') !== false) {
return $cellReference;
}
// Is it a range or a single cell?
if (!Coordinate::coordinateIsRange($cellReference)) {
// Single cell
return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences);
}
// Range
return $this->updateCellRange($cellReference, $includeAbsoluteReferences);
}
/**
* Update named formulae (i.e. containing worksheet references / named ranges).
*
* @param Spreadsheet $spreadsheet Object to update
* @param string $oldName Old name (name to replace)
* @param string $newName New name
*/
public function updateNamedFormulae(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
{
if ($oldName == '') {
return;
}
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
foreach ($sheet->getCoordinates(false) as $coordinate) {
$cell = $sheet->getCell($coordinate);
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
$formula = $cell->getValue();
if (strpos($formula, $oldName) !== false) {
$formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
$formula = str_replace($oldName . '!', $newName . '!', $formula);
$cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
}
}
}
}
}
private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
foreach ($worksheet->getParentOrThrow()->getDefinedNames() as $definedName) {
if ($definedName->isFormula() === false) {
$this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
} else {
$this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
}
}
}
private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
$cellAddress = $definedName->getValue();
$asFormula = ($cellAddress[0] === '=');
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
/**
* If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
* PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
* them with a #REF!
*/
if ($asFormula === true) {
$formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
} else {
$definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true));
}
}
}
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
/**
* If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
* PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
* Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
* TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
* them with a #REF!
*/
$formula = $definedName->getValue();
$formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
}
}
/**
* Update cell range.
*
* @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
*
* @return string Updated cell range
*/
private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false): string
{
if (!Coordinate::coordinateIsRange($cellRange)) {
throw new Exception('Only cell ranges may be passed to this method.');
}
// Update range
$range = Coordinate::splitRange($cellRange);
$ic = count($range);
for ($i = 0; $i < $ic; ++$i) {
$jc = count($range[$i]);
for ($j = 0; $j < $jc; ++$j) {
if (ctype_alpha($range[$i][$j])) {
$range[$i][$j] = Coordinate::coordinateFromString(
$this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences)
)[0];
} elseif (ctype_digit($range[$i][$j])) {
$range[$i][$j] = Coordinate::coordinateFromString(
$this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences)
)[1];
} else {
$range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences);
}
}
}
// Recreate range string
return Coordinate::buildRange($range);
}
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
{
$startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns);
$endColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
for ($row = 1; $row <= $highestRow - 1; ++$row) {
for ($column = $startColumnId; $column !== $endColumnId; ++$column) {
$coordinate = $column . $row;
$this->clearStripCell($worksheet, $coordinate);
}
}
}
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
{
$startColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
++$highestColumn;
for ($column = $startColumnId; $column !== $highestColumn; ++$column) {
for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) {
$coordinate = $column . $row;
$this->clearStripCell($worksheet, $coordinate);
}
}
}
private function clearStripCell(Worksheet $worksheet, string $coordinate): void
{
$worksheet->removeConditionalStyles($coordinate);
$worksheet->setHyperlink($coordinate);
$worksheet->setDataValidation($coordinate);
$worksheet->removeComment($coordinate);
if ($worksheet->cellExists($coordinate)) {
$worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
$worksheet->getCell($coordinate)->setXfIndex(0);
}
}
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
{
$autoFilter = $worksheet->getAutoFilter();
$autoFilterRange = $autoFilter->getRange();
if (!empty($autoFilterRange)) {
if ($numberOfColumns !== 0) {
$autoFilterColumns = $autoFilter->getColumns();
if (count($autoFilterColumns) > 0) {
$column = '';
$row = 0;
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
$columnIndex = Coordinate::columnIndexFromString((string) $column);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
if ($columnIndex <= $rangeEnd[0]) {
if ($numberOfColumns < 0) {
$this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
}
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
// Shuffle columns in autofilter range
if ($numberOfColumns > 0) {
$this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
} else {
$this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
}
}
}
}
$worksheet->setAutoFilter(
$this->updateCellReference($autoFilterRange)
);
}
}
private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void
{
// If we're actually deleting any columns that fall within the autofilter range,
// then we delete any rules for those columns
$deleteColumn = $columnIndex + $numberOfColumns - 1;
$deleteCount = abs($numberOfColumns);
for ($i = 1; $i <= $deleteCount; ++$i) {
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
if (isset($autoFilterColumns[$columnName])) {
$autoFilter->clearColumn($columnName);
}
++$deleteColumn;
}
}
private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
{
$startColRef = $startCol;
$endColRef = $rangeEnd;
$toColRef = $rangeEnd + $numberOfColumns;
do {
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
--$endColRef;
--$toColRef;
} while ($startColRef <= $endColRef);
}
private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
{
// For delete, we shuffle from beginning to end to avoid overwriting
$startColID = Coordinate::stringFromColumnIndex($startCol);
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
do {
$autoFilter->shiftColumn($startColID, $toColID);
++$startColID;
++$toColID;
} while ($startColID !== $endColID);
}
private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
{
$tableCollection = $worksheet->getTableCollection();
foreach ($tableCollection as $table) {
$tableRange = $table->getRange();
if (!empty($tableRange)) {
if ($numberOfColumns !== 0) {
$tableColumns = $table->getColumns();
if (count($tableColumns) > 0) {
$column = '';
$row = 0;
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
$columnIndex = Coordinate::columnIndexFromString((string) $column);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
if ($columnIndex <= $rangeEnd[0]) {
if ($numberOfColumns < 0) {
$this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
}
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
// Shuffle columns in table range
if ($numberOfColumns > 0) {
$this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
} else {
$this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
}
}
}
}
$table->setRange($this->updateCellReference($tableRange));
}
}
}
private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
{
// If we're actually deleting any columns that fall within the table range,
// then we delete any rules for those columns
$deleteColumn = $columnIndex + $numberOfColumns - 1;
$deleteCount = abs($numberOfColumns);
for ($i = 1; $i <= $deleteCount; ++$i) {
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
if (isset($tableColumns[$columnName])) {
$table->clearColumn($columnName);
}
++$deleteColumn;
}
}
private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
{
$startColRef = $startCol;
$endColRef = $rangeEnd;
$toColRef = $rangeEnd + $numberOfColumns;
do {
$table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
--$endColRef;
--$toColRef;
} while ($startColRef <= $endColRef);
}
private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
{
// For delete, we shuffle from beginning to end to avoid overwriting
$startColID = Coordinate::stringFromColumnIndex($startCol);
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
do {
$table->shiftColumn($startColID, $toColID);
++$startColID;
++$toColID;
} while ($startColID !== $endColID);
}
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
{
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
// Style
$coordinate = $beforeColumnName . $i;
if ($worksheet->cellExists($coordinate)) {
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
$worksheet->getCell([$j, $i])->setXfIndex($xfIndex);
}
}
}
}
private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void
{
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
// Style
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
if ($worksheet->cellExists($coordinate)) {
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
}
}
}
}
/**
* __clone implementation. Cloning should not be allowed in a Singleton!
*/
final public function __clone()
{
throw new Exception('Cloning a Singleton is not allowed!');
}
}
phpspreadsheet/src/PhpSpreadsheet/Settings.php 0000644 00000014001 15243417764 0015613 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer;
use PhpOffice\PhpSpreadsheet\Collection\Memory;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\SimpleCache\CacheInterface;
use ReflectionClass;
class Settings
{
/**
* Class name of the chart renderer used for rendering charts
* eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph.
*
* @var ?string
*/
private static $chartRenderer;
/**
* Default options for libxml loader.
*
* @var ?int
*/
private static $libXmlLoaderOptions;
/**
* The cache implementation to be used for cell collection.
*
* @var ?CacheInterface
*/
private static $cache;
/**
* The HTTP client implementation to be used for network request.
*
* @var null|ClientInterface
*/
private static $httpClient;
/**
* @var null|RequestFactoryInterface
*/
private static $requestFactory;
/**
* Set the locale code to use for formula translations and any special formatting.
*
* @param string $locale The locale code to use (e.g. "fr" or "pt_br" or "en_uk")
*
* @return bool Success or failure
*/
public static function setLocale(string $locale)
{
return Calculation::getInstance()->setLocale($locale);
}
public static function getLocale(): string
{
return Calculation::getInstance()->getLocale();
}
/**
* Identify to PhpSpreadsheet the external library to use for rendering charts.
*
* @param string $rendererClassName Class name of the chart renderer
* eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph
*/
public static function setChartRenderer(string $rendererClassName): void
{
if (!is_a($rendererClassName, IRenderer::class, true)) {
throw new Exception('Chart renderer must implement ' . IRenderer::class);
}
self::$chartRenderer = $rendererClassName;
}
/**
* Return the Chart Rendering Library that PhpSpreadsheet is currently configured to use.
*
* @return null|string Class name of the chart renderer
* eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph
*/
public static function getChartRenderer(): ?string
{
return self::$chartRenderer;
}
public static function htmlEntityFlags(): int
{
return \ENT_COMPAT;
}
/**
* Set default options for libxml loader.
*
* @param ?int $options Default options for libxml loader
*/
public static function setLibXmlLoaderOptions($options): int
{
if ($options === null) {
$options = defined('LIBXML_DTDLOAD') ? (LIBXML_DTDLOAD | LIBXML_DTDATTR) : 0;
}
self::$libXmlLoaderOptions = $options;
return $options;
}
/**
* Get default options for libxml loader.
* Defaults to LIBXML_DTDLOAD | LIBXML_DTDATTR when not set explicitly.
*
* @return int Default options for libxml loader
*/
public static function getLibXmlLoaderOptions(): int
{
if (self::$libXmlLoaderOptions === null) {
return self::setLibXmlLoaderOptions(null);
}
return self::$libXmlLoaderOptions;
}
/**
* Deprecated, has no effect.
*
* @param bool $state
*
* @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+
*
* @codeCoverageIgnore
*/
public static function setLibXmlDisableEntityLoader(/** @scrutinizer ignore-unused */ $state): void
{
// noop
}
/**
* Deprecated, has no effect.
*
* @return bool $state
*
* @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+
*
* @codeCoverageIgnore
*/
public static function getLibXmlDisableEntityLoader(): bool
{
return true;
}
/**
* Sets the implementation of cache that should be used for cell collection.
*/
public static function setCache(?CacheInterface $cache): void
{
self::$cache = $cache;
}
/**
* Gets the implementation of cache that is being used for cell collection.
*/
public static function getCache(): CacheInterface
{
if (!self::$cache) {
self::$cache = self::useSimpleCacheVersion3() ? new Memory\SimpleCache3() : new Memory\SimpleCache1();
}
return self::$cache;
}
public static function useSimpleCacheVersion3(): bool
{
return
PHP_MAJOR_VERSION === 8 &&
(new ReflectionClass(CacheInterface::class))->getMethod('get')->getReturnType() !== null;
}
/**
* Set the HTTP client implementation to be used for network request.
*/
public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void
{
self::$httpClient = $httpClient;
self::$requestFactory = $requestFactory;
}
/**
* Unset the HTTP client configuration.
*/
public static function unsetHttpClient(): void
{
self::$httpClient = null;
self::$requestFactory = null;
}
/**
* Get the HTTP client implementation to be used for network request.
*/
public static function getHttpClient(): ClientInterface
{
if (!self::$httpClient || !self::$requestFactory) {
throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.');
}
return self::$httpClient;
}
/**
* Get the HTTP request factory.
*/
public static function getRequestFactory(): RequestFactoryInterface
{
if (!self::$httpClient || !self::$requestFactory) {
throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.');
}
return self::$requestFactory;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php 0000644 00000007474 15243417764 0023673 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Style\Style;
class StyleMerger
{
/**
* @var Style
*/
protected $baseStyle;
public function __construct(Style $baseStyle)
{
$this->baseStyle = $baseStyle;
}
public function getStyle(): Style
{
return $this->baseStyle;
}
public function mergeStyle(Style $style): void
{
if ($style->getNumberFormat() !== null && $style->getNumberFormat()->getFormatCode() !== null) {
$this->baseStyle->getNumberFormat()->setFormatCode($style->getNumberFormat()->getFormatCode());
}
if ($style->getFont() !== null) {
$this->mergeFontStyle($this->baseStyle->getFont(), $style->getFont());
}
if ($style->getFill() !== null) {
$this->mergeFillStyle($this->baseStyle->getFill(), $style->getFill());
}
if ($style->getBorders() !== null) {
$this->mergeBordersStyle($this->baseStyle->getBorders(), $style->getBorders());
}
}
protected function mergeFontStyle(Font $baseFontStyle, Font $fontStyle): void
{
if ($fontStyle->getBold() !== null) {
$baseFontStyle->setBold($fontStyle->getBold());
}
if ($fontStyle->getItalic() !== null) {
$baseFontStyle->setItalic($fontStyle->getItalic());
}
if ($fontStyle->getStrikethrough() !== null) {
$baseFontStyle->setStrikethrough($fontStyle->getStrikethrough());
}
if ($fontStyle->getUnderline() !== null) {
$baseFontStyle->setUnderline($fontStyle->getUnderline());
}
if ($fontStyle->getColor() !== null && $fontStyle->getColor()->getARGB() !== null) {
$baseFontStyle->setColor($fontStyle->getColor());
}
}
protected function mergeFillStyle(Fill $baseFillStyle, Fill $fillStyle): void
{
if ($fillStyle->getFillType() !== null) {
$baseFillStyle->setFillType($fillStyle->getFillType());
}
//if ($fillStyle->getRotation() !== null) {
$baseFillStyle->setRotation($fillStyle->getRotation());
//}
if ($fillStyle->getStartColor() !== null && $fillStyle->getStartColor()->getARGB() !== null) {
$baseFillStyle->setStartColor($fillStyle->getStartColor());
}
if ($fillStyle->getEndColor() !== null && $fillStyle->getEndColor()->getARGB() !== null) {
$baseFillStyle->setEndColor($fillStyle->getEndColor());
}
}
protected function mergeBordersStyle(Borders $baseBordersStyle, Borders $bordersStyle): void
{
if ($bordersStyle->getTop() !== null) {
$this->mergeBorderStyle($baseBordersStyle->getTop(), $bordersStyle->getTop());
}
if ($bordersStyle->getBottom() !== null) {
$this->mergeBorderStyle($baseBordersStyle->getBottom(), $bordersStyle->getBottom());
}
if ($bordersStyle->getLeft() !== null) {
$this->mergeBorderStyle($baseBordersStyle->getLeft(), $bordersStyle->getLeft());
}
if ($bordersStyle->getRight() !== null) {
$this->mergeBorderStyle($baseBordersStyle->getRight(), $bordersStyle->getRight());
}
}
protected function mergeBorderStyle(Border $baseBorderStyle, Border $borderStyle): void
{
//if ($borderStyle->getBorderStyle() !== null) {
$baseBorderStyle->setBorderStyle($borderStyle->getBorderStyle());
//}
if ($borderStyle->getColor() !== null && $borderStyle->getColor()->getARGB() !== null) {
$baseBorderStyle->setColor($borderStyle->getColor());
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php 0000644 00000024366 15243417764 0023613 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Exception;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class CellMatcher
{
public const COMPARISON_OPERATORS = [
Conditional::OPERATOR_EQUAL => '=',
Conditional::OPERATOR_GREATERTHAN => '>',
Conditional::OPERATOR_GREATERTHANOREQUAL => '>=',
Conditional::OPERATOR_LESSTHAN => '<',
Conditional::OPERATOR_LESSTHANOREQUAL => '<=',
Conditional::OPERATOR_NOTEQUAL => '<>',
];
public const COMPARISON_RANGE_OPERATORS = [
Conditional::OPERATOR_BETWEEN => 'IF(AND(A1>=%s,A1<=%s),TRUE,FALSE)',
Conditional::OPERATOR_NOTBETWEEN => 'IF(AND(A1>=%s,A1<=%s),FALSE,TRUE)',
];
public const COMPARISON_DUPLICATES_OPERATORS = [
Conditional::CONDITION_DUPLICATES => "COUNTIF('%s'!%s,%s)>1",
Conditional::CONDITION_UNIQUE => "COUNTIF('%s'!%s,%s)=1",
];
/**
* @var Cell
*/
protected $cell;
/**
* @var int
*/
protected $cellRow;
/**
* @var Worksheet
*/
protected $worksheet;
/**
* @var int
*/
protected $cellColumn;
/**
* @var string
*/
protected $conditionalRange;
/**
* @var string
*/
protected $referenceCell;
/**
* @var int
*/
protected $referenceRow;
/**
* @var int
*/
protected $referenceColumn;
/**
* @var Calculation
*/
protected $engine;
public function __construct(Cell $cell, string $conditionalRange)
{
$this->cell = $cell;
$this->worksheet = $cell->getWorksheet();
[$this->cellColumn, $this->cellRow] = Coordinate::indexesFromString($this->cell->getCoordinate());
$this->setReferenceCellForExpressions($conditionalRange);
$this->engine = Calculation::getInstance($this->worksheet->getParent());
}
protected function setReferenceCellForExpressions(string $conditionalRange): void
{
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
[$this->referenceCell] = $conditionalRange[0];
[$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
// Convert our conditional range to an absolute conditional range, so it can be used "pinned" in formulae
$rangeSets = [];
foreach ($conditionalRange as $rangeSet) {
$absoluteRangeSet = array_map(
[Coordinate::class, 'absoluteCoordinate'],
$rangeSet
);
$rangeSets[] = implode(':', $absoluteRangeSet);
}
$this->conditionalRange = implode(',', $rangeSets);
}
public function evaluateConditional(Conditional $conditional): bool
{
// Some calculations may modify the stored cell; so reset it before every evaluation.
$cellColumn = Coordinate::stringFromColumnIndex($this->cellColumn);
$cellAddress = "{$cellColumn}{$this->cellRow}";
$this->cell = $this->worksheet->getCell($cellAddress);
switch ($conditional->getConditionType()) {
case Conditional::CONDITION_CELLIS:
return $this->processOperatorComparison($conditional);
case Conditional::CONDITION_DUPLICATES:
case Conditional::CONDITION_UNIQUE:
return $this->processDuplicatesComparison($conditional);
case Conditional::CONDITION_CONTAINSTEXT:
// Expression is NOT(ISERROR(SEARCH("<TEXT>",<Cell Reference>)))
case Conditional::CONDITION_NOTCONTAINSTEXT:
// Expression is ISERROR(SEARCH("<TEXT>",<Cell Reference>))
case Conditional::CONDITION_BEGINSWITH:
// Expression is LEFT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>"
case Conditional::CONDITION_ENDSWITH:
// Expression is RIGHT(<Cell Reference>,LEN("<TEXT>"))="<TEXT>"
case Conditional::CONDITION_CONTAINSBLANKS:
// Expression is LEN(TRIM(<Cell Reference>))=0
case Conditional::CONDITION_NOTCONTAINSBLANKS:
// Expression is LEN(TRIM(<Cell Reference>))>0
case Conditional::CONDITION_CONTAINSERRORS:
// Expression is ISERROR(<Cell Reference>)
case Conditional::CONDITION_NOTCONTAINSERRORS:
// Expression is NOT(ISERROR(<Cell Reference>))
case Conditional::CONDITION_TIMEPERIOD:
// Expression varies, depending on specified timePeriod value, e.g.
// Yesterday FLOOR(<Cell Reference>,1)=TODAY()-1
// Today FLOOR(<Cell Reference>,1)=TODAY()
// Tomorrow FLOOR(<Cell Reference>,1)=TODAY()+1
// Last 7 Days AND(TODAY()-FLOOR(<Cell Reference>,1)<=6,FLOOR(<Cell Reference>,1)<=TODAY())
case Conditional::CONDITION_EXPRESSION:
return $this->processExpression($conditional);
}
return false;
}
/**
* @param mixed $value
*
* @return float|int|string
*/
protected function wrapValue($value)
{
if (!is_numeric($value)) {
if (is_bool($value)) {
return $value ? 'TRUE' : 'FALSE';
} elseif ($value === null) {
return 'NULL';
}
return '"' . $value . '"';
}
return $value;
}
/**
* @return float|int|string
*/
protected function wrapCellValue()
{
return $this->wrapValue($this->cell->getCalculatedValue());
}
/**
* @return float|int|string
*/
protected function conditionCellAdjustment(array $matches)
{
$column = $matches[6];
$row = $matches[7];
if (strpos($column, '$') === false) {
$column = Coordinate::columnIndexFromString($column);
$column += $this->cellColumn - $this->referenceColumn;
$column = Coordinate::stringFromColumnIndex($column);
}
if (strpos($row, '$') === false) {
$row += $this->cellRow - $this->referenceRow;
}
if (!empty($matches[4])) {
$worksheet = $this->worksheet->getParentOrThrow()->getSheetByName(trim($matches[4], "'"));
if ($worksheet === null) {
return $this->wrapValue(null);
}
return $this->wrapValue(
$worksheet
->getCell(str_replace('$', '', "{$column}{$row}"))
->getCalculatedValue()
);
}
return $this->wrapValue(
$this->worksheet
->getCell(str_replace('$', '', "{$column}{$row}"))
->getCalculatedValue()
);
}
protected function cellConditionCheck(string $condition): string
{
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
$i = false;
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
$i = $i === false;
if ($i) {
$value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
[$this, 'conditionCellAdjustment'],
$value
);
}
}
unset($value);
// Then rebuild the condition string to return it
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
}
protected function adjustConditionsForCellReferences(array $conditions): array
{
return array_map(
[$this, 'cellConditionCheck'],
$conditions
);
}
protected function processOperatorComparison(Conditional $conditional): bool
{
if (array_key_exists($conditional->getOperatorType(), self::COMPARISON_RANGE_OPERATORS)) {
return $this->processRangeOperator($conditional);
}
$operator = self::COMPARISON_OPERATORS[$conditional->getOperatorType()];
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
$expression = sprintf('%s%s%s', (string) $this->wrapCellValue(), $operator, (string) array_pop($conditions));
return $this->evaluateExpression($expression);
}
protected function processRangeOperator(Conditional $conditional): bool
{
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
sort($conditions);
$expression = sprintf(
(string) preg_replace(
'/\bA1\b/i',
(string) $this->wrapCellValue(),
self::COMPARISON_RANGE_OPERATORS[$conditional->getOperatorType()]
),
...$conditions
);
return $this->evaluateExpression($expression);
}
protected function processDuplicatesComparison(Conditional $conditional): bool
{
$worksheetName = $this->cell->getWorksheet()->getTitle();
$expression = sprintf(
self::COMPARISON_DUPLICATES_OPERATORS[$conditional->getConditionType()],
$worksheetName,
$this->conditionalRange,
$this->cellConditionCheck($this->cell->getCalculatedValue())
);
return $this->evaluateExpression($expression);
}
protected function processExpression(Conditional $conditional): bool
{
$conditions = $this->adjustConditionsForCellReferences($conditional->getConditions());
$expression = array_pop($conditions);
$expression = (string) preg_replace(
'/\b' . $this->referenceCell . '\b/i',
(string) $this->wrapCellValue(),
$expression
);
return $this->evaluateExpression($expression);
}
protected function evaluateExpression(string $expression): bool
{
$expression = "={$expression}";
try {
$this->engine->flushInstance();
$result = (bool) $this->engine->calculateFormula($expression);
} catch (Exception $e) {
return false;
}
return $result;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php0000644 00000017327 15243417764 0030452 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use SimpleXMLElement;
class ConditionalFormattingRuleExtension
{
const CONDITION_EXTENSION_DATABAR = 'dataBar';
/** <conditionalFormatting> attributes */
/** @var string */
private $id;
/** @var string Conditional Formatting Rule */
private $cfRule;
/** <conditionalFormatting> children */
/** @var ConditionalDataBarExtension */
private $dataBar;
/** @var string Sequence of References */
private $sqref;
/**
* ConditionalFormattingRuleExtension constructor.
*/
public function __construct(?string $id = null, string $cfRule = self::CONDITION_EXTENSION_DATABAR)
{
if (null === $id) {
$this->id = '{' . $this->generateUuid() . '}';
} else {
$this->id = $id;
}
$this->cfRule = $cfRule;
}
private function generateUuid(): string
{
$chars = str_split('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx');
foreach ($chars as $i => $char) {
if ($char === 'x') {
$chars[$i] = dechex(random_int(0, 15));
} elseif ($char === 'y') {
$chars[$i] = dechex(random_int(8, 11));
}
}
return implode('', /** @scrutinizer ignore-type */ $chars);
}
public static function parseExtLstXml(?SimpleXMLElement $extLstXml): array
{
$conditionalFormattingRuleExtensions = [];
$conditionalFormattingRuleExtensionXml = null;
if ($extLstXml instanceof SimpleXMLElement) {
foreach ((count($extLstXml) > 0 ? $extLstXml : [$extLstXml]) as $extLst) {
//this uri is conditionalFormattings
//https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627
if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
$conditionalFormattingRuleExtensionXml = $extLst->ext;
}
}
if ($conditionalFormattingRuleExtensionXml) {
$ns = $conditionalFormattingRuleExtensionXml->getNamespaces(true);
$extFormattingsXml = $conditionalFormattingRuleExtensionXml->children($ns['x14']);
foreach ($extFormattingsXml->children($ns['x14']) as $extFormattingXml) {
$extCfRuleXml = $extFormattingXml->cfRule;
$attributes = $extCfRuleXml->attributes();
if (!$attributes || ((string) $attributes->type) !== Conditional::CONDITION_DATABAR) {
continue;
}
$extFormattingRuleObj = new self((string) $attributes->id);
$extFormattingRuleObj->setSqref((string) $extFormattingXml->children($ns['xm'])->sqref);
$conditionalFormattingRuleExtensions[$extFormattingRuleObj->getId()] = $extFormattingRuleObj;
$extDataBarObj = new ConditionalDataBarExtension();
$extFormattingRuleObj->setDataBarExt($extDataBarObj);
$dataBarXml = $extCfRuleXml->dataBar;
self::parseExtDataBarAttributesFromXml($extDataBarObj, $dataBarXml);
self::parseExtDataBarElementChildrenFromXml($extDataBarObj, $dataBarXml, $ns);
}
}
}
return $conditionalFormattingRuleExtensions;
}
private static function parseExtDataBarAttributesFromXml(
ConditionalDataBarExtension $extDataBarObj,
SimpleXMLElement $dataBarXml
): void {
$dataBarAttribute = $dataBarXml->attributes();
if ($dataBarAttribute === null) {
return;
}
if ($dataBarAttribute->minLength) {
$extDataBarObj->setMinLength((int) $dataBarAttribute->minLength);
}
if ($dataBarAttribute->maxLength) {
$extDataBarObj->setMaxLength((int) $dataBarAttribute->maxLength);
}
if ($dataBarAttribute->border) {
$extDataBarObj->setBorder((bool) (string) $dataBarAttribute->border);
}
if ($dataBarAttribute->gradient) {
$extDataBarObj->setGradient((bool) (string) $dataBarAttribute->gradient);
}
if ($dataBarAttribute->direction) {
$extDataBarObj->setDirection((string) $dataBarAttribute->direction);
}
if ($dataBarAttribute->negativeBarBorderColorSameAsPositive) {
$extDataBarObj->setNegativeBarBorderColorSameAsPositive((bool) (string) $dataBarAttribute->negativeBarBorderColorSameAsPositive);
}
if ($dataBarAttribute->axisPosition) {
$extDataBarObj->setAxisPosition((string) $dataBarAttribute->axisPosition);
}
}
/** @param array|SimpleXMLElement $ns */
private static function parseExtDataBarElementChildrenFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml, $ns): void
{
if ($dataBarXml->borderColor) {
$attributes = $dataBarXml->borderColor->attributes();
if ($attributes !== null) {
$extDataBarObj->setBorderColor((string) $attributes['rgb']);
}
}
if ($dataBarXml->negativeFillColor) {
$attributes = $dataBarXml->negativeFillColor->attributes();
if ($attributes !== null) {
$extDataBarObj->setNegativeFillColor((string) $attributes['rgb']);
}
}
if ($dataBarXml->negativeBorderColor) {
$attributes = $dataBarXml->negativeBorderColor->attributes();
if ($attributes !== null) {
$extDataBarObj->setNegativeBorderColor((string) $attributes['rgb']);
}
}
if ($dataBarXml->axisColor) {
$axisColorAttr = $dataBarXml->axisColor->attributes();
if ($axisColorAttr !== null) {
$extDataBarObj->setAxisColor((string) $axisColorAttr['rgb'], (string) $axisColorAttr['theme'], (string) $axisColorAttr['tint']);
}
}
$cfvoIndex = 0;
foreach ($dataBarXml->cfvo as $cfvo) {
$f = (string) $cfvo->/** @scrutinizer ignore-call */ children($ns['xm'])->f;
/** @scrutinizer ignore-call */
$attributes = $cfvo->attributes();
if (!($attributes)) {
continue;
}
if ($cfvoIndex === 0) {
$extDataBarObj->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f)));
}
if ($cfvoIndex === 1) {
$extDataBarObj->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f)));
}
++$cfvoIndex;
}
}
/**
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* @param mixed $id
*/
public function setId($id): self
{
$this->id = $id;
return $this;
}
public function getCfRule(): string
{
return $this->cfRule;
}
public function setCfRule(string $cfRule): self
{
$this->cfRule = $cfRule;
return $this;
}
public function getDataBarExt(): ConditionalDataBarExtension
{
return $this->dataBar;
}
public function setDataBarExt(ConditionalDataBarExtension $dataBar): self
{
$this->dataBar = $dataBar;
return $this;
}
public function getSqref(): string
{
return $this->sqref;
}
public function setSqref(string $sqref): self
{
$this->sqref = $sqref;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php 0000644 00000016655 15243417764 0024546 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellMatcher;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
/**
* @method CellValue equals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue notEquals($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue greaterThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue greaterThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue lessThan($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue lessThanOrEqual($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue between($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue notBetween($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method CellValue and($value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
*/
class CellValue extends WizardAbstract implements WizardInterface
{
protected const MAGIC_OPERATIONS = [
'equals' => Conditional::OPERATOR_EQUAL,
'notEquals' => Conditional::OPERATOR_NOTEQUAL,
'greaterThan' => Conditional::OPERATOR_GREATERTHAN,
'greaterThanOrEqual' => Conditional::OPERATOR_GREATERTHANOREQUAL,
'lessThan' => Conditional::OPERATOR_LESSTHAN,
'lessThanOrEqual' => Conditional::OPERATOR_LESSTHANOREQUAL,
'between' => Conditional::OPERATOR_BETWEEN,
'notBetween' => Conditional::OPERATOR_NOTBETWEEN,
];
protected const SINGLE_OPERATORS = CellMatcher::COMPARISON_OPERATORS;
protected const RANGE_OPERATORS = CellMatcher::COMPARISON_RANGE_OPERATORS;
/** @var string */
protected $operator = Conditional::OPERATOR_EQUAL;
/** @var array */
protected $operand = [0];
/**
* @var string[]
*/
protected $operandValueType = [];
public function __construct(string $cellRange)
{
parent::__construct($cellRange);
}
protected function operator(string $operator): void
{
if ((!isset(self::SINGLE_OPERATORS[$operator])) && (!isset(self::RANGE_OPERATORS[$operator]))) {
throw new Exception('Invalid Operator for Cell Value CF Rule Wizard');
}
$this->operator = $operator;
}
/**
* @param mixed $operand
*/
protected function operand(int $index, $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
{
if (is_string($operand)) {
$operand = $this->validateOperand($operand, $operandValueType);
}
$this->operand[$index] = $operand;
$this->operandValueType[$index] = $operandValueType;
}
/**
* @param mixed $value
*
* @return float|int|string
*/
protected function wrapValue($value, string $operandValueType)
{
if (!is_numeric($value) && !is_bool($value) && null !== $value) {
if ($operandValueType === Wizard::VALUE_TYPE_LITERAL) {
return '"' . str_replace('"', '""', $value) . '"';
}
return $this->cellConditionCheck($value);
}
if (null === $value) {
$value = 'NULL';
} elseif (is_bool($value)) {
$value = $value ? 'TRUE' : 'FALSE';
}
return $value;
}
public function getConditional(): Conditional
{
if (!isset(self::RANGE_OPERATORS[$this->operator])) {
unset($this->operand[1], $this->operandValueType[1]);
}
$values = array_map([$this, 'wrapValue'], $this->operand, $this->operandValueType);
$conditional = new Conditional();
$conditional->setConditionType(Conditional::CONDITION_CELLIS);
$conditional->setOperatorType($this->operator);
$conditional->setConditions($values);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
protected static function unwrapString(string $condition): string
{
if ((strpos($condition, '"') === 0) && (strpos(strrev($condition), '"') === 0)) {
$condition = substr($condition, 1, -1);
}
return str_replace('""', '"', $condition);
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if ($conditional->getConditionType() !== Conditional::CONDITION_CELLIS) {
throw new Exception('Conditional is not a Cell Value CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->operator = $conditional->getOperatorType();
$conditions = $conditional->getConditions();
foreach ($conditions as $index => $condition) {
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
$operandValueType = Wizard::VALUE_TYPE_LITERAL;
if (is_string($condition)) {
if (Calculation::keyInExcelConstants($condition)) {
$condition = Calculation::getExcelConstants($condition);
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
$operandValueType = Wizard::VALUE_TYPE_CELL;
$condition = self::reverseAdjustCellRef($condition, $cellRange);
} elseif (
preg_match('/\(\)/', $condition) ||
preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
) {
$operandValueType = Wizard::VALUE_TYPE_FORMULA;
$condition = self::reverseAdjustCellRef($condition, $cellRange);
} else {
$condition = self::unwrapString($condition);
}
}
$wizard->operand($index, $condition, $operandValueType);
}
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!isset(self::MAGIC_OPERATIONS[$methodName]) && $methodName !== 'and') {
throw new Exception('Invalid Operator for Cell Value CF Rule Wizard');
}
if ($methodName === 'and') {
if (!isset(self::RANGE_OPERATORS[$this->operator])) {
throw new Exception('AND Value is only appropriate for range operators');
}
// Scrutinizer ignores its own suggested workaround.
//$this->operand(1, /** @scrutinizer ignore-type */ ...$arguments);
if (count($arguments) < 2) {
$this->operand(1, $arguments[0]);
} else {
$this->operand(1, $arguments[0], $arguments[1]);
}
return $this;
}
$this->operator(self::MAGIC_OPERATIONS[$methodName]);
//$this->operand(0, ...$arguments);
if (count($arguments) < 2) {
$this->operand(0, $arguments[0]);
} else {
$this->operand(0, $arguments[0], $arguments[1]);
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php 0000644 00000005253 15243417764 0024136 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
/**
* @method Errors notError()
* @method Errors isError()
*/
class Errors extends WizardAbstract implements WizardInterface
{
protected const OPERATORS = [
'notError' => false,
'isError' => true,
];
protected const EXPRESSIONS = [
Wizard::NOT_ERRORS => 'NOT(ISERROR(%s))',
Wizard::ERRORS => 'ISERROR(%s)',
];
/**
* @var bool
*/
protected $inverse;
public function __construct(string $cellRange, bool $inverse = false)
{
parent::__construct($cellRange);
$this->inverse = $inverse;
}
protected function inverse(bool $inverse): void
{
$this->inverse = $inverse;
}
protected function getExpression(): void
{
$this->expression = sprintf(
self::EXPRESSIONS[$this->inverse ? Wizard::ERRORS : Wizard::NOT_ERRORS],
$this->referenceCell
);
}
public function getConditional(): Conditional
{
$this->getExpression();
$conditional = new Conditional();
$conditional->setConditionType(
$this->inverse ? Conditional::CONDITION_CONTAINSERRORS : Conditional::CONDITION_NOTCONTAINSERRORS
);
$conditional->setConditions([$this->expression]);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if (
$conditional->getConditionType() !== Conditional::CONDITION_CONTAINSERRORS &&
$conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSERRORS
) {
throw new Exception('Conditional is not an Errors CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSERRORS;
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!array_key_exists($methodName, self::OPERATORS)) {
throw new Exception('Invalid Operation for Errors CF Rule Wizard');
}
$this->inverse(self::OPERATORS[$methodName]);
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php 0000644 00000004213 15243417764 0024752 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
/**
* @method Errors duplicates()
* @method Errors unique()
*/
class Duplicates extends WizardAbstract implements WizardInterface
{
protected const OPERATORS = [
'duplicates' => false,
'unique' => true,
];
/**
* @var bool
*/
protected $inverse;
public function __construct(string $cellRange, bool $inverse = false)
{
parent::__construct($cellRange);
$this->inverse = $inverse;
}
protected function inverse(bool $inverse): void
{
$this->inverse = $inverse;
}
public function getConditional(): Conditional
{
$conditional = new Conditional();
$conditional->setConditionType(
$this->inverse ? Conditional::CONDITION_UNIQUE : Conditional::CONDITION_DUPLICATES
);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if (
$conditional->getConditionType() !== Conditional::CONDITION_DUPLICATES &&
$conditional->getConditionType() !== Conditional::CONDITION_UNIQUE
) {
throw new Exception('Conditional is not a Duplicates CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_UNIQUE;
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!array_key_exists($methodName, self::OPERATORS)) {
throw new Exception('Invalid Operation for Errors CF Rule Wizard');
}
$this->inverse(self::OPERATORS[$methodName]);
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php 0000644 00000001216 15243417764 0025736 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\Style;
interface WizardInterface
{
public function getCellRange(): string;
public function setCellRange(string $cellRange): void;
public function getStyle(): Style;
public function setStyle(Style $style): void;
public function getStopIfTrue(): bool;
public function setStopIfTrue(bool $stopIfTrue): void;
public function getConditional(): Conditional;
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): self;
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php 0000644 00000010361 15243417764 0024530 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
/**
* @method DateValue yesterday()
* @method DateValue today()
* @method DateValue tomorrow()
* @method DateValue lastSevenDays()
* @method DateValue lastWeek()
* @method DateValue thisWeek()
* @method DateValue nextWeek()
* @method DateValue lastMonth()
* @method DateValue thisMonth()
* @method DateValue nextMonth()
*/
class DateValue extends WizardAbstract implements WizardInterface
{
protected const MAGIC_OPERATIONS = [
'yesterday' => Conditional::TIMEPERIOD_YESTERDAY,
'today' => Conditional::TIMEPERIOD_TODAY,
'tomorrow' => Conditional::TIMEPERIOD_TOMORROW,
'lastSevenDays' => Conditional::TIMEPERIOD_LAST_7_DAYS,
'last7Days' => Conditional::TIMEPERIOD_LAST_7_DAYS,
'lastWeek' => Conditional::TIMEPERIOD_LAST_WEEK,
'thisWeek' => Conditional::TIMEPERIOD_THIS_WEEK,
'nextWeek' => Conditional::TIMEPERIOD_NEXT_WEEK,
'lastMonth' => Conditional::TIMEPERIOD_LAST_MONTH,
'thisMonth' => Conditional::TIMEPERIOD_THIS_MONTH,
'nextMonth' => Conditional::TIMEPERIOD_NEXT_MONTH,
];
protected const EXPRESSIONS = [
Conditional::TIMEPERIOD_YESTERDAY => 'FLOOR(%s,1)=TODAY()-1',
Conditional::TIMEPERIOD_TODAY => 'FLOOR(%s,1)=TODAY()',
Conditional::TIMEPERIOD_TOMORROW => 'FLOOR(%s,1)=TODAY()+1',
Conditional::TIMEPERIOD_LAST_7_DAYS => 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())',
Conditional::TIMEPERIOD_LAST_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))',
Conditional::TIMEPERIOD_THIS_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))',
Conditional::TIMEPERIOD_NEXT_WEEK => 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))',
Conditional::TIMEPERIOD_LAST_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0-1)),YEAR(%s)=YEAR(EDATE(TODAY(),0-1)))',
Conditional::TIMEPERIOD_THIS_MONTH => 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))',
Conditional::TIMEPERIOD_NEXT_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0+1)),YEAR(%s)=YEAR(EDATE(TODAY(),0+1)))',
];
/** @var string */
protected $operator;
public function __construct(string $cellRange)
{
parent::__construct($cellRange);
}
protected function operator(string $operator): void
{
$this->operator = $operator;
}
protected function setExpression(): void
{
$referenceCount = substr_count(self::EXPRESSIONS[$this->operator], '%s');
$references = array_fill(0, $referenceCount, $this->referenceCell);
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], ...$references);
}
public function getConditional(): Conditional
{
$this->setExpression();
$conditional = new Conditional();
$conditional->setConditionType(Conditional::CONDITION_TIMEPERIOD);
$conditional->setText($this->operator);
$conditional->setConditions([$this->expression]);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if ($conditional->getConditionType() !== Conditional::CONDITION_TIMEPERIOD) {
throw new Exception('Conditional is not a Date Value CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->operator = $conditional->getText();
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
throw new Exception('Invalid Operation for Date Value CF Rule Wizard');
}
$this->operator(self::MAGIC_OPERATIONS[$methodName]);
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php 0000644 00000004367 15243417764 0025026 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
/**
* @method Expression formula(string $expression)
*/
class Expression extends WizardAbstract implements WizardInterface
{
/**
* @var string
*/
protected $expression;
public function __construct(string $cellRange)
{
parent::__construct($cellRange);
}
public function expression(string $expression): self
{
$expression = $this->validateOperand($expression, Wizard::VALUE_TYPE_FORMULA);
$this->expression = $expression;
return $this;
}
public function getConditional(): Conditional
{
$expression = $this->adjustConditionsForCellReferences([$this->expression]);
$conditional = new Conditional();
$conditional->setConditionType(Conditional::CONDITION_EXPRESSION);
$conditional->setConditions($expression);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if ($conditional->getConditionType() !== Conditional::CONDITION_EXPRESSION) {
throw new Exception('Conditional is not an Expression CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->expression = self::reverseAdjustCellRef((string) ($conditional->getConditions()[0]), $cellRange);
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if ($methodName !== 'formula') {
throw new Exception('Invalid Operation for Expression CF Rule Wizard');
}
// Scrutinizer ignores its own recommendation
//$this->expression(/** @scrutinizer ignore-type */ ...$arguments);
$this->expression($arguments[0]);
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php 0000644 00000014176 15243417764 0024607 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
/**
* @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
* @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
*/
class TextValue extends WizardAbstract implements WizardInterface
{
protected const MAGIC_OPERATIONS = [
'contains' => Conditional::OPERATOR_CONTAINSTEXT,
'doesntContain' => Conditional::OPERATOR_NOTCONTAINS,
'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS,
'beginsWith' => Conditional::OPERATOR_BEGINSWITH,
'startsWith' => Conditional::OPERATOR_BEGINSWITH,
'endsWith' => Conditional::OPERATOR_ENDSWITH,
];
protected const OPERATORS = [
Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT,
Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT,
Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH,
Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH,
];
protected const EXPRESSIONS = [
Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))',
Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))',
Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s',
Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s',
];
/** @var string */
protected $operator;
/** @var string */
protected $operand;
/**
* @var string
*/
protected $operandValueType;
public function __construct(string $cellRange)
{
parent::__construct($cellRange);
}
protected function operator(string $operator): void
{
if (!isset(self::OPERATORS[$operator])) {
throw new Exception('Invalid Operator for Text Value CF Rule Wizard');
}
$this->operator = $operator;
}
protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
{
$operand = $this->validateOperand($operand, $operandValueType);
$this->operand = $operand;
$this->operandValueType = $operandValueType;
}
protected function wrapValue(string $value): string
{
return '"' . $value . '"';
}
protected function setExpression(): void
{
$operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL
? $this->wrapValue(str_replace('"', '""', $this->operand))
: $this->cellConditionCheck($this->operand);
if (
$this->operator === Conditional::OPERATOR_CONTAINSTEXT ||
$this->operator === Conditional::OPERATOR_NOTCONTAINS
) {
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell);
} else {
$this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand);
}
}
public function getConditional(): Conditional
{
$this->setExpression();
$conditional = new Conditional();
$conditional->setConditionType(self::OPERATORS[$this->operator]);
$conditional->setOperatorType($this->operator);
$conditional->setText(
$this->operandValueType !== Wizard::VALUE_TYPE_LITERAL
? $this->cellConditionCheck($this->operand)
: $this->operand
);
$conditional->setConditions([$this->expression]);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) {
throw new Exception('Conditional is not a Text Value CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
$wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
$condition = $conditional->getText();
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
$wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
$condition = self::reverseAdjustCellRef($condition, $cellRange);
} elseif (
preg_match('/\(\)/', $condition) ||
preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
) {
$wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA;
}
$wizard->operand = $condition;
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
throw new Exception('Invalid Operation for Text Value CF Rule Wizard');
}
$this->operator(self::MAGIC_OPERATIONS[$methodName]);
//$this->operand(...$arguments);
if (count($arguments) < 2) {
$this->operand($arguments[0]);
} else {
$this->operand($arguments[0], $arguments[1]);
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php 0000644 00000013344 15243417764 0025606 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Style\Style;
abstract class WizardAbstract
{
/**
* @var ?Style
*/
protected $style;
/**
* @var string
*/
protected $expression;
/**
* @var string
*/
protected $cellRange;
/**
* @var string
*/
protected $referenceCell;
/**
* @var int
*/
protected $referenceRow;
/**
* @var bool
*/
protected $stopIfTrue = false;
/**
* @var int
*/
protected $referenceColumn;
public function __construct(string $cellRange)
{
$this->setCellRange($cellRange);
}
public function getCellRange(): string
{
return $this->cellRange;
}
public function setCellRange(string $cellRange): void
{
$this->cellRange = $cellRange;
$this->setReferenceCellForExpressions($cellRange);
}
protected function setReferenceCellForExpressions(string $conditionalRange): void
{
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
[$this->referenceCell] = $conditionalRange[0];
[$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
}
public function getStopIfTrue(): bool
{
return $this->stopIfTrue;
}
public function setStopIfTrue(bool $stopIfTrue): void
{
$this->stopIfTrue = $stopIfTrue;
}
public function getStyle(): Style
{
return $this->style ?? new Style(false, true);
}
public function setStyle(Style $style): void
{
$this->style = $style;
}
protected function validateOperand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): string
{
if (
$operandValueType === Wizard::VALUE_TYPE_LITERAL &&
substr($operand, 0, 1) === '"' &&
substr($operand, -1) === '"'
) {
$operand = str_replace('""', '"', substr($operand, 1, -1));
} elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && substr($operand, 0, 1) === '=') {
$operand = substr($operand, 1);
}
return $operand;
}
protected static function reverseCellAdjustment(array $matches, int $referenceColumn, int $referenceRow): string
{
$worksheet = $matches[1];
$column = $matches[6];
$row = $matches[7];
if (strpos($column, '$') === false) {
$column = Coordinate::columnIndexFromString($column);
$column -= $referenceColumn - 1;
$column = Coordinate::stringFromColumnIndex($column);
}
if (strpos($row, '$') === false) {
$row -= $referenceRow - 1;
}
return "{$worksheet}{$column}{$row}";
}
public static function reverseAdjustCellRef(string $condition, string $cellRange): string
{
$conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange)));
[$referenceCell] = $conditionalRange[0];
[$referenceColumnIndex, $referenceRow] = Coordinate::indexesFromString($referenceCell);
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
$i = false;
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
$i = $i === false;
if ($i) {
$value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
function ($matches) use ($referenceColumnIndex, $referenceRow) {
return self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow);
},
$value
);
}
}
unset($value);
// Then rebuild the condition string to return it
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
}
protected function conditionCellAdjustment(array $matches): string
{
$worksheet = $matches[1];
$column = $matches[6];
$row = $matches[7];
if (strpos($column, '$') === false) {
$column = Coordinate::columnIndexFromString($column);
$column += $this->referenceColumn - 1;
$column = Coordinate::stringFromColumnIndex($column);
}
if (strpos($row, '$') === false) {
$row += $this->referenceRow - 1;
}
return "{$worksheet}{$column}{$row}";
}
protected function cellConditionCheck(string $condition): string
{
$splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition);
$i = false;
foreach ($splitCondition as &$value) {
// Only count/replace in alternating array entries (ie. not in quoted strings)
$i = $i === false;
if ($i) {
$value = (string) preg_replace_callback(
'/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i',
[$this, 'conditionCellAdjustment'],
$value
);
}
}
unset($value);
// Then rebuild the condition string to return it
return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition);
}
protected function adjustConditionsForCellReferences(array $conditions): array
{
return array_map(
[$this, 'cellConditionCheck'],
$conditions
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php 0000644 00000005434 15243417764 0024075 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
/**
* @method Blanks notBlank()
* @method Blanks notEmpty()
* @method Blanks isBlank()
* @method Blanks isEmpty()
*/
class Blanks extends WizardAbstract implements WizardInterface
{
protected const OPERATORS = [
'notBlank' => false,
'isBlank' => true,
'notEmpty' => false,
'empty' => true,
];
protected const EXPRESSIONS = [
Wizard::NOT_BLANKS => 'LEN(TRIM(%s))>0',
Wizard::BLANKS => 'LEN(TRIM(%s))=0',
];
/**
* @var bool
*/
protected $inverse;
public function __construct(string $cellRange, bool $inverse = false)
{
parent::__construct($cellRange);
$this->inverse = $inverse;
}
protected function inverse(bool $inverse): void
{
$this->inverse = $inverse;
}
protected function getExpression(): void
{
$this->expression = sprintf(
self::EXPRESSIONS[$this->inverse ? Wizard::BLANKS : Wizard::NOT_BLANKS],
$this->referenceCell
);
}
public function getConditional(): Conditional
{
$this->getExpression();
$conditional = new Conditional();
$conditional->setConditionType(
$this->inverse ? Conditional::CONDITION_CONTAINSBLANKS : Conditional::CONDITION_NOTCONTAINSBLANKS
);
$conditional->setConditions([$this->expression]);
$conditional->setStyle($this->getStyle());
$conditional->setStopIfTrue($this->getStopIfTrue());
return $conditional;
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
if (
$conditional->getConditionType() !== Conditional::CONDITION_CONTAINSBLANKS &&
$conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSBLANKS
) {
throw new Exception('Conditional is not a Blanks CF Rule conditional');
}
$wizard = new self($cellRange);
$wizard->style = $conditional->getStyle();
$wizard->stopIfTrue = $conditional->getStopIfTrue();
$wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSBLANKS;
return $wizard;
}
/**
* @param string $methodName
* @param mixed[] $arguments
*/
public function __call($methodName, $arguments): self
{
if (!array_key_exists($methodName, self::OPERATORS)) {
throw new Exception('Invalid Operation for Blanks CF Rule Wizard');
}
$this->inverse(self::OPERATORS[$methodName]);
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php 0000644 00000007763 15243417764 0022672 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard\WizardInterface;
class Wizard
{
public const CELL_VALUE = 'cellValue';
public const TEXT_VALUE = 'textValue';
public const BLANKS = Conditional::CONDITION_CONTAINSBLANKS;
public const NOT_BLANKS = Conditional::CONDITION_NOTCONTAINSBLANKS;
public const ERRORS = Conditional::CONDITION_CONTAINSERRORS;
public const NOT_ERRORS = Conditional::CONDITION_NOTCONTAINSERRORS;
public const EXPRESSION = Conditional::CONDITION_EXPRESSION;
public const FORMULA = Conditional::CONDITION_EXPRESSION;
public const DATES_OCCURRING = 'DateValue';
public const DUPLICATES = Conditional::CONDITION_DUPLICATES;
public const UNIQUE = Conditional::CONDITION_UNIQUE;
public const VALUE_TYPE_LITERAL = 'value';
public const VALUE_TYPE_CELL = 'cell';
public const VALUE_TYPE_FORMULA = 'formula';
/**
* @var string
*/
protected $cellRange;
public function __construct(string $cellRange)
{
$this->cellRange = $cellRange;
}
public function newRule(string $ruleType): WizardInterface
{
switch ($ruleType) {
case self::CELL_VALUE:
return new Wizard\CellValue($this->cellRange);
case self::TEXT_VALUE:
return new Wizard\TextValue($this->cellRange);
case self::BLANKS:
return new Wizard\Blanks($this->cellRange, true);
case self::NOT_BLANKS:
return new Wizard\Blanks($this->cellRange, false);
case self::ERRORS:
return new Wizard\Errors($this->cellRange, true);
case self::NOT_ERRORS:
return new Wizard\Errors($this->cellRange, false);
case self::EXPRESSION:
case self::FORMULA:
return new Wizard\Expression($this->cellRange);
case self::DATES_OCCURRING:
return new Wizard\DateValue($this->cellRange);
case self::DUPLICATES:
return new Wizard\Duplicates($this->cellRange, false);
case self::UNIQUE:
return new Wizard\Duplicates($this->cellRange, true);
default:
throw new Exception('No wizard exists for this CF rule type');
}
}
public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
{
$conditionalType = $conditional->getConditionType();
switch ($conditionalType) {
case Conditional::CONDITION_CELLIS:
return Wizard\CellValue::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_CONTAINSTEXT:
case Conditional::CONDITION_NOTCONTAINSTEXT:
case Conditional::CONDITION_BEGINSWITH:
case Conditional::CONDITION_ENDSWITH:
return Wizard\TextValue::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_CONTAINSBLANKS:
case Conditional::CONDITION_NOTCONTAINSBLANKS:
return Wizard\Blanks::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_CONTAINSERRORS:
case Conditional::CONDITION_NOTCONTAINSERRORS:
return Wizard\Errors::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_TIMEPERIOD:
return Wizard\DateValue::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_EXPRESSION:
return Wizard\Expression::fromConditional($conditional, $cellRange);
case Conditional::CONDITION_DUPLICATES:
case Conditional::CONDITION_UNIQUE:
return Wizard\Duplicates::fromConditional($conditional, $cellRange);
default:
throw new Exception('No wizard exists for this CF rule type');
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php 0000644 00000002353 15243417764 0025043 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\Style;
class CellStyleAssessor
{
/**
* @var CellMatcher
*/
protected $cellMatcher;
/**
* @var StyleMerger
*/
protected $styleMerger;
public function __construct(Cell $cell, string $conditionalRange)
{
$this->cellMatcher = new CellMatcher($cell, $conditionalRange);
$this->styleMerger = new StyleMerger($cell->getStyle());
}
/**
* @param Conditional[] $conditionalStyles
*/
public function matchConditions(array $conditionalStyles = []): Style
{
foreach ($conditionalStyles as $conditional) {
/** @var Conditional $conditional */
if ($this->cellMatcher->evaluateConditional($conditional) === true) {
// Merging the conditional style into the base style goes in here
$this->styleMerger->mergeStyle($conditional->getStyle());
if ($conditional->getStopIfTrue() === true) {
break;
}
}
}
return $this->styleMerger->getStyle();
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php 0000644 00000004375 15243417764 0025110 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
class ConditionalDataBar
{
/** <dataBar> attribute */
/** @var null|bool */
private $showValue;
/** <dataBar> children */
/** @var ?ConditionalFormatValueObject */
private $minimumConditionalFormatValueObject;
/** @var ?ConditionalFormatValueObject */
private $maximumConditionalFormatValueObject;
/** @var string */
private $color;
/** <extLst> */
/** @var ?ConditionalFormattingRuleExtension */
private $conditionalFormattingRuleExt;
/**
* @return null|bool
*/
public function getShowValue()
{
return $this->showValue;
}
/**
* @param bool $showValue
*/
public function setShowValue($showValue): self
{
$this->showValue = $showValue;
return $this;
}
public function getMinimumConditionalFormatValueObject(): ?ConditionalFormatValueObject
{
return $this->minimumConditionalFormatValueObject;
}
public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject): self
{
$this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject;
return $this;
}
public function getMaximumConditionalFormatValueObject(): ?ConditionalFormatValueObject
{
return $this->maximumConditionalFormatValueObject;
}
public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject): self
{
$this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject;
return $this;
}
public function getColor(): string
{
return $this->color;
}
public function setColor(string $color): self
{
$this->color = $color;
return $this;
}
public function getConditionalFormattingRuleExt(): ?ConditionalFormattingRuleExtension
{
return $this->conditionalFormattingRuleExt;
}
public function setConditionalFormattingRuleExt(ConditionalFormattingRuleExtension $conditionalFormattingRuleExt): self
{
$this->conditionalFormattingRuleExt = $conditionalFormattingRuleExt;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php 0000644 00000014174 15243417764 0027003 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
class ConditionalDataBarExtension
{
/** <dataBar> attributes */
/** @var int */
private $minLength;
/** @var int */
private $maxLength;
/** @var null|bool */
private $border;
/** @var null|bool */
private $gradient;
/** @var string */
private $direction;
/** @var null|bool */
private $negativeBarBorderColorSameAsPositive;
/** @var string */
private $axisPosition;
// <dataBar> children
/** @var ConditionalFormatValueObject */
private $maximumConditionalFormatValueObject;
/** @var ConditionalFormatValueObject */
private $minimumConditionalFormatValueObject;
/** @var string */
private $borderColor;
/** @var string */
private $negativeFillColor;
/** @var string */
private $negativeBorderColor;
/** @var array */
private $axisColor = [
'rgb' => null,
'theme' => null,
'tint' => null,
];
public function getXmlAttributes(): array
{
$ret = [];
foreach (['minLength', 'maxLength', 'direction', 'axisPosition'] as $attrKey) {
if (null !== $this->{$attrKey}) {
$ret[$attrKey] = $this->{$attrKey};
}
}
foreach (['border', 'gradient', 'negativeBarBorderColorSameAsPositive'] as $attrKey) {
if (null !== $this->{$attrKey}) {
$ret[$attrKey] = $this->{$attrKey} ? '1' : '0';
}
}
return $ret;
}
public function getXmlElements(): array
{
$ret = [];
$elms = ['borderColor', 'negativeFillColor', 'negativeBorderColor'];
foreach ($elms as $elmKey) {
if (null !== $this->{$elmKey}) {
$ret[$elmKey] = ['rgb' => $this->{$elmKey}];
}
}
foreach (array_filter($this->axisColor) as $attrKey => $axisColorAttr) {
if (!isset($ret['axisColor'])) {
$ret['axisColor'] = [];
}
$ret['axisColor'][$attrKey] = $axisColorAttr;
}
return $ret;
}
/**
* @return int
*/
public function getMinLength()
{
return $this->minLength;
}
public function setMinLength(int $minLength): self
{
$this->minLength = $minLength;
return $this;
}
/**
* @return int
*/
public function getMaxLength()
{
return $this->maxLength;
}
public function setMaxLength(int $maxLength): self
{
$this->maxLength = $maxLength;
return $this;
}
/**
* @return null|bool
*/
public function getBorder()
{
return $this->border;
}
public function setBorder(bool $border): self
{
$this->border = $border;
return $this;
}
/**
* @return null|bool
*/
public function getGradient()
{
return $this->gradient;
}
public function setGradient(bool $gradient): self
{
$this->gradient = $gradient;
return $this;
}
/**
* @return string
*/
public function getDirection()
{
return $this->direction;
}
public function setDirection(string $direction): self
{
$this->direction = $direction;
return $this;
}
/**
* @return null|bool
*/
public function getNegativeBarBorderColorSameAsPositive()
{
return $this->negativeBarBorderColorSameAsPositive;
}
public function setNegativeBarBorderColorSameAsPositive(bool $negativeBarBorderColorSameAsPositive): self
{
$this->negativeBarBorderColorSameAsPositive = $negativeBarBorderColorSameAsPositive;
return $this;
}
/**
* @return string
*/
public function getAxisPosition()
{
return $this->axisPosition;
}
public function setAxisPosition(string $axisPosition): self
{
$this->axisPosition = $axisPosition;
return $this;
}
/**
* @return ConditionalFormatValueObject
*/
public function getMaximumConditionalFormatValueObject()
{
return $this->maximumConditionalFormatValueObject;
}
public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject): self
{
$this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject;
return $this;
}
/**
* @return ConditionalFormatValueObject
*/
public function getMinimumConditionalFormatValueObject()
{
return $this->minimumConditionalFormatValueObject;
}
public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject): self
{
$this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject;
return $this;
}
/**
* @return string
*/
public function getBorderColor()
{
return $this->borderColor;
}
public function setBorderColor(string $borderColor): self
{
$this->borderColor = $borderColor;
return $this;
}
/**
* @return string
*/
public function getNegativeFillColor()
{
return $this->negativeFillColor;
}
public function setNegativeFillColor(string $negativeFillColor): self
{
$this->negativeFillColor = $negativeFillColor;
return $this;
}
/**
* @return string
*/
public function getNegativeBorderColor()
{
return $this->negativeBorderColor;
}
public function setNegativeBorderColor(string $negativeBorderColor): self
{
$this->negativeBorderColor = $negativeBorderColor;
return $this;
}
public function getAxisColor(): array
{
return $this->axisColor;
}
/**
* @param mixed $rgb
* @param null|mixed $theme
* @param null|mixed $tint
*/
public function setAxisColor($rgb, $theme = null, $tint = null): self
{
$this->axisColor = [
'rgb' => $rgb,
'theme' => $theme,
'tint' => $tint,
];
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php 0000644 00000002616 15243417764 0027162 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
class ConditionalFormatValueObject
{
/** @var mixed */
private $type;
/** @var mixed */
private $value;
/** @var mixed */
private $cellFormula;
/**
* ConditionalFormatValueObject constructor.
*
* @param mixed $type
* @param mixed $value
* @param null|mixed $cellFormula
*/
public function __construct($type, $value = null, $cellFormula = null)
{
$this->type = $type;
$this->value = $value;
$this->cellFormula = $cellFormula;
}
/**
* @return mixed
*/
public function getType()
{
return $this->type;
}
/**
* @param mixed $type
*/
public function setType($type): self
{
$this->type = $type;
return $this;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* @param mixed $value
*/
public function setValue($value): self
{
$this->value = $value;
return $this;
}
/**
* @return mixed
*/
public function getCellFormula()
{
return $this->cellFormula;
}
/**
* @param mixed $cellFormula
*/
public function setCellFormula($cellFormula): self
{
$this->cellFormula = $cellFormula;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php 0000644 00000011373 15243417764 0017252 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
class Protection extends Supervisor
{
/** Protection styles */
const PROTECTION_INHERIT = 'inherit';
const PROTECTION_PROTECTED = 'protected';
const PROTECTION_UNPROTECTED = 'unprotected';
/**
* Locked.
*
* @var string
*/
protected $locked;
/**
* Hidden.
*
* @var string
*/
protected $hidden;
/**
* Create a new Protection.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
if (!$isConditional) {
$this->locked = self::PROTECTION_INHERIT;
$this->hidden = self::PROTECTION_INHERIT;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Protection
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getProtection();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['protection' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getLocked()->applyFromArray(
* [
* 'locked' => TRUE,
* 'hidden' => FALSE
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['locked'])) {
$this->setLocked($styleArray['locked']);
}
if (isset($styleArray['hidden'])) {
$this->setHidden($styleArray['hidden']);
}
}
return $this;
}
/**
* Get locked.
*
* @return string
*/
public function getLocked()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getLocked();
}
return $this->locked;
}
/**
* Set locked.
*
* @param string $lockType see self::PROTECTION_*
*
* @return $this
*/
public function setLocked($lockType)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['locked' => $lockType]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->locked = $lockType;
}
return $this;
}
/**
* Get hidden.
*
* @return string
*/
public function getHidden()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHidden();
}
return $this->hidden;
}
/**
* Set hidden.
*
* @param string $hiddenType see self::PROTECTION_*
*
* @return $this
*/
public function setHidden($hiddenType)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['hidden' => $hiddenType]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->hidden = $hiddenType;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->locked .
$this->hidden .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'locked', $this->getLocked());
$this->exportArray2($exportedArray, 'hidden', $this->getHidden());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php 0000644 00000010615 15243417764 0017303 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\IComparable;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
abstract class Supervisor implements IComparable
{
/**
* Supervisor?
*
* @var bool
*/
protected $isSupervisor;
/**
* Parent. Only used for supervisor.
*
* @var Spreadsheet|Supervisor
*/
protected $parent;
/**
* Parent property name.
*
* @var null|string
*/
protected $parentPropertyName;
/**
* Create a new Supervisor.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false)
{
// Supervisor?
$this->isSupervisor = $isSupervisor;
}
/**
* Bind parent. Only used for supervisor.
*
* @param Spreadsheet|Supervisor $parent
* @param null|string $parentPropertyName
*
* @return $this
*/
public function bindParent($parent, $parentPropertyName = null)
{
$this->parent = $parent;
$this->parentPropertyName = $parentPropertyName;
return $this;
}
/**
* Is this a supervisor or a cell style component?
*
* @return bool
*/
public function getIsSupervisor()
{
return $this->isSupervisor;
}
/**
* Get the currently active sheet. Only used for supervisor.
*
* @return Worksheet
*/
public function getActiveSheet()
{
return $this->parent->getActiveSheet();
}
/**
* Get the currently active cell coordinate in currently active sheet.
* Only used for supervisor.
*
* @return string E.g. 'A1'
*/
public function getSelectedCells()
{
return $this->getActiveSheet()->getSelectedCells();
}
/**
* Get the currently active cell coordinate in currently active sheet.
* Only used for supervisor.
*
* @return string E.g. 'A1'
*/
public function getActiveCell()
{
return $this->getActiveSheet()->getActiveCell();
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if ((is_object($value)) && ($key != 'parent')) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
/**
* Export style as array.
*
* Available to anything which extends this class:
* Alignment, Border, Borders, Color, Fill, Font,
* NumberFormat, Protection, and Style.
*/
final public function exportArray(): array
{
return $this->exportArray1();
}
/**
* Abstract method to be implemented in anything which
* extends this class.
*
* This method invokes exportArray2 with the names and values
* of all properties to be included in output array,
* returning that array to exportArray, then to caller.
*/
abstract protected function exportArray1(): array;
/**
* Populate array from exportArray1.
* This method is available to anything which extends this class.
* The parameter index is the key to be added to the array.
* The parameter objOrValue is either a primitive type,
* which is the value added to the array,
* or a Style object to be recursively added via exportArray.
*
* @param mixed $objOrValue
*/
final protected function exportArray2(array &$exportedArray, string $index, $objOrValue): void
{
if ($objOrValue instanceof self) {
$exportedArray[$index] = $objOrValue->exportArray();
} else {
$exportedArray[$index] = $objOrValue;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return mixed
*/
abstract public function getSharedComponent();
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
abstract public function getStyleArray($array);
}
phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php 0000644 00000025316 15243417764 0016526 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Borders extends Supervisor
{
// Diagonal directions
const DIAGONAL_NONE = 0;
const DIAGONAL_UP = 1;
const DIAGONAL_DOWN = 2;
const DIAGONAL_BOTH = 3;
/**
* Left.
*
* @var Border
*/
protected $left;
/**
* Right.
*
* @var Border
*/
protected $right;
/**
* Top.
*
* @var Border
*/
protected $top;
/**
* Bottom.
*
* @var Border
*/
protected $bottom;
/**
* Diagonal.
*
* @var Border
*/
protected $diagonal;
/**
* DiagonalDirection.
*
* @var int
*/
protected $diagonalDirection;
/**
* All borders pseudo-border. Only applies to supervisor.
*
* @var Border
*/
protected $allBorders;
/**
* Outline pseudo-border. Only applies to supervisor.
*
* @var Border
*/
protected $outline;
/**
* Inside pseudo-border. Only applies to supervisor.
*
* @var Border
*/
protected $inside;
/**
* Vertical pseudo-border. Only applies to supervisor.
*
* @var Border
*/
protected $vertical;
/**
* Horizontal pseudo-border. Only applies to supervisor.
*
* @var Border
*/
protected $horizontal;
/**
* Create a new Borders.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, bool $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
$this->left = new Border($isSupervisor, $isConditional);
$this->right = new Border($isSupervisor, $isConditional);
$this->top = new Border($isSupervisor, $isConditional);
$this->bottom = new Border($isSupervisor, $isConditional);
$this->diagonal = new Border($isSupervisor, $isConditional);
$this->diagonalDirection = self::DIAGONAL_NONE;
// Specially for supervisor
if ($isSupervisor) {
// Initialize pseudo-borders
$this->allBorders = new Border(true, $isConditional);
$this->outline = new Border(true, $isConditional);
$this->inside = new Border(true, $isConditional);
$this->vertical = new Border(true, $isConditional);
$this->horizontal = new Border(true, $isConditional);
// bind parent if we are a supervisor
$this->left->bindParent($this, 'left');
$this->right->bindParent($this, 'right');
$this->top->bindParent($this, 'top');
$this->bottom->bindParent($this, 'bottom');
$this->diagonal->bindParent($this, 'diagonal');
$this->allBorders->bindParent($this, 'allBorders');
$this->outline->bindParent($this, 'outline');
$this->inside->bindParent($this, 'inside');
$this->vertical->bindParent($this, 'vertical');
$this->horizontal->bindParent($this, 'horizontal');
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Borders
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getBorders();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['borders' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
* [
* 'bottom' => [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ],
* 'top' => [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ]
* ]
* );
* </code>
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
* [
* 'allBorders' => [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ]
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['left'])) {
$this->getLeft()->applyFromArray($styleArray['left']);
}
if (isset($styleArray['right'])) {
$this->getRight()->applyFromArray($styleArray['right']);
}
if (isset($styleArray['top'])) {
$this->getTop()->applyFromArray($styleArray['top']);
}
if (isset($styleArray['bottom'])) {
$this->getBottom()->applyFromArray($styleArray['bottom']);
}
if (isset($styleArray['diagonal'])) {
$this->getDiagonal()->applyFromArray($styleArray['diagonal']);
}
if (isset($styleArray['diagonalDirection'])) {
$this->setDiagonalDirection($styleArray['diagonalDirection']);
}
if (isset($styleArray['allBorders'])) {
$this->getLeft()->applyFromArray($styleArray['allBorders']);
$this->getRight()->applyFromArray($styleArray['allBorders']);
$this->getTop()->applyFromArray($styleArray['allBorders']);
$this->getBottom()->applyFromArray($styleArray['allBorders']);
}
}
return $this;
}
/**
* Get Left.
*
* @return Border
*/
public function getLeft()
{
return $this->left;
}
/**
* Get Right.
*
* @return Border
*/
public function getRight()
{
return $this->right;
}
/**
* Get Top.
*
* @return Border
*/
public function getTop()
{
return $this->top;
}
/**
* Get Bottom.
*
* @return Border
*/
public function getBottom()
{
return $this->bottom;
}
/**
* Get Diagonal.
*
* @return Border
*/
public function getDiagonal()
{
return $this->diagonal;
}
/**
* Get AllBorders (pseudo-border). Only applies to supervisor.
*
* @return Border
*/
public function getAllBorders()
{
if (!$this->isSupervisor) {
throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
}
return $this->allBorders;
}
/**
* Get Outline (pseudo-border). Only applies to supervisor.
*
* @return Border
*/
public function getOutline()
{
if (!$this->isSupervisor) {
throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
}
return $this->outline;
}
/**
* Get Inside (pseudo-border). Only applies to supervisor.
*
* @return Border
*/
public function getInside()
{
if (!$this->isSupervisor) {
throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
}
return $this->inside;
}
/**
* Get Vertical (pseudo-border). Only applies to supervisor.
*
* @return Border
*/
public function getVertical()
{
if (!$this->isSupervisor) {
throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
}
return $this->vertical;
}
/**
* Get Horizontal (pseudo-border). Only applies to supervisor.
*
* @return Border
*/
public function getHorizontal()
{
if (!$this->isSupervisor) {
throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
}
return $this->horizontal;
}
/**
* Get DiagonalDirection.
*
* @return int
*/
public function getDiagonalDirection()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getDiagonalDirection();
}
return $this->diagonalDirection;
}
/**
* Set DiagonalDirection.
*
* @param int $direction see self::DIAGONAL_*
*
* @return $this
*/
public function setDiagonalDirection($direction)
{
if ($direction == '') {
$direction = self::DIAGONAL_NONE;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['diagonalDirection' => $direction]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->diagonalDirection = $direction;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashcode();
}
return md5(
$this->getLeft()->getHashCode() .
$this->getRight()->getHashCode() .
$this->getTop()->getHashCode() .
$this->getBottom()->getHashCode() .
$this->getDiagonal()->getHashCode() .
$this->getDiagonalDirection() .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'bottom', $this->getBottom());
$this->exportArray2($exportedArray, 'diagonal', $this->getDiagonal());
$this->exportArray2($exportedArray, 'diagonalDirection', $this->getDiagonalDirection());
$this->exportArray2($exportedArray, 'left', $this->getLeft());
$this->exportArray2($exportedArray, 'right', $this->getRight());
$this->exportArray2($exportedArray, 'top', $this->getTop());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php 0000644 00000017021 15243417764 0017363 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\IComparable;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar;
class Conditional implements IComparable
{
// Condition types
const CONDITION_NONE = 'none';
const CONDITION_BEGINSWITH = 'beginsWith';
const CONDITION_CELLIS = 'cellIs';
const CONDITION_CONTAINSBLANKS = 'containsBlanks';
const CONDITION_CONTAINSERRORS = 'containsErrors';
const CONDITION_CONTAINSTEXT = 'containsText';
const CONDITION_DATABAR = 'dataBar';
const CONDITION_ENDSWITH = 'endsWith';
const CONDITION_EXPRESSION = 'expression';
const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks';
const CONDITION_NOTCONTAINSERRORS = 'notContainsErrors';
const CONDITION_NOTCONTAINSTEXT = 'notContainsText';
const CONDITION_TIMEPERIOD = 'timePeriod';
const CONDITION_DUPLICATES = 'duplicateValues';
const CONDITION_UNIQUE = 'uniqueValues';
private const CONDITION_TYPES = [
self::CONDITION_BEGINSWITH,
self::CONDITION_CELLIS,
self::CONDITION_CONTAINSBLANKS,
self::CONDITION_CONTAINSERRORS,
self::CONDITION_CONTAINSTEXT,
self::CONDITION_DATABAR,
self::CONDITION_DUPLICATES,
self::CONDITION_ENDSWITH,
self::CONDITION_EXPRESSION,
self::CONDITION_NONE,
self::CONDITION_NOTCONTAINSBLANKS,
self::CONDITION_NOTCONTAINSERRORS,
self::CONDITION_NOTCONTAINSTEXT,
self::CONDITION_TIMEPERIOD,
self::CONDITION_UNIQUE,
];
// Operator types
const OPERATOR_NONE = '';
const OPERATOR_BEGINSWITH = 'beginsWith';
const OPERATOR_ENDSWITH = 'endsWith';
const OPERATOR_EQUAL = 'equal';
const OPERATOR_GREATERTHAN = 'greaterThan';
const OPERATOR_GREATERTHANOREQUAL = 'greaterThanOrEqual';
const OPERATOR_LESSTHAN = 'lessThan';
const OPERATOR_LESSTHANOREQUAL = 'lessThanOrEqual';
const OPERATOR_NOTEQUAL = 'notEqual';
const OPERATOR_CONTAINSTEXT = 'containsText';
const OPERATOR_NOTCONTAINS = 'notContains';
const OPERATOR_BETWEEN = 'between';
const OPERATOR_NOTBETWEEN = 'notBetween';
const TIMEPERIOD_TODAY = 'today';
const TIMEPERIOD_YESTERDAY = 'yesterday';
const TIMEPERIOD_TOMORROW = 'tomorrow';
const TIMEPERIOD_LAST_7_DAYS = 'last7Days';
const TIMEPERIOD_LAST_WEEK = 'lastWeek';
const TIMEPERIOD_THIS_WEEK = 'thisWeek';
const TIMEPERIOD_NEXT_WEEK = 'nextWeek';
const TIMEPERIOD_LAST_MONTH = 'lastMonth';
const TIMEPERIOD_THIS_MONTH = 'thisMonth';
const TIMEPERIOD_NEXT_MONTH = 'nextMonth';
/**
* Condition type.
*
* @var string
*/
private $conditionType = self::CONDITION_NONE;
/**
* Operator type.
*
* @var string
*/
private $operatorType = self::OPERATOR_NONE;
/**
* Text.
*
* @var string
*/
private $text;
/**
* Stop on this condition, if it matches.
*
* @var bool
*/
private $stopIfTrue = false;
/**
* Condition.
*
* @var (bool|float|int|string)[]
*/
private $condition = [];
/**
* @var ConditionalDataBar
*/
private $dataBar;
/**
* Style.
*
* @var Style
*/
private $style;
/** @var bool */
private $noFormatSet = false;
/**
* Create a new Conditional.
*/
public function __construct()
{
// Initialise values
$this->style = new Style(false, true);
}
public function getNoFormatSet(): bool
{
return $this->noFormatSet;
}
public function setNoFormatSet(bool $noFormatSet): self
{
$this->noFormatSet = $noFormatSet;
return $this;
}
/**
* Get Condition type.
*
* @return string
*/
public function getConditionType()
{
return $this->conditionType;
}
/**
* Set Condition type.
*
* @param string $type Condition type, see self::CONDITION_*
*
* @return $this
*/
public function setConditionType($type)
{
$this->conditionType = $type;
return $this;
}
/**
* Get Operator type.
*
* @return string
*/
public function getOperatorType()
{
return $this->operatorType;
}
/**
* Set Operator type.
*
* @param string $type Conditional operator type, see self::OPERATOR_*
*
* @return $this
*/
public function setOperatorType($type)
{
$this->operatorType = $type;
return $this;
}
/**
* Get text.
*
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* Set text.
*
* @param string $text
*
* @return $this
*/
public function setText($text)
{
$this->text = $text;
return $this;
}
/**
* Get StopIfTrue.
*
* @return bool
*/
public function getStopIfTrue()
{
return $this->stopIfTrue;
}
/**
* Set StopIfTrue.
*
* @param bool $stopIfTrue
*
* @return $this
*/
public function setStopIfTrue($stopIfTrue)
{
$this->stopIfTrue = $stopIfTrue;
return $this;
}
/**
* Get Conditions.
*
* @return (bool|float|int|string)[]
*/
public function getConditions()
{
return $this->condition;
}
/**
* Set Conditions.
*
* @param (bool|float|int|string)[]|bool|float|int|string $conditions Condition
*
* @return $this
*/
public function setConditions($conditions)
{
if (!is_array($conditions)) {
$conditions = [$conditions];
}
$this->condition = $conditions;
return $this;
}
/**
* Add Condition.
*
* @param bool|float|int|string $condition Condition
*
* @return $this
*/
public function addCondition($condition)
{
$this->condition[] = $condition;
return $this;
}
/**
* Get Style.
*
* @return Style
*/
public function getStyle()
{
return $this->style;
}
/**
* Set Style.
*
* @return $this
*/
public function setStyle(Style $style)
{
$this->style = $style;
return $this;
}
/**
* get DataBar.
*
* @return null|ConditionalDataBar
*/
public function getDataBar()
{
return $this->dataBar;
}
/**
* set DataBar.
*
* @return $this
*/
public function setDataBar(ConditionalDataBar $dataBar)
{
$this->dataBar = $dataBar;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->conditionType .
$this->operatorType .
implode(';', $this->condition) .
$this->style->getHashCode() .
__CLASS__
);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
/**
* Verify if param is valid condition type.
*/
public static function isValidConditionType(string $type): bool
{
return in_array($type, self::CONDITION_TYPES);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Font.php 0000644 00000055337 15243417764 0016042 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
class Font extends Supervisor
{
// Underline types
const UNDERLINE_NONE = 'none';
const UNDERLINE_DOUBLE = 'double';
const UNDERLINE_DOUBLEACCOUNTING = 'doubleAccounting';
const UNDERLINE_SINGLE = 'single';
const UNDERLINE_SINGLEACCOUNTING = 'singleAccounting';
/**
* Font Name.
*
* @var null|string
*/
protected $name = 'Calibri';
/**
* The following 7 are used only for chart titles, I think.
*
*@var string
*/
private $latin = '';
/** @var string */
private $eastAsian = '';
/** @var string */
private $complexScript = '';
/** @var int */
private $baseLine = 0;
/** @var string */
private $strikeType = '';
/** @var ?ChartColor */
private $underlineColor;
/** @var ?ChartColor */
private $chartColor;
// end of chart title items
/**
* Font Size.
*
* @var null|float
*/
protected $size = 11;
/**
* Bold.
*
* @var null|bool
*/
protected $bold = false;
/**
* Italic.
*
* @var null|bool
*/
protected $italic = false;
/**
* Superscript.
*
* @var null|bool
*/
protected $superscript = false;
/**
* Subscript.
*
* @var null|bool
*/
protected $subscript = false;
/**
* Underline.
*
* @var null|string
*/
protected $underline = self::UNDERLINE_NONE;
/**
* Strikethrough.
*
* @var null|bool
*/
protected $strikethrough = false;
/**
* Foreground color.
*
* @var Color
*/
protected $color;
/**
* @var null|int
*/
public $colorIndex;
/** @var string */
protected $scheme = '';
/**
* Create a new Font.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
if ($isConditional) {
$this->name = null;
$this->size = null;
$this->bold = null;
$this->italic = null;
$this->superscript = null;
$this->subscript = null;
$this->underline = null;
$this->strikethrough = null;
$this->color = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
} else {
$this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
}
// bind parent if we are a supervisor
if ($isSupervisor) {
$this->color->bindParent($this, 'color');
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Font
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getFont();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['font' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->applyFromArray(
* [
* 'name' => 'Arial',
* 'bold' => TRUE,
* 'italic' => FALSE,
* 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE,
* 'strikethrough' => FALSE,
* 'color' => [
* 'rgb' => '808080'
* ]
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['name'])) {
$this->setName($styleArray['name']);
}
if (isset($styleArray['latin'])) {
$this->setLatin($styleArray['latin']);
}
if (isset($styleArray['eastAsian'])) {
$this->setEastAsian($styleArray['eastAsian']);
}
if (isset($styleArray['complexScript'])) {
$this->setComplexScript($styleArray['complexScript']);
}
if (isset($styleArray['bold'])) {
$this->setBold($styleArray['bold']);
}
if (isset($styleArray['italic'])) {
$this->setItalic($styleArray['italic']);
}
if (isset($styleArray['superscript'])) {
$this->setSuperscript($styleArray['superscript']);
}
if (isset($styleArray['subscript'])) {
$this->setSubscript($styleArray['subscript']);
}
if (isset($styleArray['underline'])) {
$this->setUnderline($styleArray['underline']);
}
if (isset($styleArray['strikethrough'])) {
$this->setStrikethrough($styleArray['strikethrough']);
}
if (isset($styleArray['color'])) {
$this->getColor()->applyFromArray($styleArray['color']);
}
if (isset($styleArray['size'])) {
$this->setSize($styleArray['size']);
}
if (isset($styleArray['chartColor'])) {
$this->chartColor = $styleArray['chartColor'];
}
if (isset($styleArray['scheme'])) {
$this->setScheme($styleArray['scheme']);
}
}
return $this;
}
/**
* Get Name.
*
* @return null|string
*/
public function getName()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getName();
}
return $this->name;
}
public function getLatin(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getLatin();
}
return $this->latin;
}
public function getEastAsian(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getEastAsian();
}
return $this->eastAsian;
}
public function getComplexScript(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getComplexScript();
}
return $this->complexScript;
}
/**
* Set Name and turn off Scheme.
*
* @param string $fontname
*/
public function setName($fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['name' => $fontname]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->name = $fontname;
}
return $this->setScheme('');
}
public function setLatin(string $fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
}
if (!$this->isSupervisor) {
$this->latin = $fontname;
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['latin' => $fontname]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function setEastAsian(string $fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
}
if (!$this->isSupervisor) {
$this->eastAsian = $fontname;
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['eastAsian' => $fontname]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function setComplexScript(string $fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
}
if (!$this->isSupervisor) {
$this->complexScript = $fontname;
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['complexScript' => $fontname]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
/**
* Get Size.
*
* @return null|float
*/
public function getSize()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getSize();
}
return $this->size;
}
/**
* Set Size.
*
* @param mixed $sizeInPoints A float representing the value of a positive measurement in points (1/72 of an inch)
*
* @return $this
*/
public function setSize($sizeInPoints, bool $nullOk = false)
{
if (is_string($sizeInPoints) || is_int($sizeInPoints)) {
$sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric
}
// Size must be a positive floating point number
// ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536
if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) {
if (!$nullOk || $sizeInPoints !== null) {
$sizeInPoints = 10.0;
}
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['size' => $sizeInPoints]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->size = $sizeInPoints;
}
return $this;
}
/**
* Get Bold.
*
* @return null|bool
*/
public function getBold()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getBold();
}
return $this->bold;
}
/**
* Set Bold.
*
* @param bool $bold
*
* @return $this
*/
public function setBold($bold)
{
if ($bold == '') {
$bold = false;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['bold' => $bold]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->bold = $bold;
}
return $this;
}
/**
* Get Italic.
*
* @return null|bool
*/
public function getItalic()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getItalic();
}
return $this->italic;
}
/**
* Set Italic.
*
* @param bool $italic
*
* @return $this
*/
public function setItalic($italic)
{
if ($italic == '') {
$italic = false;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['italic' => $italic]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->italic = $italic;
}
return $this;
}
/**
* Get Superscript.
*
* @return null|bool
*/
public function getSuperscript()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getSuperscript();
}
return $this->superscript;
}
/**
* Set Superscript.
*
* @return $this
*/
public function setSuperscript(bool $superscript)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['superscript' => $superscript]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->superscript = $superscript;
if ($this->superscript) {
$this->subscript = false;
}
}
return $this;
}
/**
* Get Subscript.
*
* @return null|bool
*/
public function getSubscript()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getSubscript();
}
return $this->subscript;
}
/**
* Set Subscript.
*
* @return $this
*/
public function setSubscript(bool $subscript)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['subscript' => $subscript]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->subscript = $subscript;
if ($this->subscript) {
$this->superscript = false;
}
}
return $this;
}
public function getBaseLine(): int
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getBaseLine();
}
return $this->baseLine;
}
public function setBaseLine(int $baseLine): self
{
if (!$this->isSupervisor) {
$this->baseLine = $baseLine;
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['baseLine' => $baseLine]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function getStrikeType(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getStrikeType();
}
return $this->strikeType;
}
public function setStrikeType(string $strikeType): self
{
if (!$this->isSupervisor) {
$this->strikeType = $strikeType;
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['strikeType' => $strikeType]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function getUnderlineColor(): ?ChartColor
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getUnderlineColor();
}
return $this->underlineColor;
}
public function setUnderlineColor(array $colorArray): self
{
if (!$this->isSupervisor) {
$this->underlineColor = new ChartColor($colorArray);
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['underlineColor' => $colorArray]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function getChartColor(): ?ChartColor
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getChartColor();
}
return $this->chartColor;
}
public function setChartColor(array $colorArray): self
{
if (!$this->isSupervisor) {
$this->chartColor = new ChartColor($colorArray);
} else {
// should never be true
// @codeCoverageIgnoreStart
$styleArray = $this->getStyleArray(['chartColor' => $colorArray]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
return $this;
}
public function setChartColorFromObject(?ChartColor $chartColor): self
{
$this->chartColor = $chartColor;
return $this;
}
/**
* Get Underline.
*
* @return null|string
*/
public function getUnderline()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getUnderline();
}
return $this->underline;
}
/**
* Set Underline.
*
* @param bool|string $underlineStyle \PhpOffice\PhpSpreadsheet\Style\Font underline type
* If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE,
* false equates to UNDERLINE_NONE
*
* @return $this
*/
public function setUnderline($underlineStyle)
{
if (is_bool($underlineStyle)) {
$underlineStyle = ($underlineStyle) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE;
} elseif ($underlineStyle == '') {
$underlineStyle = self::UNDERLINE_NONE;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['underline' => $underlineStyle]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->underline = $underlineStyle;
}
return $this;
}
/**
* Get Strikethrough.
*
* @return null|bool
*/
public function getStrikethrough()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getStrikethrough();
}
return $this->strikethrough;
}
/**
* Set Strikethrough.
*
* @param bool $strikethru
*
* @return $this
*/
public function setStrikethrough($strikethru)
{
if ($strikethru == '') {
$strikethru = false;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['strikethrough' => $strikethru]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->strikethrough = $strikethru;
}
return $this;
}
/**
* Get Color.
*
* @return Color
*/
public function getColor()
{
return $this->color;
}
/**
* Set Color.
*
* @return $this
*/
public function setColor(Color $color)
{
// make sure parameter is a real color and not a supervisor
$color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
if ($this->isSupervisor) {
$styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->color = $color;
}
return $this;
}
private function hashChartColor(?ChartColor $underlineColor): string
{
if ($underlineColor === null) {
return '';
}
return
$underlineColor->getValue()
. $underlineColor->getType()
. (string) $underlineColor->getAlpha();
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->name .
$this->size .
($this->bold ? 't' : 'f') .
($this->italic ? 't' : 'f') .
($this->superscript ? 't' : 'f') .
($this->subscript ? 't' : 'f') .
$this->underline .
($this->strikethrough ? 't' : 'f') .
$this->color->getHashCode() .
$this->scheme .
implode(
'*',
[
$this->latin,
$this->eastAsian,
$this->complexScript,
$this->strikeType,
$this->hashChartColor($this->chartColor),
$this->hashChartColor($this->underlineColor),
(string) $this->baseLine,
]
) .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine());
$this->exportArray2($exportedArray, 'bold', $this->getBold());
$this->exportArray2($exportedArray, 'chartColor', $this->getChartColor());
$this->exportArray2($exportedArray, 'color', $this->getColor());
$this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript());
$this->exportArray2($exportedArray, 'eastAsian', $this->getEastAsian());
$this->exportArray2($exportedArray, 'italic', $this->getItalic());
$this->exportArray2($exportedArray, 'latin', $this->getLatin());
$this->exportArray2($exportedArray, 'name', $this->getName());
$this->exportArray2($exportedArray, 'scheme', $this->getScheme());
$this->exportArray2($exportedArray, 'size', $this->getSize());
$this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
$this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
$this->exportArray2($exportedArray, 'subscript', $this->getSubscript());
$this->exportArray2($exportedArray, 'superscript', $this->getSuperscript());
$this->exportArray2($exportedArray, 'underline', $this->getUnderline());
$this->exportArray2($exportedArray, 'underlineColor', $this->getUnderlineColor());
return $exportedArray;
}
public function getScheme(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getScheme();
}
return $this->scheme;
}
public function setScheme(string $scheme): self
{
if ($scheme === '' || $scheme === 'major' || $scheme === 'minor') {
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['scheme' => $scheme]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->scheme = $scheme;
}
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php 0000644 00000027464 15243417764 0022651 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class NumberFormatter
{
private const NUMBER_REGEX = '/(0+)(\\.?)(0*)/';
private static function mergeComplexNumberFormatMasks(array $numbers, array $masks): array
{
$decimalCount = strlen($numbers[1]);
$postDecimalMasks = [];
do {
$tempMask = array_pop($masks);
if ($tempMask !== null) {
$postDecimalMasks[] = $tempMask;
$decimalCount -= strlen($tempMask);
}
} while ($tempMask !== null && $decimalCount > 0);
return [
implode('.', $masks),
implode('.', array_reverse($postDecimalMasks)),
];
}
/**
* @param mixed $number
*/
private static function processComplexNumberFormatMask($number, string $mask): string
{
/** @var string */
$result = $number;
$maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
if ($maskingBlockCount > 1) {
$maskingBlocks = array_reverse($maskingBlocks[0]);
$offset = 0;
foreach ($maskingBlocks as $block) {
$size = strlen($block[0]);
$divisor = 10 ** $size;
$offset = $block[1];
/** @var float */
$numberFloat = $number;
$blockValue = sprintf("%0{$size}d", fmod($numberFloat, $divisor));
$number = floor($numberFloat / $divisor);
$mask = substr_replace($mask, $blockValue, $offset, $size);
}
/** @var string */
$numberString = $number;
if ($number > 0) {
$mask = substr_replace($mask, $numberString, $offset, 0);
}
$result = $mask;
}
return self::makeString($result);
}
/**
* @param mixed $number
*/
private static function complexNumberFormatMask($number, string $mask, bool $splitOnPoint = true): string
{
/** @var float */
$numberFloat = $number;
if ($splitOnPoint) {
$masks = explode('.', $mask);
if (count($masks) <= 2) {
$decmask = $masks[1] ?? '';
$decpos = substr_count($decmask, '0');
$numberFloat = round($numberFloat, $decpos);
}
}
$sign = ($numberFloat < 0.0) ? '-' : '';
$number = self::f2s(abs($numberFloat));
if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
$numbers = explode('.', $number);
$masks = explode('.', $mask);
if (count($masks) > 2) {
$masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
}
$integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false);
$numlen = strlen($numbers[1]);
$msklen = strlen($masks[1]);
if ($numlen < $msklen) {
$numbers[1] .= str_repeat('0', $msklen - $numlen);
}
$decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
$decimalPart = substr($decimalPart, 0, $msklen);
return "{$sign}{$integerPart}.{$decimalPart}";
}
if (strlen($number) < strlen($mask)) {
$number = str_repeat('0', strlen($mask) - strlen($number)) . $number;
}
$result = self::processComplexNumberFormatMask($number, $mask);
return "{$sign}{$result}";
}
public static function f2s(float $f): string
{
return self::floatStringConvertScientific((string) $f);
}
public static function floatStringConvertScientific(string $s): string
{
// convert only normalized form of scientific notation:
// optional sign, single digit 1-9,
// decimal point and digits (allowed to be omitted),
// E (e permitted), optional sign, one or more digits
if (preg_match('/^([+-])?([1-9])([.]([0-9]+))?[eE]([+-]?[0-9]+)$/', $s, $matches) === 1) {
$exponent = (int) $matches[5];
$sign = ($matches[1] === '-') ? '-' : '';
if ($exponent >= 0) {
$exponentPlus1 = $exponent + 1;
$out = $matches[2] . $matches[4];
$len = strlen($out);
if ($len < $exponentPlus1) {
$out .= str_repeat('0', $exponentPlus1 - $len);
}
$out = substr($out, 0, $exponentPlus1) . ((strlen($out) === $exponentPlus1) ? '' : ('.' . substr($out, $exponentPlus1)));
$s = "$sign$out";
} else {
$s = $sign . '0.' . str_repeat('0', -$exponent - 1) . $matches[2] . $matches[4];
}
}
return $s;
}
/**
* @param mixed $value
*/
private static function formatStraightNumericValue($value, string $format, array $matches, bool $useThousands): string
{
/** @var float */
$valueFloat = $value;
$left = $matches[1];
$dec = $matches[2];
$right = $matches[3];
// minimun width of formatted number (including dot)
$minWidth = strlen($left) + strlen($dec) + strlen($right);
if ($useThousands) {
$value = number_format(
$valueFloat,
strlen($right),
StringHelper::getDecimalSeparator(),
StringHelper::getThousandsSeparator()
);
return self::pregReplace(self::NUMBER_REGEX, $value, $format);
}
if (preg_match('/[0#]E[+-]0/i', $format)) {
// Scientific format
$decimals = strlen($right);
$size = $decimals + 3;
return sprintf("%{$size}.{$decimals}E", $valueFloat);
} elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
if ($valueFloat == floor($valueFloat) && substr_count($format, '.') === 1) {
$value *= 10 ** strlen(explode('.', $format)[1]);
}
$result = self::complexNumberFormatMask($value, $format);
if (strpos($result, 'E') !== false) {
// This is a hack and doesn't match Excel.
// It will, at least, be an accurate representation,
// even if formatted incorrectly.
// This is needed for absolute values >=1E18.
$result = self::f2s($valueFloat);
}
return $result;
}
$sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
/** @var float */
$valueFloat = $value;
$value = sprintf($sprintf_pattern, round($valueFloat, strlen($right)));
return self::pregReplace(self::NUMBER_REGEX, $value, $format);
}
/**
* @param mixed $value
*/
public static function format($value, string $format): string
{
// The "_" in this string has already been stripped out,
// so this test is never true. Furthermore, testing
// on Excel shows this format uses Euro symbol, not "EUR".
// if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) {
// return 'EUR ' . sprintf('%1.2f', $value);
// }
$baseFormat = $format;
$useThousands = self::areThousandsRequired($format);
$scale = self::scaleThousandsMillions($format);
if (preg_match('/[#\?0]?.*[#\?0]\/(\?+|\d+|#)/', $format)) {
// It's a dirty hack; but replace # and 0 digit placeholders with ?
$format = (string) preg_replace('/[#0]+\//', '?/', $format);
$format = (string) preg_replace('/\/[#0]+/', '/?', $format);
$value = FractionFormatter::format($value, $format);
} else {
// Handle the number itself
// scale number
$value = $value / $scale;
$paddingPlaceholder = (strpos($format, '?') !== false);
// Replace # or ? with 0
$format = self::pregReplace('/[\\#\?](?=(?:[^"]*"[^"]*")*[^"]*\Z)/', '0', $format);
// Remove locale code [$-###] for an LCID
$format = self::pregReplace('/\[\$\-.*\]/', '', $format);
$n = '/\\[[^\\]]+\\]/';
$m = self::pregReplace($n, '', $format);
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
$format = self::makeString(str_replace(['"', '*'], '', $format));
if (preg_match(self::NUMBER_REGEX, $m, $matches)) {
// There are placeholders for digits, so inject digits from the value into the mask
$value = self::formatStraightNumericValue($value, $format, $matches, $useThousands);
if ($paddingPlaceholder === true) {
$value = self::padValue($value, $baseFormat);
}
} elseif ($format !== NumberFormat::FORMAT_GENERAL) {
// Yes, I know that this is basically just a hack;
// if there's no placeholders for digits, just return the format mask "as is"
$value = self::makeString(str_replace('?', '', $format));
}
}
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
// Currency or Accounting
$currencyCode = $m[1];
[$currencyCode] = explode('-', $currencyCode);
if ($currencyCode == '') {
$currencyCode = StringHelper::getCurrencyCode();
}
$value = self::pregReplace('/\[\$([^\]]*)\]/u', $currencyCode, (string) $value);
}
if (
(strpos((string) $value, '0.') !== false) &&
((strpos($baseFormat, '#.') !== false) || (strpos($baseFormat, '?.') !== false))
) {
$value = preg_replace('/(\b)0\.|([^\d])0\./', '${2}.', (string) $value);
}
return (string) $value;
}
/**
* @param array|string $value
*/
private static function makeString($value): string
{
return is_array($value) ? '' : "$value";
}
private static function pregReplace(string $pattern, string $replacement, string $subject): string
{
return self::makeString(preg_replace($pattern, $replacement, $subject) ?? '');
}
public static function padValue(string $value, string $baseFormat): string
{
/** @phpstan-ignore-next-line */
[$preDecimal, $postDecimal] = preg_split('/\.(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu', $baseFormat . '.?');
$length = strlen($value);
if (strpos($postDecimal, '?') !== false) {
$value = str_pad(rtrim($value, '0. '), $length, ' ', STR_PAD_RIGHT);
}
if (strpos($preDecimal, '?') !== false) {
$value = str_pad(ltrim($value, '0, '), $length, ' ', STR_PAD_LEFT);
}
return $value;
}
/**
* Find out if we need thousands separator
* This is indicated by a comma enclosed by a digit placeholders: #, 0 or ?
*/
public static function areThousandsRequired(string &$format): bool
{
$useThousands = (bool) preg_match('/([#\?0]),([#\?0])/', $format);
if ($useThousands) {
$format = self::pregReplace('/([#\?0]),([#\?0])/', '${1}${2}', $format);
}
return $useThousands;
}
/**
* Scale thousands, millions,...
* This is indicated by a number of commas after a digit placeholder: #, or 0.0,, or ?,.
*/
public static function scaleThousandsMillions(string &$format): int
{
$scale = 1; // same as no scale
if (preg_match('/(#|0|\?)(,+)/', $format, $matches)) {
$scale = 1000 ** strlen($matches[2]);
// strip the commas
$format = self::pregReplace('/([#\?0]),+/', '${1}', $format);
}
return $scale;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php 0000644 00000002656 15243417764 0022465 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
class DateTime extends DateTimeWizard
{
/**
* @var string[]
*/
protected array $separators;
/**
* @var array<DateTimeWizard|string>
*/
protected array $formatBlocks;
/**
* @param null|string|string[] $separators
* If you want to use only a single format block, then pass a null as the separator argument
* @param DateTimeWizard|string ...$formatBlocks
*/
public function __construct($separators, ...$formatBlocks)
{
$this->separators = $this->padSeparatorArray(
is_array($separators) ? $separators : [$separators],
count($formatBlocks) - 1
);
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
}
/**
* @param DateTimeWizard|string $value
*/
private function mapFormatBlocks($value): string
{
// Any date masking codes are returned as lower case values
if (is_object($value)) {
// We can't explicitly test for Stringable until PHP >= 8.0
return $value;
}
// Wrap any string literals in quotes, so that they're clearly defined as string literals
return $this->wrapLiteral($value);
}
public function format(): string
{
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php 0000644 00000010442 15243417764 0022553 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
class Currency extends Number
{
public const LEADING_SYMBOL = true;
public const TRAILING_SYMBOL = false;
public const SYMBOL_WITH_SPACING = true;
public const SYMBOL_WITHOUT_SPACING = false;
protected string $currencyCode = '$';
protected bool $currencySymbolPosition = self::LEADING_SYMBOL;
protected bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING;
/**
* @param string $currencyCode the currency symbol or code to display for this mask
* @param int $decimals number of decimal places to display, in the range 0-30
* @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
* @param bool $currencySymbolPosition indicates whether the currency symbol comes before or after the value
* Possible values are Currency::LEADING_SYMBOL and Currency::TRAILING_SYMBOL
* @param bool $currencySymbolSpacing indicates whether there is spacing between the currency symbol and the value
* Possible values are Currency::SYMBOL_WITH_SPACING and Currency::SYMBOL_WITHOUT_SPACING
* @param ?string $locale Set the locale for the currency format; or leave as the default null.
* If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
* Note that setting a locale will override any other settings defined in this class
* other than the currency code; or decimals (unless the decimals value is set to 0).
*
* @throws Exception If a provided locale code is not a valid format
*/
public function __construct(
string $currencyCode = '$',
int $decimals = 2,
bool $thousandsSeparator = true,
bool $currencySymbolPosition = self::LEADING_SYMBOL,
bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING,
?string $locale = null
) {
$this->setCurrencyCode($currencyCode);
$this->setThousandsSeparator($thousandsSeparator);
$this->setDecimals($decimals);
$this->setCurrencySymbolPosition($currencySymbolPosition);
$this->setCurrencySymbolSpacing($currencySymbolSpacing);
$this->setLocale($locale);
}
public function setCurrencyCode(string $currencyCode): void
{
$this->currencyCode = $currencyCode;
}
public function setCurrencySymbolPosition(bool $currencySymbolPosition = self::LEADING_SYMBOL): void
{
$this->currencySymbolPosition = $currencySymbolPosition;
}
public function setCurrencySymbolSpacing(bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING): void
{
$this->currencySymbolSpacing = $currencySymbolSpacing;
}
protected function getLocaleFormat(): string
{
$formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY);
$mask = $formatter->format();
if ($this->decimals === 0) {
$mask = (string) preg_replace('/\.0+/miu', '', $mask);
}
return str_replace('¤', $this->formatCurrencyCode(), $mask);
}
private function formatCurrencyCode(): string
{
if ($this->locale === null) {
return $this->currencyCode;
}
return "[\${$this->currencyCode}-{$this->locale}]";
}
public function format(): string
{
if ($this->localeFormat !== null) {
return $this->localeFormat;
}
return sprintf(
'%s%s%s0%s%s%s',
$this->currencySymbolPosition === self::LEADING_SYMBOL ? $this->formatCurrencyCode() : null,
(
$this->currencySymbolPosition === self::LEADING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
) ? "\u{a0}" : '',
$this->thousandsSeparator ? '#,##' : null,
$this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
(
$this->currencySymbolPosition === self::TRAILING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
) ? "\u{a0}" : '',
$this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php 0000644 00000002243 15243417764 0023636 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
abstract class DateTimeWizard implements Wizard
{
protected const NO_ESCAPING_NEEDED = "$+-/():!^&'~{}<>= ";
protected function padSeparatorArray(array $separators, int $count): array
{
$lastSeparator = array_pop($separators);
return $separators + array_fill(0, $count, $lastSeparator);
}
protected function escapeSingleCharacter(string $value): string
{
if (strpos(self::NO_ESCAPING_NEEDED, $value) !== false) {
return $value;
}
return "\\{$value}";
}
protected function wrapLiteral(string $value): string
{
if (mb_strlen($value, 'UTF-8') === 1) {
return $this->escapeSingleCharacter($value);
}
// Wrap any other string literals in quotes, so that they're clearly defined as string literals
return '"' . str_replace('"', '""', $value) . '"';
}
protected function intersperse(string $formatBlock, ?string $separator): string
{
return "{$formatBlock}{$separator}";
}
public function __toString(): string
{
return $this->format();
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php 0000644 00000006704 15243417764 0021644 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
class Date extends DateTimeWizard
{
/**
* Year (4 digits), e.g. 2023.
*/
public const YEAR_FULL = 'yyyy';
/**
* Year (last 2 digits), e.g. 23.
*/
public const YEAR_SHORT = 'yy';
public const MONTH_FIRST_LETTER = 'mmmmm';
/**
* Month name, long form, e.g. January.
*/
public const MONTH_NAME_FULL = 'mmmm';
/**
* Month name, short form, e.g. Jan.
*/
public const MONTH_NAME_SHORT = 'mmm';
/**
* Month number with a leading zero if required, e.g. 01.
*/
public const MONTH_NUMBER_LONG = 'mm';
/**
* Month number without a leading zero, e.g. 1.
*/
public const MONTH_NUMBER_SHORT = 'm';
/**
* Day of the week, full form, e.g. Tuesday.
*/
public const WEEKDAY_NAME_LONG = 'dddd';
/**
* Day of the week, short form, e.g. Tue.
*/
public const WEEKDAY_NAME_SHORT = 'ddd';
/**
* Day number with a leading zero, e.g. 03.
*/
public const DAY_NUMBER_LONG = 'dd';
/**
* Day number without a leading zero, e.g. 3.
*/
public const DAY_NUMBER_SHORT = 'd';
protected const DATE_BLOCKS = [
self::YEAR_FULL,
self::YEAR_SHORT,
self::MONTH_FIRST_LETTER,
self::MONTH_NAME_FULL,
self::MONTH_NAME_SHORT,
self::MONTH_NUMBER_LONG,
self::MONTH_NUMBER_SHORT,
self::WEEKDAY_NAME_LONG,
self::WEEKDAY_NAME_SHORT,
self::DAY_NUMBER_LONG,
self::DAY_NUMBER_SHORT,
];
public const SEPARATOR_DASH = '-';
public const SEPARATOR_DOT = '.';
public const SEPARATOR_SLASH = '/';
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
public const SEPARATOR_SPACE = ' ';
protected const DATE_DEFAULT = [
self::YEAR_FULL,
self::MONTH_NUMBER_LONG,
self::DAY_NUMBER_LONG,
];
/**
* @var string[]
*/
protected array $separators;
/**
* @var string[]
*/
protected array $formatBlocks;
/**
* @param null|string|string[] $separators
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
* if you wish to use different separators, then they should be passed as an array.
* If you want to use only a single format block, then pass a null as the separator argument
*/
public function __construct($separators = self::SEPARATOR_DASH, string ...$formatBlocks)
{
$separators ??= self::SEPARATOR_DASH;
$formatBlocks = (count($formatBlocks) === 0) ? self::DATE_DEFAULT : $formatBlocks;
$this->separators = $this->padSeparatorArray(
is_array($separators) ? $separators : [$separators],
count($formatBlocks) - 1
);
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
}
private function mapFormatBlocks(string $value): string
{
// Any date masking codes are returned as lower case values
if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) {
return mb_strtolower($value);
}
// Wrap any string literals in quotes, so that they're clearly defined as string literals
return $this->wrapLiteral($value);
}
public function format(): string
{
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php 0000644 00000004413 15243417764 0023005 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
abstract class NumberBase
{
protected const MAX_DECIMALS = 30;
protected int $decimals = 2;
protected ?string $locale = null;
protected ?string $fullLocale = null;
protected ?string $localeFormat = null;
public function setDecimals(int $decimals = 2): void
{
$this->decimals = ($decimals > self::MAX_DECIMALS) ? self::MAX_DECIMALS : max($decimals, 0);
}
/**
* Setting a locale will override any settings defined in this class.
*
* @throws Exception If the locale code is not a valid format
*/
public function setLocale(?string $locale = null): void
{
if ($locale === null) {
$this->localeFormat = $this->locale = $this->fullLocale = null;
return;
}
$this->locale = $this->validateLocale($locale);
if (class_exists(NumberFormatter::class)) {
$this->localeFormat = $this->getLocaleFormat();
}
}
/**
* Stub: should be implemented as a concrete method in concrete wizards.
*/
abstract protected function getLocaleFormat(): string;
/**
* @throws Exception If the locale code is not a valid format
*/
private function validateLocale(string $locale): string
{
if (preg_match(Locale::STRUCTURE, $locale, $matches, PREG_UNMATCHED_AS_NULL) !== 1) {
throw new Exception("Invalid locale code '{$locale}'");
}
['language' => $language, 'script' => $script, 'country' => $country] = $matches;
// Set case and separator to match standardised locale case
$language = strtolower($language ?? '');
$script = ($script === null) ? null : ucfirst(strtolower($script));
$country = ($country === null) ? null : strtoupper($country);
$this->fullLocale = implode('-', array_filter([$language, $script, $country]));
return $country === null ? $language : "{$language}-{$country}";
}
public function format(): string
{
return NumberFormat::FORMAT_GENERAL;
}
public function __toString(): string
{
return $this->format();
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php 0000644 00000003637 15243417764 0022221 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
class Number extends NumberBase implements Wizard
{
public const WITH_THOUSANDS_SEPARATOR = true;
public const WITHOUT_THOUSANDS_SEPARATOR = false;
protected bool $thousandsSeparator = true;
/**
* @param int $decimals number of decimal places to display, in the range 0-30
* @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
* @param ?string $locale Set the locale for the number format; or leave as the default null.
* Locale has no effect for Number Format values, and is retained here only for compatibility
* with the other Wizards.
* If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
*
* @throws Exception If a provided locale code is not a valid format
*/
public function __construct(
int $decimals = 2,
bool $thousandsSeparator = self::WITH_THOUSANDS_SEPARATOR,
?string $locale = null
) {
$this->setDecimals($decimals);
$this->setThousandsSeparator($thousandsSeparator);
$this->setLocale($locale);
}
public function setThousandsSeparator(bool $thousandsSeparator = self::WITH_THOUSANDS_SEPARATOR): void
{
$this->thousandsSeparator = $thousandsSeparator;
}
/**
* As MS Excel cannot easily handle Lakh, which is the only locale-specific Number format variant,
* we don't use locale with Numbers.
*/
protected function getLocaleFormat(): string
{
return $this->format();
}
public function format(): string
{
return sprintf(
'%s0%s',
$this->thousandsSeparator ? '#,##' : null,
$this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php 0000644 00000011177 15243417764 0022554 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
class Duration extends DateTimeWizard
{
public const DAYS_DURATION = 'd';
/**
* Hours as a duration (can exceed 24), e.g. 29.
*/
public const HOURS_DURATION = '[h]';
/**
* Hours without a leading zero, e.g. 9.
*/
public const HOURS_SHORT = 'h';
/**
* Hours with a leading zero, e.g. 09.
*/
public const HOURS_LONG = 'hh';
/**
* Minutes as a duration (can exceed 60), e.g. 109.
*/
public const MINUTES_DURATION = '[m]';
/**
* Minutes without a leading zero, e.g. 5.
*/
public const MINUTES_SHORT = 'm';
/**
* Minutes with a leading zero, e.g. 05.
*/
public const MINUTES_LONG = 'mm';
/**
* Seconds as a duration (can exceed 60), e.g. 129.
*/
public const SECONDS_DURATION = '[s]';
/**
* Seconds without a leading zero, e.g. 2.
*/
public const SECONDS_SHORT = 's';
/**
* Seconds with a leading zero, e.g. 02.
*/
public const SECONDS_LONG = 'ss';
protected const DURATION_BLOCKS = [
self::DAYS_DURATION,
self::HOURS_DURATION,
self::HOURS_LONG,
self::HOURS_SHORT,
self::MINUTES_DURATION,
self::MINUTES_LONG,
self::MINUTES_SHORT,
self::SECONDS_DURATION,
self::SECONDS_LONG,
self::SECONDS_SHORT,
];
protected const DURATION_MASKS = [
self::DAYS_DURATION => self::DAYS_DURATION,
self::HOURS_DURATION => self::HOURS_SHORT,
self::MINUTES_DURATION => self::MINUTES_LONG,
self::SECONDS_DURATION => self::SECONDS_LONG,
];
protected const DURATION_DEFAULTS = [
self::HOURS_LONG => self::HOURS_DURATION,
self::HOURS_SHORT => self::HOURS_DURATION,
self::MINUTES_LONG => self::MINUTES_DURATION,
self::MINUTES_SHORT => self::MINUTES_DURATION,
self::SECONDS_LONG => self::SECONDS_DURATION,
self::SECONDS_SHORT => self::SECONDS_DURATION,
];
public const SEPARATOR_COLON = ':';
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
public const SEPARATOR_SPACE = ' ';
public const DURATION_DEFAULT = [
self::HOURS_DURATION,
self::MINUTES_LONG,
self::SECONDS_LONG,
];
/**
* @var string[]
*/
protected array $separators;
/**
* @var string[]
*/
protected array $formatBlocks;
protected bool $durationIsSet = false;
/**
* @param null|string|string[] $separators
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
* if you wish to use different separators, then they should be passed as an array.
* If you want to use only a single format block, then pass a null as the separator argument
*/
public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
{
$separators ??= self::SEPARATOR_COLON;
$formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks;
$this->separators = $this->padSeparatorArray(
is_array($separators) ? $separators : [$separators],
count($formatBlocks) - 1
);
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
if ($this->durationIsSet === false) {
// We need at least one duration mask, so if none has been set we change the first mask element
// to a duration.
$this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])];
}
}
private function mapFormatBlocks(string $value): string
{
// Any duration masking codes are returned as lower case values
if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) {
if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) {
if ($this->durationIsSet) {
// We should only have a single duration mask, the first defined in the mask set,
// so convert any additional duration masks to standard time masks.
$value = self::DURATION_MASKS[mb_strtolower($value)];
}
$this->durationIsSet = true;
}
return mb_strtolower($value);
}
// Wrap any string literals in quotes, so that they're clearly defined as string literals
return $this->wrapLiteral($value);
}
public function format(): string
{
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php 0000644 00000002116 15243417764 0022157 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
final class Locale
{
/**
* Language code: ISO-639 2 character, alpha.
* Optional script code: ISO-15924 4 alpha.
* Optional country code: ISO-3166-1, 2 character alpha.
* Separated by underscores or dashes.
*/
public const STRUCTURE = '/^(?P<language>[a-z]{2})([-_](?P<script>[a-z]{4}))?([-_](?P<country>[a-z]{2}))?$/i';
private NumberFormatter $formatter;
public function __construct(?string $locale, int $style)
{
if (class_exists(NumberFormatter::class) === false) {
throw new Exception();
}
$formatterLocale = str_replace('-', '_', $locale ?? '');
$this->formatter = new NumberFormatter($formatterLocale, $style);
if ($this->formatter->getLocale() !== $formatterLocale) {
throw new Exception("Unable to read locale data for '{$locale}'");
}
}
public function format(): string
{
return $this->formatter->getPattern();
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php 0000644 00000000201 15243417764 0022211 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
interface Wizard
{
public function format(): string;
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php 0000644 00000002426 15243417764 0023041 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
class Percentage extends NumberBase implements Wizard
{
/**
* @param int $decimals number of decimal places to display, in the range 0-30
* @param ?string $locale Set the locale for the percentage format; or leave as the default null.
* If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
*
* @throws Exception If a provided locale code is not a valid format
*/
public function __construct(int $decimals = 2, ?string $locale = null)
{
$this->setDecimals($decimals);
$this->setLocale($locale);
}
protected function getLocaleFormat(): string
{
$formatter = new Locale($this->fullLocale, NumberFormatter::PERCENT);
return $this->decimals > 0
? str_replace('0', '0.' . str_repeat('0', $this->decimals), $formatter->format())
: $formatter->format();
}
public function format(): string
{
if ($this->localeFormat !== null) {
return $this->localeFormat;
}
return sprintf('0%s%%', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php 0000644 00000010174 15243417764 0023055 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use NumberFormatter;
use PhpOffice\PhpSpreadsheet\Exception;
class Accounting extends Currency
{
/**
* @param string $currencyCode the currency symbol or code to display for this mask
* @param int $decimals number of decimal places to display, in the range 0-30
* @param bool $thousandsSeparator indicator whether the thousands separator should be used, or not
* @param bool $currencySymbolPosition indicates whether the currency symbol comes before or after the value
* Possible values are Currency::LEADING_SYMBOL and Currency::TRAILING_SYMBOL
* @param bool $currencySymbolSpacing indicates whether there is spacing between the currency symbol and the value
* Possible values are Currency::SYMBOL_WITH_SPACING and Currency::SYMBOL_WITHOUT_SPACING
* @param ?string $locale Set the locale for the currency format; or leave as the default null.
* If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
* Note that setting a locale will override any other settings defined in this class
* other than the currency code; or decimals (unless the decimals value is set to 0).
*
* @throws Exception If a provided locale code is not a valid format
*/
public function __construct(
string $currencyCode = '$',
int $decimals = 2,
bool $thousandsSeparator = true,
bool $currencySymbolPosition = self::LEADING_SYMBOL,
bool $currencySymbolSpacing = self::SYMBOL_WITHOUT_SPACING,
?string $locale = null
) {
$this->setCurrencyCode($currencyCode);
$this->setThousandsSeparator($thousandsSeparator);
$this->setDecimals($decimals);
$this->setCurrencySymbolPosition($currencySymbolPosition);
$this->setCurrencySymbolSpacing($currencySymbolSpacing);
$this->setLocale($locale);
}
/**
* @throws Exception if the Intl extension and ICU version don't support Accounting formats
*/
protected function getLocaleFormat(): string
{
if (version_compare(PHP_VERSION, '7.4.1', '<')) {
throw new Exception('The Intl extension does not support Accounting Formats below PHP 7.4.1');
}
if ($this->icuVersion() < 53.0) {
throw new Exception('The Intl extension does not support Accounting Formats without ICU 53');
}
// Scrutinizer does not recognize CURRENCY_ACCOUNTING
$formatter = new Locale($this->fullLocale, NumberFormatter::CURRENCY_ACCOUNTING);
$mask = $formatter->format();
if ($this->decimals === 0) {
$mask = (string) preg_replace('/\.0+/miu', '', $mask);
}
return str_replace('¤', $this->formatCurrencyCode(), $mask);
}
private function icuVersion(): float
{
[$major, $minor] = explode('.', INTL_ICU_VERSION);
return (float) "{$major}.{$minor}";
}
private function formatCurrencyCode(): string
{
if ($this->locale === null) {
return $this->currencyCode . '*';
}
return "[\${$this->currencyCode}-{$this->locale}]";
}
public function format(): string
{
if ($this->localeFormat !== null) {
return $this->localeFormat;
}
return sprintf(
'_-%s%s%s0%s%s%s_-',
$this->currencySymbolPosition === self::LEADING_SYMBOL ? $this->formatCurrencyCode() : null,
(
$this->currencySymbolPosition === self::LEADING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
) ? "\u{a0}" : '',
$this->thousandsSeparator ? '#,##' : null,
$this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
(
$this->currencySymbolPosition === self::TRAILING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
) ? "\u{a0}" : '',
$this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php 0000644 00000005711 15243417764 0021662 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
class Time extends DateTimeWizard
{
/**
* Hours without a leading zero, e.g. 9.
*/
public const HOURS_SHORT = 'h';
/**
* Hours with a leading zero, e.g. 09.
*/
public const HOURS_LONG = 'hh';
/**
* Minutes without a leading zero, e.g. 5.
*/
public const MINUTES_SHORT = 'm';
/**
* Minutes with a leading zero, e.g. 05.
*/
public const MINUTES_LONG = 'mm';
/**
* Seconds without a leading zero, e.g. 2.
*/
public const SECONDS_SHORT = 's';
/**
* Seconds with a leading zero, e.g. 02.
*/
public const SECONDS_LONG = 'ss';
public const MORNING_AFTERNOON = 'AM/PM';
protected const TIME_BLOCKS = [
self::HOURS_LONG,
self::HOURS_SHORT,
self::MINUTES_LONG,
self::MINUTES_SHORT,
self::SECONDS_LONG,
self::SECONDS_SHORT,
self::MORNING_AFTERNOON,
];
public const SEPARATOR_COLON = ':';
public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
public const SEPARATOR_SPACE = ' ';
protected const TIME_DEFAULT = [
self::HOURS_LONG,
self::MINUTES_LONG,
self::SECONDS_LONG,
];
/**
* @var string[]
*/
protected array $separators;
/**
* @var string[]
*/
protected array $formatBlocks;
/**
* @param null|string|string[] $separators
* If you want to use the same separator for all format blocks, then it can be passed as a string literal;
* if you wish to use different separators, then they should be passed as an array.
* If you want to use only a single format block, then pass a null as the separator argument
*/
public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
{
$separators ??= self::SEPARATOR_COLON;
$formatBlocks = (count($formatBlocks) === 0) ? self::TIME_DEFAULT : $formatBlocks;
$this->separators = $this->padSeparatorArray(
is_array($separators) ? $separators : [$separators],
count($formatBlocks) - 1
);
$this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
}
private function mapFormatBlocks(string $value): string
{
// Any date masking codes are returned as lower case values
// except for AM/PM, which is set to uppercase
if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) {
return mb_strtolower($value);
} elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) {
return mb_strtoupper($value);
}
// Wrap any string literals in quotes, so that they're clearly defined as string literals
return $this->wrapLiteral($value);
}
public function format(): string
{
return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php 0000644 00000002146 15243417764 0023043 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat\Wizard;
use PhpOffice\PhpSpreadsheet\Exception;
class Scientific extends NumberBase implements Wizard
{
/**
* @param int $decimals number of decimal places to display, in the range 0-30
* @param ?string $locale Set the locale for the scientific format; or leave as the default null.
* Locale has no effect for Scientific Format values, and is retained here for compatibility
* with the other Wizards.
* If provided, Locale values must be a valid formatted locale string (e.g. 'en-GB', 'fr', uz-Arab-AF).
*
* @throws Exception If a provided locale code is not a valid format
*/
public function __construct(int $decimals = 2, ?string $locale = null)
{
$this->setDecimals($decimals);
$this->setLocale($locale);
}
protected function getLocaleFormat(): string
{
return $this->format();
}
public function format(): string
{
return sprintf('0%sE+00', $this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php 0000644 00000003412 15243417764 0023461 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class PercentageFormatter extends BaseFormatter
{
/** @param float|int $value */
public static function format($value, string $format): string
{
if ($format === NumberFormat::FORMAT_PERCENTAGE) {
return round((100 * $value), 0) . '%';
}
$value *= 100;
$format = self::stripQuotes($format);
[, $vDecimals] = explode('.', ((string) $value) . '.');
$vDecimalCount = strlen(rtrim($vDecimals, '0'));
$format = str_replace('%', '%%', $format);
$wholePartSize = strlen((string) floor($value));
$decimalPartSize = 0;
$placeHolders = '';
// Number of decimals
if (preg_match('/\.([?0]+)/u', $format, $matches)) {
$decimalPartSize = strlen($matches[1]);
$vMinDecimalCount = strlen(rtrim($matches[1], '?'));
$decimalPartSize = min(max($vMinDecimalCount, $vDecimalCount), $decimalPartSize);
$placeHolders = str_repeat(' ', strlen($matches[1]) - $decimalPartSize);
}
// Number of digits to display before the decimal
if (preg_match('/([#0,]+)\.?/u', $format, $matches)) {
$firstZero = preg_replace('/^[#,]*/', '', $matches[1]) ?? '';
$wholePartSize = max($wholePartSize, strlen($firstZero));
}
$wholePartSize += $decimalPartSize + (int) ($decimalPartSize > 0);
$replacement = "0{$wholePartSize}.{$decimalPartSize}";
$mask = (string) preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}f{$placeHolders}", $format);
/** @var float */
$valueFloat = $value;
return sprintf($mask, round($valueFloat, $decimalPartSize));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php 0000644 00000014775 15243417764 0022277 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class DateFormatter
{
/**
* Search/replace values to convert Excel date/time format masks to PHP format masks.
*/
private const DATE_FORMAT_REPLACEMENTS = [
// first remove escapes related to non-format characters
'\\' => '',
// 12-hour suffix
'am/pm' => 'A',
// 4-digit year
'e' => 'Y',
'yyyy' => 'Y',
// 2-digit year
'yy' => 'y',
// first letter of month - no php equivalent
'mmmmm' => 'M',
// full month name
'mmmm' => 'F',
// short month name
'mmm' => 'M',
// mm is minutes if time, but can also be month w/leading zero
// so we try to identify times be the inclusion of a : separator in the mask
// It isn't perfect, but the best way I know how
':mm' => ':i',
'mm:' => 'i:',
// full day of week name
'dddd' => 'l',
// short day of week name
'ddd' => 'D',
// days leading zero
'dd' => 'd',
// days no leading zero
'd' => 'j',
// fractional seconds - no php equivalent
'.s' => '',
];
/**
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
*/
private const DATE_FORMAT_REPLACEMENTS24 = [
'hh' => 'H',
'h' => 'G',
// month leading zero
'mm' => 'm',
// month no leading zero
'm' => 'n',
// seconds
'ss' => 's',
];
/**
* Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
*/
private const DATE_FORMAT_REPLACEMENTS12 = [
'hh' => 'h',
'h' => 'g',
// month leading zero
'mm' => 'm',
// month no leading zero
'm' => 'n',
// seconds
'ss' => 's',
];
private const HOURS_IN_DAY = 24;
private const MINUTES_IN_DAY = 60 * self::HOURS_IN_DAY;
private const SECONDS_IN_DAY = 60 * self::MINUTES_IN_DAY;
private const INTERVAL_PRECISION = 10;
private const INTERVAL_LEADING_ZERO = [
'[hh]',
'[mm]',
'[ss]',
];
private const INTERVAL_ROUND_PRECISION = [
// hours and minutes truncate
'[h]' => self::INTERVAL_PRECISION,
'[hh]' => self::INTERVAL_PRECISION,
'[m]' => self::INTERVAL_PRECISION,
'[mm]' => self::INTERVAL_PRECISION,
// seconds round
'[s]' => 0,
'[ss]' => 0,
];
private const INTERVAL_MULTIPLIER = [
'[h]' => self::HOURS_IN_DAY,
'[hh]' => self::HOURS_IN_DAY,
'[m]' => self::MINUTES_IN_DAY,
'[mm]' => self::MINUTES_IN_DAY,
'[s]' => self::SECONDS_IN_DAY,
'[ss]' => self::SECONDS_IN_DAY,
];
/** @param mixed $value */
private static function tryInterval(bool &$seekingBracket, string &$block, $value, string $format): void
{
if ($seekingBracket) {
if (false !== strpos($block, $format)) {
$hours = (string) (int) round(
self::INTERVAL_MULTIPLIER[$format] * $value,
self::INTERVAL_ROUND_PRECISION[$format]
);
if (strlen($hours) === 1 && in_array($format, self::INTERVAL_LEADING_ZERO, true)) {
$hours = "0$hours";
}
$block = str_replace($format, $hours, $block);
$seekingBracket = false;
}
}
}
/** @param mixed $value */
public static function format($value, string $format): string
{
// strip off first part containing e.g. [$-F800] or [$USD-409]
// general syntax: [$<Currency string>-<language info>]
// language info is in hexadecimal
// strip off chinese part like [DBNum1][$-804]
$format = (string) preg_replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format);
// OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
// but we don't want to change any quoted strings
/** @var callable */
$callable = [self::class, 'setLowercaseCallback'];
$format = (string) preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', $callable, $format);
// Only process the non-quoted blocks for date format characters
$blocks = explode('"', $format);
foreach ($blocks as $key => &$block) {
if ($key % 2 == 0) {
$block = strtr($block, self::DATE_FORMAT_REPLACEMENTS);
if (!strpos($block, 'A')) {
// 24-hour time format
// when [h]:mm format, the [h] should replace to the hours of the value * 24
$seekingBracket = true;
self::tryInterval($seekingBracket, $block, $value, '[h]');
self::tryInterval($seekingBracket, $block, $value, '[hh]');
self::tryInterval($seekingBracket, $block, $value, '[mm]');
self::tryInterval($seekingBracket, $block, $value, '[m]');
self::tryInterval($seekingBracket, $block, $value, '[s]');
self::tryInterval($seekingBracket, $block, $value, '[ss]');
$block = strtr($block, self::DATE_FORMAT_REPLACEMENTS24);
} else {
// 12-hour time format
$block = strtr($block, self::DATE_FORMAT_REPLACEMENTS12);
}
}
}
$format = implode('"', $blocks);
// escape any quoted characters so that DateTime format() will render them correctly
/** @var callable */
$callback = [self::class, 'escapeQuotesCallback'];
$format = (string) preg_replace_callback('/"(.*)"/U', $callback, $format);
$dateObj = Date::excelToDateTimeObject($value);
// If the colon preceding minute had been quoted, as happens in
// Excel 2003 XML formats, m will not have been changed to i above.
// Change it now.
$format = (string) \preg_replace('/\\\\:m/', ':i', $format);
return $dateObj->format($format);
}
private static function setLowercaseCallback(array $matches): string
{
return mb_strtolower($matches[0]);
}
private static function escapeQuotesCallback(array $matches): string
{
return '\\' . implode('\\', /** @scrutinizer ignore-type */ str_split($matches[1]));
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php 0000644 00000020466 15243417764 0021473 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BIFF8;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
class Formatter
{
/**
* Matches any @ symbol that isn't enclosed in quotes.
*/
private const SYMBOL_AT = '/@(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu';
/**
* Matches any ; symbol that isn't enclosed in quotes, for a "section" split.
*/
private const SECTION_SPLIT = '/;(?=(?:[^"]*"[^"]*")*[^"]*\Z)/miu';
/**
* @param mixed $value
* @param mixed $comparisonValue
* @param mixed $defaultComparisonValue
*/
private static function splitFormatComparison(
$value,
?string $condition,
$comparisonValue,
string $defaultCondition,
$defaultComparisonValue
): bool {
if (!$condition) {
$condition = $defaultCondition;
$comparisonValue = $defaultComparisonValue;
}
switch ($condition) {
case '>':
return $value > $comparisonValue;
case '<':
return $value < $comparisonValue;
case '<=':
return $value <= $comparisonValue;
case '<>':
return $value != $comparisonValue;
case '=':
return $value == $comparisonValue;
}
return $value >= $comparisonValue;
}
/** @param mixed $value */
private static function splitFormatForSectionSelection(array $sections, $value): array
{
// Extract the relevant section depending on whether number is positive, negative, or zero?
// Text not supported yet.
// Here is how the sections apply to various values in Excel:
// 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
// 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE]
// 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
// 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
$sectionCount = count($sections);
// Colour could be a named colour, or a numeric index entry in the colour-palette
$color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . '|color\\s*(\\d+))\\]/mui';
$cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
$colors = ['', '', '', '', ''];
$conditionOperations = ['', '', '', '', ''];
$conditionComparisonValues = [0, 0, 0, 0, 0];
for ($idx = 0; $idx < $sectionCount; ++$idx) {
if (preg_match($color_regex, $sections[$idx], $matches)) {
if (isset($matches[2])) {
$colors[$idx] = '#' . BIFF8::lookup((int) $matches[2] + 7)['rgb'];
} else {
$colors[$idx] = $matches[0];
}
$sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]);
}
if (preg_match($cond_regex, $sections[$idx], $matches)) {
$conditionOperations[$idx] = $matches[1];
$conditionComparisonValues[$idx] = $matches[2];
$sections[$idx] = (string) preg_replace($cond_regex, '', $sections[$idx]);
}
}
$color = $colors[0];
$format = $sections[0];
$absval = $value;
switch ($sectionCount) {
case 2:
$absval = abs($value);
if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>=', 0)) {
$color = $colors[1];
$format = $sections[1];
}
break;
case 3:
case 4:
$absval = abs($value);
if (!self::splitFormatComparison($value, $conditionOperations[0], $conditionComparisonValues[0], '>', 0)) {
if (self::splitFormatComparison($value, $conditionOperations[1], $conditionComparisonValues[1], '<', 0)) {
$color = $colors[1];
$format = $sections[1];
} else {
$color = $colors[2];
$format = $sections[2];
}
}
break;
}
return [$color, $format, $absval];
}
/**
* Convert a value in a pre-defined format to a PHP string.
*
* @param null|bool|float|int|RichText|string $value Value to format
* @param string $format Format code: see = self::FORMAT_* for predefined values;
* or can be any valid MS Excel custom format string
* @param array $callBack Callback function for additional formatting of string
*
* @return string Formatted string
*/
public static function toFormattedString($value, $format, $callBack = null)
{
if (is_bool($value)) {
return $value ? Calculation::getTRUE() : Calculation::getFALSE();
}
// For now we do not treat strings in sections, although section 4 of a format code affects strings
// Process a single block format code containing @ for text substitution
if (preg_match(self::SECTION_SPLIT, $format) === 0 && preg_match(self::SYMBOL_AT, $format) === 1) {
return str_replace('"', '', preg_replace(self::SYMBOL_AT, (string) $value, $format) ?? '');
}
// If we have a text value, return it "as is"
if (!is_numeric($value)) {
return (string) $value;
}
// For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
// it seems to round numbers to a total of 10 digits.
if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
return (string) $value;
}
// Ignore square-$-brackets prefix in format string, like "[$-411]ge.m.d", "[$-010419]0%", etc
$format = (string) preg_replace('/^\[\$-[^\]]*\]/', '', $format);
$format = (string) preg_replace_callback(
'/(["])(?:(?=(\\\\?))\\2.)*?\\1/u',
function ($matches) {
return str_replace('.', chr(0x00), $matches[0]);
},
$format
);
// Convert any other escaped characters to quoted strings, e.g. (\T to "T")
$format = (string) preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
$sections = preg_split(self::SECTION_SPLIT, $format) ?: [];
[$colors, $format, $value] = self::splitFormatForSectionSelection($sections, $value);
// In Excel formats, "_" is used to add spacing,
// The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
$format = (string) preg_replace('/_.?/ui', ' ', $format);
// Let's begin inspecting the format and converting the value to a formatted string
if (
// Check for date/time characters (not inside quotes)
(preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format))
// A date/time with a decimal time shouldn't have a digit placeholder before the decimal point
&& (preg_match('/[0\?#]\.(?![^\[]*\])/miu', $format) === 0)
) {
// datetime format
$value = DateFormatter::format($value, $format);
} else {
if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) {
$value = substr($format, 1, -1);
} elseif (preg_match('/[0#, ]%/', $format)) {
// % number format - avoid weird '-0' problem
$value = PercentageFormatter::format(0 + (float) $value, $format);
} else {
$value = NumberFormatter::format($value, $format);
}
}
// Additional formatting provided by callback function
if ($callBack !== null) {
[$writerInstance, $function] = $callBack;
$value = $writerInstance->$function($value, $colors);
}
return str_replace(chr(0x00), '.', $value);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php 0000644 00000000524 15243417764 0022257 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
abstract class BaseFormatter
{
protected static function stripQuotes(string $format): string
{
// Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
return str_replace(['"', '*'], '', $format);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php 0000644 00000004455 15243417764 0023161 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
class FractionFormatter extends BaseFormatter
{
/**
* @param mixed $value
*/
public static function format($value, string $format): string
{
$format = self::stripQuotes($format);
$value = (float) $value;
$absValue = abs($value);
$sign = ($value < 0.0) ? '-' : '';
$integerPart = floor($absValue);
$decimalPart = self::getDecimal((string) $absValue);
if ($decimalPart === '0') {
return "{$sign}{$integerPart}";
}
$decimalLength = strlen($decimalPart);
$decimalDivisor = 10 ** $decimalLength;
preg_match('/(#?.*\?)\/(\?+|\d+)/', $format, $matches);
$formatIntegerPart = $matches[1];
if (is_numeric($matches[2])) {
$fractionDivisor = 100 / (int) $matches[2];
} else {
/** @var float */
$fractionDivisor = MathTrig\Gcd::evaluate((int) $decimalPart, $decimalDivisor);
}
$adjustedDecimalPart = (int) round((int) $decimalPart / $fractionDivisor, 0);
$adjustedDecimalDivisor = $decimalDivisor / $fractionDivisor;
if ((strpos($formatIntegerPart, '0') !== false)) {
return "{$sign}{$integerPart} {$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
} elseif ((strpos($formatIntegerPart, '#') !== false)) {
if ($integerPart == 0) {
return "{$sign}{$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
}
return "{$sign}{$integerPart} {$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
} elseif ((substr($formatIntegerPart, 0, 3) == '? ?')) {
if ($integerPart == 0) {
$integerPart = '';
}
return "{$sign}{$integerPart} {$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
}
$adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
return "{$sign}{$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
}
private static function getDecimal(string $value): string
{
$decimalPart = '0';
if (preg_match('/^\\d*[.](\\d*[1-9])0*$/', $value, $matches) === 1) {
$decimalPart = $matches[1];
}
return $decimalPart;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php 0000644 00000035353 15243417764 0017531 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
class NumberFormat extends Supervisor
{
// Pre-defined formats
const FORMAT_GENERAL = 'General';
const FORMAT_TEXT = '@';
const FORMAT_NUMBER = '0';
const FORMAT_NUMBER_0 = '0.0';
const FORMAT_NUMBER_00 = '0.00';
const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
const FORMAT_PERCENTAGE = '0%';
const FORMAT_PERCENTAGE_0 = '0.0%';
const FORMAT_PERCENTAGE_00 = '0.00%';
/** @deprecated 1.26 use FORMAT_DATE_YYYYMMDD instead */
const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
const FORMAT_DATE_YYYYMMDD = 'yyyy-mm-dd';
const FORMAT_DATE_DDMMYYYY = 'dd/mm/yyyy';
const FORMAT_DATE_DMYSLASH = 'd/m/yy';
const FORMAT_DATE_DMYMINUS = 'd-m-yy';
const FORMAT_DATE_DMMINUS = 'd-m';
const FORMAT_DATE_MYMINUS = 'm-yy';
const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
const FORMAT_DATE_XLSX16 = 'd-mmm';
const FORMAT_DATE_XLSX17 = 'mmm-yy';
const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
const FORMAT_DATE_TIME3 = 'h:mm';
const FORMAT_DATE_TIME4 = 'h:mm:ss';
const FORMAT_DATE_TIME5 = 'mm:ss';
const FORMAT_DATE_TIME6 = 'h:mm:ss';
const FORMAT_DATE_TIME7 = 'i:s.S';
const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@';
const DATE_TIME_OR_DATETIME_ARRAY = [
self::FORMAT_DATE_YYYYMMDD,
self::FORMAT_DATE_DDMMYYYY,
self::FORMAT_DATE_DMYSLASH,
self::FORMAT_DATE_DMYMINUS,
self::FORMAT_DATE_DMMINUS,
self::FORMAT_DATE_MYMINUS,
self::FORMAT_DATE_XLSX14,
self::FORMAT_DATE_XLSX15,
self::FORMAT_DATE_XLSX16,
self::FORMAT_DATE_XLSX17,
self::FORMAT_DATE_XLSX22,
self::FORMAT_DATE_DATETIME,
self::FORMAT_DATE_TIME1,
self::FORMAT_DATE_TIME2,
self::FORMAT_DATE_TIME3,
self::FORMAT_DATE_TIME4,
self::FORMAT_DATE_TIME5,
self::FORMAT_DATE_TIME6,
self::FORMAT_DATE_TIME7,
self::FORMAT_DATE_TIME8,
self::FORMAT_DATE_YYYYMMDDSLASH,
];
const TIME_OR_DATETIME_ARRAY = [
self::FORMAT_DATE_XLSX22,
self::FORMAT_DATE_DATETIME,
self::FORMAT_DATE_TIME1,
self::FORMAT_DATE_TIME2,
self::FORMAT_DATE_TIME3,
self::FORMAT_DATE_TIME4,
self::FORMAT_DATE_TIME5,
self::FORMAT_DATE_TIME6,
self::FORMAT_DATE_TIME7,
self::FORMAT_DATE_TIME8,
];
/** @deprecated 1.28 use FORMAT_CURRENCY_USD_INTEGER instead */
const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0_-';
const FORMAT_CURRENCY_USD_INTEGER = '$#,##0_-';
const FORMAT_CURRENCY_USD = '$#,##0.00_-';
/** @deprecated 1.28 use FORMAT_CURRENCY_EUR_INTEGER instead */
const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0_-"€"';
const FORMAT_CURRENCY_EUR_INTEGER = '#,##0_-[$€]';
const FORMAT_CURRENCY_EUR = '#,##0.00_-[$€]';
const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
/**
* Excel built-in number formats.
*
* @var array
*/
protected static $builtInFormats;
/**
* Excel built-in number formats (flipped, for faster lookups).
*
* @var array
*/
protected static $flippedBuiltInFormats;
/**
* Format Code.
*
* @var null|string
*/
protected $formatCode = self::FORMAT_GENERAL;
/**
* Built-in format Code.
*
* @var false|int
*/
protected $builtInFormatCode = 0;
/**
* Create a new NumberFormat.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
if ($isConditional) {
$this->formatCode = null;
$this->builtInFormatCode = false;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return NumberFormat
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getNumberFormat();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['numberFormat' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
* [
* 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['formatCode'])) {
$this->setFormatCode($styleArray['formatCode']);
}
}
return $this;
}
/**
* Get Format Code.
*
* @return null|string
*/
public function getFormatCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getFormatCode();
}
if (is_int($this->builtInFormatCode)) {
return self::builtInFormatCode($this->builtInFormatCode);
}
return $this->formatCode;
}
/**
* Set Format Code.
*
* @param string $formatCode see self::FORMAT_*
*
* @return $this
*/
public function setFormatCode(string $formatCode)
{
if ($formatCode == '') {
$formatCode = self::FORMAT_GENERAL;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['formatCode' => $formatCode]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->formatCode = $formatCode;
$this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode);
}
return $this;
}
/**
* Get Built-In Format Code.
*
* @return false|int
*/
public function getBuiltInFormatCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getBuiltInFormatCode();
}
// Scrutinizer says this could return true. It is wrong.
return $this->builtInFormatCode;
}
/**
* Set Built-In Format Code.
*
* @param int $formatCodeIndex Id of the built-in format code to use
*
* @return $this
*/
public function setBuiltInFormatCode(int $formatCodeIndex)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->builtInFormatCode = $formatCodeIndex;
$this->formatCode = self::builtInFormatCode($formatCodeIndex);
}
return $this;
}
/**
* Fill built-in format codes.
*/
private static function fillBuiltInFormatCodes(): void
{
// [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
// 18.8.30. numFmt (Number Format)
//
// The ECMA standard defines built-in format IDs
// 14: "mm-dd-yy"
// 22: "m/d/yy h:mm"
// 37: "#,##0 ;(#,##0)"
// 38: "#,##0 ;[Red](#,##0)"
// 39: "#,##0.00;(#,##0.00)"
// 40: "#,##0.00;[Red](#,##0.00)"
// 47: "mmss.0"
// KOR fmt 55: "yyyy-mm-dd"
// Excel defines built-in format IDs
// 14: "m/d/yyyy"
// 22: "m/d/yyyy h:mm"
// 37: "#,##0_);(#,##0)"
// 38: "#,##0_);[Red](#,##0)"
// 39: "#,##0.00_);(#,##0.00)"
// 40: "#,##0.00_);[Red](#,##0.00)"
// 47: "mm:ss.0"
// KOR fmt 55: "yyyy/mm/dd"
// Built-in format codes
if (empty(self::$builtInFormats)) {
self::$builtInFormats = [];
// General
self::$builtInFormats[0] = self::FORMAT_GENERAL;
self::$builtInFormats[1] = '0';
self::$builtInFormats[2] = '0.00';
self::$builtInFormats[3] = '#,##0';
self::$builtInFormats[4] = '#,##0.00';
self::$builtInFormats[9] = '0%';
self::$builtInFormats[10] = '0.00%';
self::$builtInFormats[11] = '0.00E+00';
self::$builtInFormats[12] = '# ?/?';
self::$builtInFormats[13] = '# ??/??';
self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
self::$builtInFormats[15] = 'd-mmm-yy';
self::$builtInFormats[16] = 'd-mmm';
self::$builtInFormats[17] = 'mmm-yy';
self::$builtInFormats[18] = 'h:mm AM/PM';
self::$builtInFormats[19] = 'h:mm:ss AM/PM';
self::$builtInFormats[20] = 'h:mm';
self::$builtInFormats[21] = 'h:mm:ss';
self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)';
self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)';
self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
self::$builtInFormats[45] = 'mm:ss';
self::$builtInFormats[46] = '[h]:mm:ss';
self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0';
self::$builtInFormats[48] = '##0.0E+0';
self::$builtInFormats[49] = '@';
// CHT
self::$builtInFormats[27] = '[$-404]e/m/d';
self::$builtInFormats[30] = 'm/d/yy';
self::$builtInFormats[36] = '[$-404]e/m/d';
self::$builtInFormats[50] = '[$-404]e/m/d';
self::$builtInFormats[57] = '[$-404]e/m/d';
// THA
self::$builtInFormats[59] = 't0';
self::$builtInFormats[60] = 't0.00';
self::$builtInFormats[61] = 't#,##0';
self::$builtInFormats[62] = 't#,##0.00';
self::$builtInFormats[67] = 't0%';
self::$builtInFormats[68] = 't0.00%';
self::$builtInFormats[69] = 't# ?/?';
self::$builtInFormats[70] = 't# ??/??';
// JPN
self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
self::$builtInFormats[32] = 'h"時"mm"分"';
self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
self::$builtInFormats[34] = 'yyyy"年"m"月"';
self::$builtInFormats[35] = 'm"月"d"日"';
self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[52] = 'yyyy"年"m"月"';
self::$builtInFormats[53] = 'm"月"d"日"';
self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
self::$builtInFormats[55] = 'yyyy"年"m"月"';
self::$builtInFormats[56] = 'm"月"d"日"';
self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
// Flip array (for faster lookups)
self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
}
}
/**
* Get built-in format code.
*
* @param int $index
*
* @return string
*/
public static function builtInFormatCode($index)
{
// Clean parameter
$index = (int) $index;
// Ensure built-in format codes are available
self::fillBuiltInFormatCodes();
// Lookup format code
if (isset(self::$builtInFormats[$index])) {
return self::$builtInFormats[$index];
}
return '';
}
/**
* Get built-in format code index.
*
* @param string $formatCodeIndex
*
* @return false|int
*/
public static function builtInFormatCodeIndex($formatCodeIndex)
{
// Ensure built-in format codes are available
self::fillBuiltInFormatCodes();
// Lookup format code
if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) {
return self::$flippedBuiltInFormats[$formatCodeIndex];
}
return false;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->formatCode .
$this->builtInFormatCode .
__CLASS__
);
}
/**
* Convert a value in a pre-defined format to a PHP string.
*
* @param mixed $value Value to format
* @param string $format Format code: see = self::FORMAT_* for predefined values;
* or can be any valid MS Excel custom format string
* @param array $callBack Callback function for additional formatting of string
*
* @return string Formatted string
*/
public static function toFormattedString($value, $format, $callBack = null)
{
return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php 0000644 00000012522 15243417764 0016472 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
/**
* Class to handle tint applied to color.
* Code borrows heavily from some Python projects.
*
* @see https://docs.python.org/3/library/colorsys.html
* @see https://gist.github.com/Mike-Honey/b36e651e9a7f1d2e1d60ce1c63b9b633
*/
class RgbTint
{
private const ONE_THIRD = 1.0 / 3.0;
private const ONE_SIXTH = 1.0 / 6.0;
private const TWO_THIRD = 2.0 / 3.0;
private const RGBMAX = 255.0;
/**
* MS excel's tint function expects that HLS is base 240.
*
* @see https://social.msdn.microsoft.com/Forums/en-US/e9d8c136-6d62-4098-9b1b-dac786149f43/excel-color-tint-algorithm-incorrect?forum=os_binaryfile#d3c2ac95-52e0-476b-86f1-e2a697f24969
*/
private const HLSMAX = 240.0;
/**
* Convert red/green/blue to hue/luminance/saturation.
*
* @param float $red 0.0 through 1.0
* @param float $green 0.0 through 1.0
* @param float $blue 0.0 through 1.0
*
* @return float[]
*/
private static function rgbToHls(float $red, float $green, float $blue): array
{
$maxc = max($red, $green, $blue);
$minc = min($red, $green, $blue);
$luminance = ($minc + $maxc) / 2.0;
if ($minc === $maxc) {
return [0.0, $luminance, 0.0];
}
$maxMinusMin = $maxc - $minc;
if ($luminance <= 0.5) {
$s = $maxMinusMin / ($maxc + $minc);
} else {
$s = $maxMinusMin / (2.0 - $maxc - $minc);
}
$rc = ($maxc - $red) / $maxMinusMin;
$gc = ($maxc - $green) / $maxMinusMin;
$bc = ($maxc - $blue) / $maxMinusMin;
if ($red === $maxc) {
$h = $bc - $gc;
} elseif ($green === $maxc) {
$h = 2.0 + $rc - $bc;
} else {
$h = 4.0 + $gc - $rc;
}
$h = self::positiveDecimalPart($h / 6.0);
return [$h, $luminance, $s];
}
/** @var mixed */
private static $scrutinizerZeroPointZero = 0.0;
/**
* Convert hue/luminance/saturation to red/green/blue.
*
* @param float $hue 0.0 through 1.0
* @param float $luminance 0.0 through 1.0
* @param float $saturation 0.0 through 1.0
*
* @return float[]
*/
private static function hlsToRgb($hue, $luminance, $saturation): array
{
if ($saturation === self::$scrutinizerZeroPointZero) {
return [$luminance, $luminance, $luminance];
}
if ($luminance <= 0.5) {
$m2 = $luminance * (1.0 + $saturation);
} else {
$m2 = $luminance + $saturation - ($luminance * $saturation);
}
$m1 = 2.0 * $luminance - $m2;
return [
self::vFunction($m1, $m2, $hue + self::ONE_THIRD),
self::vFunction($m1, $m2, $hue),
self::vFunction($m1, $m2, $hue - self::ONE_THIRD),
];
}
private static function vFunction(float $m1, float $m2, float $hue): float
{
$hue = self::positiveDecimalPart($hue);
if ($hue < self::ONE_SIXTH) {
return $m1 + ($m2 - $m1) * $hue * 6.0;
}
if ($hue < 0.5) {
return $m2;
}
if ($hue < self::TWO_THIRD) {
return $m1 + ($m2 - $m1) * (self::TWO_THIRD - $hue) * 6.0;
}
return $m1;
}
private static function positiveDecimalPart(float $hue): float
{
$hue = fmod($hue, 1.0);
return ($hue >= 0.0) ? $hue : (1.0 + $hue);
}
/**
* Convert red/green/blue to HLSMAX-based hue/luminance/saturation.
*
* @return int[]
*/
private static function rgbToMsHls(int $red, int $green, int $blue): array
{
$red01 = $red / self::RGBMAX;
$green01 = $green / self::RGBMAX;
$blue01 = $blue / self::RGBMAX;
[$hue, $luminance, $saturation] = self::rgbToHls($red01, $green01, $blue01);
return [
(int) round($hue * self::HLSMAX),
(int) round($luminance * self::HLSMAX),
(int) round($saturation * self::HLSMAX),
];
}
/**
* Converts HLSMAX based HLS values to rgb values in the range (0,1).
*
* @return float[]
*/
private static function msHlsToRgb(int $hue, int $lightness, int $saturation): array
{
return self::hlsToRgb($hue / self::HLSMAX, $lightness / self::HLSMAX, $saturation / self::HLSMAX);
}
/**
* Tints HLSMAX based luminance.
*
* @see http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html
*/
private static function tintLuminance(float $tint, float $luminance): int
{
if ($tint < 0) {
return (int) round($luminance * (1.0 + $tint));
}
return (int) round($luminance * (1.0 - $tint) + (self::HLSMAX - self::HLSMAX * (1.0 - $tint)));
}
/**
* Return result of tinting supplied rgb as 6 hex digits.
*/
public static function rgbAndTintToRgb(int $red, int $green, int $blue, float $tint): string
{
[$hue, $luminance, $saturation] = self::rgbToMsHls($red, $green, $blue);
[$red, $green, $blue] = self::msHlsToRgb($hue, self::tintLuminance($tint, $luminance), $saturation);
return sprintf(
'%02X%02X%02X',
(int) round($red * self::RGBMAX),
(int) round($green * self::RGBMAX),
(int) round($blue * self::RGBMAX)
);
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Color.php 0000644 00000033245 15243417764 0016204 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
class Color extends Supervisor
{
const NAMED_COLORS = [
'Black',
'White',
'Red',
'Green',
'Blue',
'Yellow',
'Magenta',
'Cyan',
];
// Colors
const COLOR_BLACK = 'FF000000';
const COLOR_WHITE = 'FFFFFFFF';
const COLOR_RED = 'FFFF0000';
const COLOR_DARKRED = 'FF800000';
const COLOR_BLUE = 'FF0000FF';
const COLOR_DARKBLUE = 'FF000080';
const COLOR_GREEN = 'FF00FF00';
const COLOR_DARKGREEN = 'FF008000';
const COLOR_YELLOW = 'FFFFFF00';
const COLOR_DARKYELLOW = 'FF808000';
const COLOR_MAGENTA = 'FFFF00FF';
const COLOR_CYAN = 'FF00FFFF';
const NAMED_COLOR_TRANSLATIONS = [
'Black' => self::COLOR_BLACK,
'White' => self::COLOR_WHITE,
'Red' => self::COLOR_RED,
'Green' => self::COLOR_GREEN,
'Blue' => self::COLOR_BLUE,
'Yellow' => self::COLOR_YELLOW,
'Magenta' => self::COLOR_MAGENTA,
'Cyan' => self::COLOR_CYAN,
];
const VALIDATE_ARGB_SIZE = 8;
const VALIDATE_RGB_SIZE = 6;
const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i';
const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i';
private const INDEXED_COLORS = [
1 => 'FF000000', // System Colour #1 - Black
2 => 'FFFFFFFF', // System Colour #2 - White
3 => 'FFFF0000', // System Colour #3 - Red
4 => 'FF00FF00', // System Colour #4 - Green
5 => 'FF0000FF', // System Colour #5 - Blue
6 => 'FFFFFF00', // System Colour #6 - Yellow
7 => 'FFFF00FF', // System Colour #7- Magenta
8 => 'FF00FFFF', // System Colour #8- Cyan
9 => 'FF800000', // Standard Colour #9
10 => 'FF008000', // Standard Colour #10
11 => 'FF000080', // Standard Colour #11
12 => 'FF808000', // Standard Colour #12
13 => 'FF800080', // Standard Colour #13
14 => 'FF008080', // Standard Colour #14
15 => 'FFC0C0C0', // Standard Colour #15
16 => 'FF808080', // Standard Colour #16
17 => 'FF9999FF', // Chart Fill Colour #17
18 => 'FF993366', // Chart Fill Colour #18
19 => 'FFFFFFCC', // Chart Fill Colour #19
20 => 'FFCCFFFF', // Chart Fill Colour #20
21 => 'FF660066', // Chart Fill Colour #21
22 => 'FFFF8080', // Chart Fill Colour #22
23 => 'FF0066CC', // Chart Fill Colour #23
24 => 'FFCCCCFF', // Chart Fill Colour #24
25 => 'FF000080', // Chart Line Colour #25
26 => 'FFFF00FF', // Chart Line Colour #26
27 => 'FFFFFF00', // Chart Line Colour #27
28 => 'FF00FFFF', // Chart Line Colour #28
29 => 'FF800080', // Chart Line Colour #29
30 => 'FF800000', // Chart Line Colour #30
31 => 'FF008080', // Chart Line Colour #31
32 => 'FF0000FF', // Chart Line Colour #32
33 => 'FF00CCFF', // Standard Colour #33
34 => 'FFCCFFFF', // Standard Colour #34
35 => 'FFCCFFCC', // Standard Colour #35
36 => 'FFFFFF99', // Standard Colour #36
37 => 'FF99CCFF', // Standard Colour #37
38 => 'FFFF99CC', // Standard Colour #38
39 => 'FFCC99FF', // Standard Colour #39
40 => 'FFFFCC99', // Standard Colour #40
41 => 'FF3366FF', // Standard Colour #41
42 => 'FF33CCCC', // Standard Colour #42
43 => 'FF99CC00', // Standard Colour #43
44 => 'FFFFCC00', // Standard Colour #44
45 => 'FFFF9900', // Standard Colour #45
46 => 'FFFF6600', // Standard Colour #46
47 => 'FF666699', // Standard Colour #47
48 => 'FF969696', // Standard Colour #48
49 => 'FF003366', // Standard Colour #49
50 => 'FF339966', // Standard Colour #50
51 => 'FF003300', // Standard Colour #51
52 => 'FF333300', // Standard Colour #52
53 => 'FF993300', // Standard Colour #53
54 => 'FF993366', // Standard Colour #54
55 => 'FF333399', // Standard Colour #55
56 => 'FF333333', // Standard Colour #56
];
/**
* ARGB - Alpha RGB.
*
* @var null|string
*/
protected $argb;
/** @var bool */
private $hasChanged = false;
/**
* Create a new Color.
*
* @param string $colorValue ARGB value for the colour, or named colour
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($colorValue = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
if (!$isConditional) {
$this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Color
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
/** @var Border|Fill $sharedComponent */
$sharedComponent = $parent->getSharedComponent();
if ($sharedComponent instanceof Fill) {
if ($this->parentPropertyName === 'endColor') {
return $sharedComponent->getEndColor();
}
return $sharedComponent->getStartColor();
}
return $sharedComponent->getColor();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
/** @var Style */
$parent = $this->parent;
return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]);
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['rgb'])) {
$this->setRGB($styleArray['rgb']);
}
if (isset($styleArray['argb'])) {
$this->setARGB($styleArray['argb']);
}
}
return $this;
}
private function validateColor(?string $colorValue): string
{
if ($colorValue === null || $colorValue === '') {
return self::COLOR_BLACK;
}
$named = ucfirst(strtolower($colorValue));
if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) {
return self::NAMED_COLOR_TRANSLATIONS[$named];
}
if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) {
return $colorValue;
}
if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) {
return 'FF' . $colorValue;
}
return '';
}
/**
* Get ARGB.
*/
public function getARGB(): ?string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getARGB();
}
return $this->argb;
}
/**
* Set ARGB.
*
* @param string $colorValue ARGB value, or a named color
*
* @return $this
*/
public function setARGB(?string $colorValue = self::COLOR_BLACK)
{
$this->hasChanged = true;
$colorValue = $this->validateColor($colorValue);
if ($colorValue === '') {
return $this;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['argb' => $colorValue]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->argb = $colorValue;
}
return $this;
}
/**
* Get RGB.
*/
public function getRGB(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getRGB();
}
return substr($this->argb ?? '', 2);
}
/**
* Set RGB.
*
* @param string $colorValue RGB value, or a named color
*
* @return $this
*/
public function setRGB(?string $colorValue = self::COLOR_BLACK)
{
return $this->setARGB($colorValue);
}
/**
* Get a specified colour component of an RGB value.
*
* @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
* @param int $offset Position within the RGB value to extract
* @param bool $hex Flag indicating whether the component should be returned as a hex or a
* decimal value
*
* @return int|string The extracted colour component
*/
private static function getColourComponent($rgbValue, $offset, $hex = true)
{
$colour = substr($rgbValue, $offset, 2) ?: '';
if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) {
$colour = '00';
}
return ($hex) ? $colour : (int) hexdec($colour);
}
/**
* Get the red colour component of an RGB value.
*
* @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
* @param bool $hex Flag indicating whether the component should be returned as a hex or a
* decimal value
*
* @return int|string The red colour component
*/
public static function getRed($rgbValue, $hex = true)
{
return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex);
}
/**
* Get the green colour component of an RGB value.
*
* @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
* @param bool $hex Flag indicating whether the component should be returned as a hex or a
* decimal value
*
* @return int|string The green colour component
*/
public static function getGreen($rgbValue, $hex = true)
{
return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex);
}
/**
* Get the blue colour component of an RGB value.
*
* @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
* @param bool $hex Flag indicating whether the component should be returned as a hex or a
* decimal value
*
* @return int|string The blue colour component
*/
public static function getBlue($rgbValue, $hex = true)
{
return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex);
}
/**
* Adjust the brightness of a color.
*
* @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
* @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
*
* @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
*/
public static function changeBrightness($hexColourValue, $adjustPercentage)
{
$rgba = (strlen($hexColourValue) === 8);
$adjustPercentage = max(-1.0, min(1.0, $adjustPercentage));
/** @var int $red */
$red = self::getRed($hexColourValue, false);
/** @var int $green */
$green = self::getGreen($hexColourValue, false);
/** @var int $blue */
$blue = self::getBlue($hexColourValue, false);
return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
}
/**
* Get indexed color.
*
* @param int $colorIndex Index entry point into the colour array
* @param bool $background Flag to indicate whether default background or foreground colour
* should be returned if the indexed colour doesn't exist
*/
public static function indexedColor($colorIndex, $background = false, ?array $palette = null): self
{
// Clean parameter
$colorIndex = (int) $colorIndex;
if (empty($palette)) {
if (isset(self::INDEXED_COLORS[$colorIndex])) {
return new self(self::INDEXED_COLORS[$colorIndex]);
}
} else {
if (isset($palette[$colorIndex])) {
return new self($palette[$colorIndex]);
}
}
return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK);
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->argb .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'argb', $this->getARGB());
return $exportedArray;
}
public function getHasChanged(): bool
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->hasChanged;
}
return $this->hasChanged;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php 0000644 00000037163 15243417764 0017047 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Alignment extends Supervisor
{
// Horizontal alignment styles
const HORIZONTAL_GENERAL = 'general';
const HORIZONTAL_LEFT = 'left';
const HORIZONTAL_RIGHT = 'right';
const HORIZONTAL_CENTER = 'center';
const HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous';
const HORIZONTAL_JUSTIFY = 'justify';
const HORIZONTAL_FILL = 'fill';
const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only
private const HORIZONTAL_CENTER_CONTINUOUS_LC = 'centercontinuous';
// Mapping for horizontal alignment
const HORIZONTAL_ALIGNMENT_FOR_XLSX = [
self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT,
self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT,
self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER,
self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER_CONTINUOUS,
self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY,
self::HORIZONTAL_FILL => self::HORIZONTAL_FILL,
self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_DISTRIBUTED,
];
// Mapping for horizontal alignment CSS
const HORIZONTAL_ALIGNMENT_FOR_HTML = [
self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT,
self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT,
self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER,
self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER,
self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY,
//self::HORIZONTAL_FILL => self::HORIZONTAL_FILL, // no reasonable equivalent for fill
self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_JUSTIFY,
];
// Vertical alignment styles
const VERTICAL_BOTTOM = 'bottom';
const VERTICAL_TOP = 'top';
const VERTICAL_CENTER = 'center';
const VERTICAL_JUSTIFY = 'justify';
const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only
// Vertical alignment CSS
private const VERTICAL_BASELINE = 'baseline';
private const VERTICAL_MIDDLE = 'middle';
private const VERTICAL_SUB = 'sub';
private const VERTICAL_SUPER = 'super';
private const VERTICAL_TEXT_BOTTOM = 'text-bottom';
private const VERTICAL_TEXT_TOP = 'text-top';
// Mapping for vertical alignment
const VERTICAL_ALIGNMENT_FOR_XLSX = [
self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM,
self::VERTICAL_TOP => self::VERTICAL_TOP,
self::VERTICAL_CENTER => self::VERTICAL_CENTER,
self::VERTICAL_JUSTIFY => self::VERTICAL_JUSTIFY,
self::VERTICAL_DISTRIBUTED => self::VERTICAL_DISTRIBUTED,
// css settings that arent't in sync with Excel
self::VERTICAL_BASELINE => self::VERTICAL_BOTTOM,
self::VERTICAL_MIDDLE => self::VERTICAL_CENTER,
self::VERTICAL_SUB => self::VERTICAL_BOTTOM,
self::VERTICAL_SUPER => self::VERTICAL_TOP,
self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_BOTTOM,
self::VERTICAL_TEXT_TOP => self::VERTICAL_TOP,
];
// Mapping for vertical alignment for Html
const VERTICAL_ALIGNMENT_FOR_HTML = [
self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM,
self::VERTICAL_TOP => self::VERTICAL_TOP,
self::VERTICAL_CENTER => self::VERTICAL_MIDDLE,
self::VERTICAL_JUSTIFY => self::VERTICAL_MIDDLE,
self::VERTICAL_DISTRIBUTED => self::VERTICAL_MIDDLE,
// css settings that arent't in sync with Excel
self::VERTICAL_BASELINE => self::VERTICAL_BASELINE,
self::VERTICAL_MIDDLE => self::VERTICAL_MIDDLE,
self::VERTICAL_SUB => self::VERTICAL_SUB,
self::VERTICAL_SUPER => self::VERTICAL_SUPER,
self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_TEXT_BOTTOM,
self::VERTICAL_TEXT_TOP => self::VERTICAL_TEXT_TOP,
];
// Read order
const READORDER_CONTEXT = 0;
const READORDER_LTR = 1;
const READORDER_RTL = 2;
// Special value for Text Rotation
const TEXTROTATION_STACK_EXCEL = 255;
const TEXTROTATION_STACK_PHPSPREADSHEET = -165; // 90 - 255
/**
* Horizontal alignment.
*
* @var null|string
*/
protected $horizontal = self::HORIZONTAL_GENERAL;
/**
* Vertical alignment.
*
* @var null|string
*/
protected $vertical = self::VERTICAL_BOTTOM;
/**
* Text rotation.
*
* @var null|int
*/
protected $textRotation = 0;
/**
* Wrap text.
*
* @var bool
*/
protected $wrapText = false;
/**
* Shrink to fit.
*
* @var bool
*/
protected $shrinkToFit = false;
/**
* Indent - only possible with horizontal alignment left and right.
*
* @var int
*/
protected $indent = 0;
/**
* Read order.
*
* @var int
*/
protected $readOrder = 0;
/**
* Create a new Alignment.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
if ($isConditional) {
$this->horizontal = null;
$this->vertical = null;
$this->textRotation = null;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Alignment
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getAlignment();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['alignment' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray(
* [
* 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
* 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
* 'textRotation' => 0,
* 'wrapText' => TRUE
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())
->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['horizontal'])) {
$this->setHorizontal($styleArray['horizontal']);
}
if (isset($styleArray['vertical'])) {
$this->setVertical($styleArray['vertical']);
}
if (isset($styleArray['textRotation'])) {
$this->setTextRotation($styleArray['textRotation']);
}
if (isset($styleArray['wrapText'])) {
$this->setWrapText($styleArray['wrapText']);
}
if (isset($styleArray['shrinkToFit'])) {
$this->setShrinkToFit($styleArray['shrinkToFit']);
}
if (isset($styleArray['indent'])) {
$this->setIndent($styleArray['indent']);
}
if (isset($styleArray['readOrder'])) {
$this->setReadOrder($styleArray['readOrder']);
}
}
return $this;
}
/**
* Get Horizontal.
*
* @return null|string
*/
public function getHorizontal()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHorizontal();
}
return $this->horizontal;
}
/**
* Set Horizontal.
*
* @param string $horizontalAlignment see self::HORIZONTAL_*
*
* @return $this
*/
public function setHorizontal(string $horizontalAlignment)
{
$horizontalAlignment = strtolower($horizontalAlignment);
if ($horizontalAlignment === self::HORIZONTAL_CENTER_CONTINUOUS_LC) {
$horizontalAlignment = self::HORIZONTAL_CENTER_CONTINUOUS;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['horizontal' => $horizontalAlignment]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->horizontal = $horizontalAlignment;
}
return $this;
}
/**
* Get Vertical.
*
* @return null|string
*/
public function getVertical()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getVertical();
}
return $this->vertical;
}
/**
* Set Vertical.
*
* @param string $verticalAlignment see self::VERTICAL_*
*
* @return $this
*/
public function setVertical($verticalAlignment)
{
$verticalAlignment = strtolower($verticalAlignment);
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['vertical' => $verticalAlignment]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->vertical = $verticalAlignment;
}
return $this;
}
/**
* Get TextRotation.
*
* @return null|int
*/
public function getTextRotation()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getTextRotation();
}
return $this->textRotation;
}
/**
* Set TextRotation.
*
* @param int $angleInDegrees
*
* @return $this
*/
public function setTextRotation($angleInDegrees)
{
// Excel2007 value 255 => PhpSpreadsheet value -165
if ($angleInDegrees == self::TEXTROTATION_STACK_EXCEL) {
$angleInDegrees = self::TEXTROTATION_STACK_PHPSPREADSHEET;
}
// Set rotation
if (($angleInDegrees >= -90 && $angleInDegrees <= 90) || $angleInDegrees == self::TEXTROTATION_STACK_PHPSPREADSHEET) {
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['textRotation' => $angleInDegrees]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->textRotation = $angleInDegrees;
}
} else {
throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.');
}
return $this;
}
/**
* Get Wrap Text.
*
* @return bool
*/
public function getWrapText()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getWrapText();
}
return $this->wrapText;
}
/**
* Set Wrap Text.
*
* @param bool $wrapped
*
* @return $this
*/
public function setWrapText($wrapped)
{
if ($wrapped == '') {
$wrapped = false;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['wrapText' => $wrapped]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->wrapText = $wrapped;
}
return $this;
}
/**
* Get Shrink to fit.
*
* @return bool
*/
public function getShrinkToFit()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getShrinkToFit();
}
return $this->shrinkToFit;
}
/**
* Set Shrink to fit.
*
* @param bool $shrink
*
* @return $this
*/
public function setShrinkToFit($shrink)
{
if ($shrink == '') {
$shrink = false;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['shrinkToFit' => $shrink]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->shrinkToFit = $shrink;
}
return $this;
}
/**
* Get indent.
*
* @return int
*/
public function getIndent()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getIndent();
}
return $this->indent;
}
/**
* Set indent.
*
* @param int $indent
*
* @return $this
*/
public function setIndent($indent)
{
if ($indent > 0) {
if (
$this->getHorizontal() != self::HORIZONTAL_GENERAL &&
$this->getHorizontal() != self::HORIZONTAL_LEFT &&
$this->getHorizontal() != self::HORIZONTAL_RIGHT &&
$this->getHorizontal() != self::HORIZONTAL_DISTRIBUTED
) {
$indent = 0; // indent not supported
}
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['indent' => $indent]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->indent = $indent;
}
return $this;
}
/**
* Get read order.
*
* @return int
*/
public function getReadOrder()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getReadOrder();
}
return $this->readOrder;
}
/**
* Set read order.
*
* @param int $readOrder
*
* @return $this
*/
public function setReadOrder($readOrder)
{
if ($readOrder < 0 || $readOrder > 2) {
$readOrder = 0;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['readOrder' => $readOrder]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->readOrder = $readOrder;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->horizontal .
$this->vertical .
$this->textRotation .
($this->wrapText ? 't' : 'f') .
($this->shrinkToFit ? 't' : 'f') .
$this->indent .
$this->readOrder .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'horizontal', $this->getHorizontal());
$this->exportArray2($exportedArray, 'indent', $this->getIndent());
$this->exportArray2($exportedArray, 'readOrder', $this->getReadOrder());
$this->exportArray2($exportedArray, 'shrinkToFit', $this->getShrinkToFit());
$this->exportArray2($exportedArray, 'textRotation', $this->getTextRotation());
$this->exportArray2($exportedArray, 'vertical', $this->getVertical());
$this->exportArray2($exportedArray, 'wrapText', $this->getWrapText());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Style.php 0000644 00000066014 15243417764 0016226 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class Style extends Supervisor
{
/**
* Font.
*
* @var Font
*/
protected $font;
/**
* Fill.
*
* @var Fill
*/
protected $fill;
/**
* Borders.
*
* @var Borders
*/
protected $borders;
/**
* Alignment.
*
* @var Alignment
*/
protected $alignment;
/**
* Number Format.
*
* @var NumberFormat
*/
protected $numberFormat;
/**
* Protection.
*
* @var Protection
*/
protected $protection;
/**
* Index of style in collection. Only used for real style.
*
* @var int
*/
protected $index;
/**
* Use Quote Prefix when displaying in cell editor. Only used for real style.
*
* @var bool
*/
protected $quotePrefix = false;
/**
* Internal cache for styles
* Used when applying style on range of cells (column or row) and cleared when
* all cells in range is styled.
*
* PhpSpreadsheet will always minimize the amount of styles used. So cells with
* same styles will reference the same Style instance. To check if two styles
* are similar Style::getHashCode() is used. This call is expensive. To minimize
* the need to call this method we can cache the internal PHP object id of the
* Style in the range. Style::getHashCode() will then only be called when we
* encounter a unique style.
*
* @see Style::applyFromArray()
* @see Style::getHashCode()
*
* @var null|array<string, array>
*/
private static $cachedStyles;
/**
* Create a new Style.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
parent::__construct($isSupervisor);
// Initialise values
$this->font = new Font($isSupervisor, $isConditional);
$this->fill = new Fill($isSupervisor, $isConditional);
$this->borders = new Borders($isSupervisor, $isConditional);
$this->alignment = new Alignment($isSupervisor, $isConditional);
$this->numberFormat = new NumberFormat($isSupervisor, $isConditional);
$this->protection = new Protection($isSupervisor, $isConditional);
// bind parent if we are a supervisor
if ($isSupervisor) {
$this->font->bindParent($this);
$this->fill->bindParent($this);
$this->borders->bindParent($this);
$this->alignment->bindParent($this);
$this->numberFormat->bindParent($this);
$this->protection->bindParent($this);
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*/
public function getSharedComponent(): self
{
$activeSheet = $this->getActiveSheet();
$selectedCell = Functions::trimSheetFromCellReference($this->getActiveCell()); // e.g. 'A1'
if ($activeSheet->cellExists($selectedCell)) {
$xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
} else {
$xfIndex = 0;
}
return $activeSheet->getParentOrThrow()->getCellXfByIndex($xfIndex);
}
/**
* Get parent. Only used for style supervisor.
*/
public function getParent(): Spreadsheet
{
return $this->getActiveSheet()->getParentOrThrow();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['quotePrefix' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray(
* [
* 'font' => [
* 'name' => 'Arial',
* 'bold' => true,
* 'italic' => false,
* 'underline' => Font::UNDERLINE_DOUBLE,
* 'strikethrough' => false,
* 'color' => [
* 'rgb' => '808080'
* ]
* ],
* 'borders' => [
* 'bottom' => [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ],
* 'top' => [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ]
* ],
* 'alignment' => [
* 'horizontal' => Alignment::HORIZONTAL_CENTER,
* 'vertical' => Alignment::VERTICAL_CENTER,
* 'wrapText' => true,
* ],
* 'quotePrefix' => true
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
* @param bool $advancedBorders advanced mode for setting borders
*
* @return $this
*/
public function applyFromArray(array $styleArray, $advancedBorders = true)
{
if ($this->isSupervisor) {
$pRange = $this->getSelectedCells();
// Uppercase coordinate and strip any Worksheet reference from the selected range
$pRange = strtoupper($pRange);
if (strpos($pRange, '!') !== false) {
$pRangeWorksheet = StringHelper::strToUpper(trim(substr($pRange, 0, (int) strrpos($pRange, '!')), "'"));
if ($pRangeWorksheet !== '' && StringHelper::strToUpper($this->getActiveSheet()->getTitle()) !== $pRangeWorksheet) {
throw new Exception('Invalid Worksheet for specified Range');
}
$pRange = strtoupper(Functions::trimSheetFromCellReference($pRange));
}
// Is it a cell range or a single cell?
if (strpos($pRange, ':') === false) {
$rangeA = $pRange;
$rangeB = $pRange;
} else {
[$rangeA, $rangeB] = explode(':', $pRange);
}
// Calculate range outer borders
$rangeStart = Coordinate::coordinateFromString($rangeA);
$rangeEnd = Coordinate::coordinateFromString($rangeB);
$rangeStartIndexes = Coordinate::indexesFromString($rangeA);
$rangeEndIndexes = Coordinate::indexesFromString($rangeB);
$columnStart = $rangeStart[0];
$columnEnd = $rangeEnd[0];
// Make sure we can loop upwards on rows and columns
if ($rangeStartIndexes[0] > $rangeEndIndexes[0] && $rangeStartIndexes[1] > $rangeEndIndexes[1]) {
$tmp = $rangeStartIndexes;
$rangeStartIndexes = $rangeEndIndexes;
$rangeEndIndexes = $tmp;
}
// ADVANCED MODE:
if ($advancedBorders && isset($styleArray['borders'])) {
// 'allBorders' is a shorthand property for 'outline' and 'inside' and
// it applies to components that have not been set explicitly
if (isset($styleArray['borders']['allBorders'])) {
foreach (['outline', 'inside'] as $component) {
if (!isset($styleArray['borders'][$component])) {
$styleArray['borders'][$component] = $styleArray['borders']['allBorders'];
}
}
unset($styleArray['borders']['allBorders']); // not needed any more
}
// 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
// it applies to components that have not been set explicitly
if (isset($styleArray['borders']['outline'])) {
foreach (['top', 'right', 'bottom', 'left'] as $component) {
if (!isset($styleArray['borders'][$component])) {
$styleArray['borders'][$component] = $styleArray['borders']['outline'];
}
}
unset($styleArray['borders']['outline']); // not needed any more
}
// 'inside' is a shorthand property for 'vertical' and 'horizontal'
// it applies to components that have not been set explicitly
if (isset($styleArray['borders']['inside'])) {
foreach (['vertical', 'horizontal'] as $component) {
if (!isset($styleArray['borders'][$component])) {
$styleArray['borders'][$component] = $styleArray['borders']['inside'];
}
}
unset($styleArray['borders']['inside']); // not needed any more
}
// width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
$xMax = min($rangeEndIndexes[0] - $rangeStartIndexes[0] + 1, 3);
$yMax = min($rangeEndIndexes[1] - $rangeStartIndexes[1] + 1, 3);
// loop through up to 3 x 3 = 9 regions
for ($x = 1; $x <= $xMax; ++$x) {
// start column index for region
$colStart = ($x == 3) ?
Coordinate::stringFromColumnIndex($rangeEndIndexes[0])
: Coordinate::stringFromColumnIndex($rangeStartIndexes[0] + $x - 1);
// end column index for region
$colEnd = ($x == 1) ?
Coordinate::stringFromColumnIndex($rangeStartIndexes[0])
: Coordinate::stringFromColumnIndex($rangeEndIndexes[0] - $xMax + $x);
for ($y = 1; $y <= $yMax; ++$y) {
// which edges are touching the region
$edges = [];
if ($x == 1) {
// are we at left edge
$edges[] = 'left';
}
if ($x == $xMax) {
// are we at right edge
$edges[] = 'right';
}
if ($y == 1) {
// are we at top edge?
$edges[] = 'top';
}
if ($y == $yMax) {
// are we at bottom edge?
$edges[] = 'bottom';
}
// start row index for region
$rowStart = ($y == 3) ?
$rangeEndIndexes[1] : $rangeStartIndexes[1] + $y - 1;
// end row index for region
$rowEnd = ($y == 1) ?
$rangeStartIndexes[1] : $rangeEndIndexes[1] - $yMax + $y;
// build range for region
$range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
// retrieve relevant style array for region
$regionStyles = $styleArray;
unset($regionStyles['borders']['inside']);
// what are the inner edges of the region when looking at the selection
$innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges);
// inner edges that are not touching the region should take the 'inside' border properties if they have been set
foreach ($innerEdges as $innerEdge) {
switch ($innerEdge) {
case 'top':
case 'bottom':
// should pick up 'horizontal' border property if set
if (isset($styleArray['borders']['horizontal'])) {
$regionStyles['borders'][$innerEdge] = $styleArray['borders']['horizontal'];
} else {
unset($regionStyles['borders'][$innerEdge]);
}
break;
case 'left':
case 'right':
// should pick up 'vertical' border property if set
if (isset($styleArray['borders']['vertical'])) {
$regionStyles['borders'][$innerEdge] = $styleArray['borders']['vertical'];
} else {
unset($regionStyles['borders'][$innerEdge]);
}
break;
}
}
// apply region style to region by calling applyFromArray() in simple mode
$this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
}
}
// restore initial cell selection range
$this->getActiveSheet()->getStyle($pRange);
return $this;
}
// SIMPLE MODE:
// Selection type, inspect
if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
$selectionType = 'COLUMN';
// Enable caching of styles
self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []];
} elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) {
$selectionType = 'ROW';
// Enable caching of styles
self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []];
} else {
$selectionType = 'CELL';
}
// First loop through columns, rows, or cells to find out which styles are affected by this operation
$oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $styleArray);
// clone each of the affected styles, apply the style array, and add the new styles to the workbook
$workbook = $this->getActiveSheet()->getParentOrThrow();
$newXfIndexes = [];
foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
$style = $workbook->getCellXfByIndex($oldXfIndex);
// $cachedStyles is set when applying style for a range of cells, either column or row
if (self::$cachedStyles === null) {
// Clone the old style and apply style-array
$newStyle = clone $style;
$newStyle->applyFromArray($styleArray);
// Look for existing style we can use instead (reduce memory usage)
$existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode());
} else {
// Style cache is stored by Style::getHashCode(). But calling this method is
// expensive. So we cache the php obj id -> hash.
$objId = spl_object_id($style);
// Look for the original HashCode
$styleHash = self::$cachedStyles['hashByObjId'][$objId] ?? null;
if ($styleHash === null) {
// This object_id is not cached, store the hashcode in case encounter again
$styleHash = self::$cachedStyles['hashByObjId'][$objId] = $style->getHashCode();
}
// Find existing style by hash.
$existingStyle = self::$cachedStyles['styleByHash'][$styleHash] ?? null;
if (!$existingStyle) {
// The old style combined with the new style array is not cached, so we create it now
$newStyle = clone $style;
$newStyle->applyFromArray($styleArray);
// Look for similar style in workbook to reduce memory usage
$existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode());
// Cache the new style by original hashcode
self::$cachedStyles['styleByHash'][$styleHash] = $existingStyle instanceof self ? $existingStyle : $newStyle;
}
}
if ($existingStyle) {
// there is already such cell Xf in our collection
$newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
} else {
if (!isset($newStyle)) {
// Handle bug in PHPStan, see https://github.com/phpstan/phpstan/issues/5805
// $newStyle should always be defined.
// This block might not be needed in the future
// @codeCoverageIgnoreStart
$newStyle = clone $style;
$newStyle->applyFromArray($styleArray);
// @codeCoverageIgnoreEnd
}
// we don't have such a cell Xf, need to add
$workbook->addCellXf($newStyle);
$newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
}
}
// Loop through columns, rows, or cells again and update the XF index
switch ($selectionType) {
case 'COLUMN':
for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) {
$columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
$oldXfIndex = $columnDimension->getXfIndex();
$columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
}
// Disable caching of styles
self::$cachedStyles = null;
break;
case 'ROW':
for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) {
$rowDimension = $this->getActiveSheet()->getRowDimension($row);
// row without explicit style should be formatted based on default style
$oldXfIndex = $rowDimension->getXfIndex() ?? 0;
$rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
}
// Disable caching of styles
self::$cachedStyles = null;
break;
case 'CELL':
for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) {
for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) {
$cell = $this->getActiveSheet()->getCell([$col, $row]);
$oldXfIndex = $cell->getXfIndex();
$cell->setXfIndex($newXfIndexes[$oldXfIndex]);
}
}
break;
}
} else {
// not a supervisor, just apply the style array directly on style object
if (isset($styleArray['fill'])) {
$this->getFill()->applyFromArray($styleArray['fill']);
}
if (isset($styleArray['font'])) {
$this->getFont()->applyFromArray($styleArray['font']);
}
if (isset($styleArray['borders'])) {
$this->getBorders()->applyFromArray($styleArray['borders']);
}
if (isset($styleArray['alignment'])) {
$this->getAlignment()->applyFromArray($styleArray['alignment']);
}
if (isset($styleArray['numberFormat'])) {
$this->getNumberFormat()->applyFromArray($styleArray['numberFormat']);
}
if (isset($styleArray['protection'])) {
$this->getProtection()->applyFromArray($styleArray['protection']);
}
if (isset($styleArray['quotePrefix'])) {
$this->quotePrefix = $styleArray['quotePrefix'];
}
}
return $this;
}
private function getOldXfIndexes(string $selectionType, array $rangeStart, array $rangeEnd, string $columnStart, string $columnEnd, array $styleArray): array
{
$oldXfIndexes = [];
switch ($selectionType) {
case 'COLUMN':
for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
$oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
}
foreach ($this->getActiveSheet()->getColumnIterator($columnStart, $columnEnd) as $columnIterator) {
$cellIterator = $columnIterator->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $columnCell) {
if ($columnCell !== null) {
$columnCell->getStyle()->applyFromArray($styleArray);
}
}
}
break;
case 'ROW':
for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() === null) {
$oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
} else {
$oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
}
}
foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) {
$cellIterator = $rowIterator->getCellIterator();
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $rowCell) {
if ($rowCell !== null) {
$rowCell->getStyle()->applyFromArray($styleArray);
}
}
}
break;
case 'CELL':
for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
$oldXfIndexes[$this->getActiveSheet()->getCell([$col, $row])->getXfIndex()] = true;
}
}
break;
}
return $oldXfIndexes;
}
/**
* Get Fill.
*
* @return Fill
*/
public function getFill()
{
return $this->fill;
}
/**
* Get Font.
*
* @return Font
*/
public function getFont()
{
return $this->font;
}
/**
* Set font.
*
* @return $this
*/
public function setFont(Font $font)
{
$this->font = $font;
return $this;
}
/**
* Get Borders.
*
* @return Borders
*/
public function getBorders()
{
return $this->borders;
}
/**
* Get Alignment.
*
* @return Alignment
*/
public function getAlignment()
{
return $this->alignment;
}
/**
* Get Number Format.
*
* @return NumberFormat
*/
public function getNumberFormat()
{
return $this->numberFormat;
}
/**
* Get Conditional Styles. Only used on supervisor.
*
* @return Conditional[]
*/
public function getConditionalStyles()
{
return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
}
/**
* Set Conditional Styles. Only used on supervisor.
*
* @param Conditional[] $conditionalStyleArray Array of conditional styles
*
* @return $this
*/
public function setConditionalStyles(array $conditionalStyleArray)
{
$this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $conditionalStyleArray);
return $this;
}
/**
* Get Protection.
*
* @return Protection
*/
public function getProtection()
{
return $this->protection;
}
/**
* Get quote prefix.
*
* @return bool
*/
public function getQuotePrefix()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getQuotePrefix();
}
return $this->quotePrefix;
}
/**
* Set quote prefix.
*
* @param bool $quotePrefix
*
* @return $this
*/
public function setQuotePrefix($quotePrefix)
{
if ($quotePrefix == '') {
$quotePrefix = false;
}
if ($this->isSupervisor) {
$styleArray = ['quotePrefix' => $quotePrefix];
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->quotePrefix = (bool) $quotePrefix;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->fill->getHashCode() .
$this->font->getHashCode() .
$this->borders->getHashCode() .
$this->alignment->getHashCode() .
$this->numberFormat->getHashCode() .
$this->protection->getHashCode() .
($this->quotePrefix ? 't' : 'f') .
__CLASS__
);
}
/**
* Get own index in style collection.
*
* @return int
*/
public function getIndex()
{
return $this->index;
}
/**
* Set own index in style collection.
*
* @param int $index
*/
public function setIndex($index): void
{
$this->index = $index;
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'alignment', $this->getAlignment());
$this->exportArray2($exportedArray, 'borders', $this->getBorders());
$this->exportArray2($exportedArray, 'fill', $this->getFill());
$this->exportArray2($exportedArray, 'font', $this->getFont());
$this->exportArray2($exportedArray, 'numberFormat', $this->getNumberFormat());
$this->exportArray2($exportedArray, 'protection', $this->getProtection());
$this->exportArray2($exportedArray, 'quotePrefx', $this->getQuotePrefix());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php 0000644 00000022745 15243417764 0016017 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
class Fill extends Supervisor
{
// Fill types
const FILL_NONE = 'none';
const FILL_SOLID = 'solid';
const FILL_GRADIENT_LINEAR = 'linear';
const FILL_GRADIENT_PATH = 'path';
const FILL_PATTERN_DARKDOWN = 'darkDown';
const FILL_PATTERN_DARKGRAY = 'darkGray';
const FILL_PATTERN_DARKGRID = 'darkGrid';
const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal';
const FILL_PATTERN_DARKTRELLIS = 'darkTrellis';
const FILL_PATTERN_DARKUP = 'darkUp';
const FILL_PATTERN_DARKVERTICAL = 'darkVertical';
const FILL_PATTERN_GRAY0625 = 'gray0625';
const FILL_PATTERN_GRAY125 = 'gray125';
const FILL_PATTERN_LIGHTDOWN = 'lightDown';
const FILL_PATTERN_LIGHTGRAY = 'lightGray';
const FILL_PATTERN_LIGHTGRID = 'lightGrid';
const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal';
const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis';
const FILL_PATTERN_LIGHTUP = 'lightUp';
const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical';
const FILL_PATTERN_MEDIUMGRAY = 'mediumGray';
/**
* @var null|int
*/
public $startcolorIndex;
/**
* @var null|int
*/
public $endcolorIndex;
/**
* Fill type.
*
* @var null|string
*/
protected $fillType = self::FILL_NONE;
/**
* Rotation.
*
* @var float
*/
protected $rotation = 0.0;
/**
* Start color.
*
* @var Color
*/
protected $startColor;
/**
* End color.
*
* @var Color
*/
protected $endColor;
/** @var bool */
private $colorChanged = false;
/**
* Create a new Fill.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
* @param bool $isConditional Flag indicating if this is a conditional style or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
if ($isConditional) {
$this->fillType = null;
}
$this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional);
$this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
// bind parent if we are a supervisor
if ($isSupervisor) {
$this->startColor->bindParent($this, 'startColor');
$this->endColor->bindParent($this, 'endColor');
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Fill
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
return $parent->getSharedComponent()->getFill();
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
return ['fill' => $array];
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray(
* [
* 'fillType' => Fill::FILL_GRADIENT_LINEAR,
* 'rotation' => 0.0,
* 'startColor' => [
* 'rgb' => '000000'
* ],
* 'endColor' => [
* 'argb' => 'FFFFFFFF'
* ]
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['fillType'])) {
$this->setFillType($styleArray['fillType']);
}
if (isset($styleArray['rotation'])) {
$this->setRotation($styleArray['rotation']);
}
if (isset($styleArray['startColor'])) {
$this->getStartColor()->applyFromArray($styleArray['startColor']);
}
if (isset($styleArray['endColor'])) {
$this->getEndColor()->applyFromArray($styleArray['endColor']);
}
if (isset($styleArray['color'])) {
$this->getStartColor()->applyFromArray($styleArray['color']);
$this->getEndColor()->applyFromArray($styleArray['color']);
}
}
return $this;
}
/**
* Get Fill Type.
*
* @return null|string
*/
public function getFillType()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getFillType();
}
return $this->fillType;
}
/**
* Set Fill Type.
*
* @param string $fillType Fill type, see self::FILL_*
*
* @return $this
*/
public function setFillType($fillType)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['fillType' => $fillType]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->fillType = $fillType;
}
return $this;
}
/**
* Get Rotation.
*
* @return float
*/
public function getRotation()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getRotation();
}
return $this->rotation;
}
/**
* Set Rotation.
*
* @param float $angleInDegrees
*
* @return $this
*/
public function setRotation($angleInDegrees)
{
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->rotation = $angleInDegrees;
}
return $this;
}
/**
* Get Start Color.
*
* @return Color
*/
public function getStartColor()
{
return $this->startColor;
}
/**
* Set Start Color.
*
* @return $this
*/
public function setStartColor(Color $color)
{
$this->colorChanged = true;
// make sure parameter is a real color and not a supervisor
$color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
if ($this->isSupervisor) {
$styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->startColor = $color;
}
return $this;
}
/**
* Get End Color.
*
* @return Color
*/
public function getEndColor()
{
return $this->endColor;
}
/**
* Set End Color.
*
* @return $this
*/
public function setEndColor(Color $color)
{
$this->colorChanged = true;
// make sure parameter is a real color and not a supervisor
$color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
if ($this->isSupervisor) {
$styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->endColor = $color;
}
return $this;
}
public function getColorsChanged(): bool
{
if ($this->isSupervisor) {
$changed = $this->getSharedComponent()->colorChanged;
} else {
$changed = $this->colorChanged;
}
return $changed || $this->startColor->getHasChanged() || $this->endColor->getHasChanged();
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
// Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with
// different hashes if we don't explicitly prevent this
return md5(
$this->getFillType() .
$this->getRotation() .
($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') .
($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') .
((string) $this->getColorsChanged()) .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'fillType', $this->getFillType());
$this->exportArray2($exportedArray, 'rotation', $this->getRotation());
if ($this->getColorsChanged()) {
$this->exportArray2($exportedArray, 'endColor', $this->getEndColor());
$this->exportArray2($exportedArray, 'startColor', $this->getStartColor());
}
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/Style/Border.php 0000644 00000014712 15243417764 0016341 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Style;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Border extends Supervisor
{
// Border style
const BORDER_NONE = 'none';
const BORDER_DASHDOT = 'dashDot';
const BORDER_DASHDOTDOT = 'dashDotDot';
const BORDER_DASHED = 'dashed';
const BORDER_DOTTED = 'dotted';
const BORDER_DOUBLE = 'double';
const BORDER_HAIR = 'hair';
const BORDER_MEDIUM = 'medium';
const BORDER_MEDIUMDASHDOT = 'mediumDashDot';
const BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot';
const BORDER_MEDIUMDASHED = 'mediumDashed';
const BORDER_SLANTDASHDOT = 'slantDashDot';
const BORDER_THICK = 'thick';
const BORDER_THIN = 'thin';
const BORDER_OMIT = 'omit'; // should be used only for Conditional
/**
* Border style.
*
* @var string
*/
protected $borderStyle = self::BORDER_NONE;
/**
* Border color.
*
* @var Color
*/
protected $color;
/**
* @var null|int
*/
public $colorIndex;
/**
* Create a new Border.
*
* @param bool $isSupervisor Flag indicating if this is a supervisor or not
* Leave this value at default unless you understand exactly what
* its ramifications are
*/
public function __construct($isSupervisor = false, bool $isConditional = false)
{
// Supervisor?
parent::__construct($isSupervisor);
// Initialise values
$this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
// bind parent if we are a supervisor
if ($isSupervisor) {
$this->color->bindParent($this, 'color');
}
if ($isConditional) {
$this->borderStyle = self::BORDER_OMIT;
}
}
/**
* Get the shared style component for the currently active cell in currently active sheet.
* Only used for style supervisor.
*
* @return Border
*/
public function getSharedComponent()
{
/** @var Style */
$parent = $this->parent;
/** @var Borders $sharedComponent */
$sharedComponent = $parent->getSharedComponent();
switch ($this->parentPropertyName) {
case 'bottom':
return $sharedComponent->getBottom();
case 'diagonal':
return $sharedComponent->getDiagonal();
case 'left':
return $sharedComponent->getLeft();
case 'right':
return $sharedComponent->getRight();
case 'top':
return $sharedComponent->getTop();
}
throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.');
}
/**
* Build style array from subcomponents.
*
* @param array $array
*
* @return array
*/
public function getStyleArray($array)
{
/** @var Style */
$parent = $this->parent;
return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]);
}
/**
* Apply styles from array.
*
* <code>
* $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->getTop()->applyFromArray(
* [
* 'borderStyle' => Border::BORDER_DASHDOT,
* 'color' => [
* 'rgb' => '808080'
* ]
* ]
* );
* </code>
*
* @param array $styleArray Array containing style information
*
* @return $this
*/
public function applyFromArray(array $styleArray)
{
if ($this->isSupervisor) {
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
} else {
if (isset($styleArray['borderStyle'])) {
$this->setBorderStyle($styleArray['borderStyle']);
}
if (isset($styleArray['color'])) {
$this->getColor()->applyFromArray($styleArray['color']);
}
}
return $this;
}
/**
* Get Border style.
*
* @return string
*/
public function getBorderStyle()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getBorderStyle();
}
return $this->borderStyle;
}
/**
* Set Border style.
*
* @param bool|string $style
* When passing a boolean, FALSE equates Border::BORDER_NONE
* and TRUE to Border::BORDER_MEDIUM
*
* @return $this
*/
public function setBorderStyle($style)
{
if (empty($style)) {
$style = self::BORDER_NONE;
} elseif (is_bool($style)) {
$style = self::BORDER_MEDIUM;
}
if ($this->isSupervisor) {
$styleArray = $this->getStyleArray(['borderStyle' => $style]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->borderStyle = $style;
}
return $this;
}
/**
* Get Border Color.
*
* @return Color
*/
public function getColor()
{
return $this->color;
}
/**
* Set Border Color.
*
* @return $this
*/
public function setColor(Color $color)
{
// make sure parameter is a real color and not a supervisor
$color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
if ($this->isSupervisor) {
$styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
$this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
} else {
$this->color = $color;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
if ($this->isSupervisor) {
return $this->getSharedComponent()->getHashCode();
}
return md5(
$this->borderStyle .
$this->color->getHashCode() .
__CLASS__
);
}
protected function exportArray1(): array
{
$exportedArray = [];
$this->exportArray2($exportedArray, 'borderStyle', $this->getBorderStyle());
$this->exportArray2($exportedArray, 'color', $this->getColor());
return $exportedArray;
}
}
phpspreadsheet/src/PhpSpreadsheet/HashTable.php 0000644 00000007406 15243417764 0015661 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
/**
* @template T of IComparable
*/
class HashTable
{
/**
* HashTable elements.
*
* @var array<string, T>
*/
protected $items = [];
/**
* HashTable key map.
*
* @var array<int, string>
*/
protected $keyMap = [];
/**
* Create a new HashTable.
*
* @param T[] $source Optional source array to create HashTable from
*/
public function __construct($source = null)
{
if ($source !== null) {
// Create HashTable
$this->addFromSource($source);
}
}
/**
* Add HashTable items from source.
*
* @param T[] $source Source array to create HashTable from
*/
public function addFromSource(?array $source = null): void
{
// Check if an array was passed
if ($source === null) {
return;
}
foreach ($source as $item) {
$this->add($item);
}
}
/**
* Add HashTable item.
*
* @param T $source Item to add
*/
public function add(IComparable $source): void
{
$hash = $source->getHashCode();
if (!isset($this->items[$hash])) {
$this->items[$hash] = $source;
$this->keyMap[count($this->items) - 1] = $hash;
}
}
/**
* Remove HashTable item.
*
* @param T $source Item to remove
*/
public function remove(IComparable $source): void
{
$hash = $source->getHashCode();
if (isset($this->items[$hash])) {
unset($this->items[$hash]);
$deleteKey = -1;
foreach ($this->keyMap as $key => $value) {
if ($deleteKey >= 0) {
$this->keyMap[$key - 1] = $value;
}
if ($value == $hash) {
$deleteKey = $key;
}
}
unset($this->keyMap[count($this->keyMap) - 1]);
}
}
/**
* Clear HashTable.
*/
public function clear(): void
{
$this->items = [];
$this->keyMap = [];
}
/**
* Count.
*
* @return int
*/
public function count()
{
return count($this->items);
}
/**
* Get index for hash code.
*
* @return false|int Index
*/
public function getIndexForHashCode(string $hashCode)
{
// Scrutinizer thinks the following could return string. It is wrong.
return array_search($hashCode, $this->keyMap, true);
}
/**
* Get by index.
*
* @return null|T
*/
public function getByIndex(int $index)
{
if (isset($this->keyMap[$index])) {
return $this->getByHashCode($this->keyMap[$index]);
}
return null;
}
/**
* Get by hashcode.
*
* @return null|T
*/
public function getByHashCode(string $hashCode)
{
if (isset($this->items[$hashCode])) {
return $this->items[$hashCode];
}
return null;
}
/**
* HashTable to array.
*
* @return T[]
*/
public function toArray()
{
return $this->items;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
// each member of this class is an array
if (is_array($value)) {
$array1 = $value;
foreach ($array1 as $key1 => $value1) {
if (is_object($value1)) {
$array1[$key1] = clone $value1;
}
}
$this->$key = $array1;
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php 0000644 00000037536 15243417764 0016137 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Chart
{
/**
* Chart Name.
*
* @var string
*/
private $name = '';
/**
* Worksheet.
*
* @var ?Worksheet
*/
private $worksheet;
/**
* Chart Title.
*
* @var ?Title
*/
private $title;
/**
* Chart Legend.
*
* @var ?Legend
*/
private $legend;
/**
* X-Axis Label.
*
* @var ?Title
*/
private $xAxisLabel;
/**
* Y-Axis Label.
*
* @var ?Title
*/
private $yAxisLabel;
/**
* Chart Plot Area.
*
* @var ?PlotArea
*/
private $plotArea;
/**
* Plot Visible Only.
*
* @var bool
*/
private $plotVisibleOnly = true;
/**
* Display Blanks as.
*
* @var string
*/
private $displayBlanksAs = DataSeries::EMPTY_AS_GAP;
/**
* Chart Asix Y as.
*
* @var Axis
*/
private $yAxis;
/**
* Chart Asix X as.
*
* @var Axis
*/
private $xAxis;
/**
* Top-Left Cell Position.
*
* @var string
*/
private $topLeftCellRef = 'A1';
/**
* Top-Left X-Offset.
*
* @var int
*/
private $topLeftXOffset = 0;
/**
* Top-Left Y-Offset.
*
* @var int
*/
private $topLeftYOffset = 0;
/**
* Bottom-Right Cell Position.
*
* @var string
*/
private $bottomRightCellRef = '';
/**
* Bottom-Right X-Offset.
*
* @var int
*/
private $bottomRightXOffset = 10;
/**
* Bottom-Right Y-Offset.
*
* @var int
*/
private $bottomRightYOffset = 10;
/** @var ?int */
private $rotX;
/** @var ?int */
private $rotY;
/** @var ?int */
private $rAngAx;
/** @var ?int */
private $perspective;
/** @var bool */
private $oneCellAnchor = false;
/** @var bool */
private $autoTitleDeleted = false;
/** @var bool */
private $noFill = false;
/** @var bool */
private $roundedCorners = false;
/** @var GridLines */
private $borderLines;
/** @var ChartColor */
private $fillColor;
/**
* Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis.
*
* @param mixed $name
* @param mixed $plotVisibleOnly
* @param string $displayBlanksAs
*/
public function __construct($name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = DataSeries::EMPTY_AS_GAP, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null)
{
$this->name = $name;
$this->title = $title;
$this->legend = $legend;
$this->xAxisLabel = $xAxisLabel;
$this->yAxisLabel = $yAxisLabel;
$this->plotArea = $plotArea;
$this->plotVisibleOnly = $plotVisibleOnly;
$this->displayBlanksAs = $displayBlanksAs;
$this->xAxis = $xAxis ?? new Axis();
$this->yAxis = $yAxis ?? new Axis();
if ($majorGridlines !== null) {
$this->yAxis->setMajorGridlines($majorGridlines);
}
if ($minorGridlines !== null) {
$this->yAxis->setMinorGridlines($minorGridlines);
}
$this->fillColor = new ChartColor();
$this->borderLines = new GridLines();
}
/**
* Get Name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* Get Worksheet.
*/
public function getWorksheet(): ?Worksheet
{
return $this->worksheet;
}
/**
* Set Worksheet.
*
* @return $this
*/
public function setWorksheet(?Worksheet $worksheet = null)
{
$this->worksheet = $worksheet;
return $this;
}
public function getTitle(): ?Title
{
return $this->title;
}
/**
* Set Title.
*
* @return $this
*/
public function setTitle(Title $title)
{
$this->title = $title;
return $this;
}
public function getLegend(): ?Legend
{
return $this->legend;
}
/**
* Set Legend.
*
* @return $this
*/
public function setLegend(Legend $legend)
{
$this->legend = $legend;
return $this;
}
public function getXAxisLabel(): ?Title
{
return $this->xAxisLabel;
}
/**
* Set X-Axis Label.
*
* @return $this
*/
public function setXAxisLabel(Title $label)
{
$this->xAxisLabel = $label;
return $this;
}
public function getYAxisLabel(): ?Title
{
return $this->yAxisLabel;
}
/**
* Set Y-Axis Label.
*
* @return $this
*/
public function setYAxisLabel(Title $label)
{
$this->yAxisLabel = $label;
return $this;
}
public function getPlotArea(): ?PlotArea
{
return $this->plotArea;
}
/**
* Set Plot Area.
*/
public function setPlotArea(PlotArea $plotArea): self
{
$this->plotArea = $plotArea;
return $this;
}
/**
* Get Plot Visible Only.
*
* @return bool
*/
public function getPlotVisibleOnly()
{
return $this->plotVisibleOnly;
}
/**
* Set Plot Visible Only.
*
* @param bool $plotVisibleOnly
*
* @return $this
*/
public function setPlotVisibleOnly($plotVisibleOnly)
{
$this->plotVisibleOnly = $plotVisibleOnly;
return $this;
}
/**
* Get Display Blanks as.
*
* @return string
*/
public function getDisplayBlanksAs()
{
return $this->displayBlanksAs;
}
/**
* Set Display Blanks as.
*
* @param string $displayBlanksAs
*
* @return $this
*/
public function setDisplayBlanksAs($displayBlanksAs)
{
$this->displayBlanksAs = $displayBlanksAs;
return $this;
}
public function getChartAxisY(): Axis
{
return $this->yAxis;
}
/**
* Set yAxis.
*/
public function setChartAxisY(?Axis $axis): self
{
$this->yAxis = $axis ?? new Axis();
return $this;
}
public function getChartAxisX(): Axis
{
return $this->xAxis;
}
/**
* Set xAxis.
*/
public function setChartAxisX(?Axis $axis): self
{
$this->xAxis = $axis ?? new Axis();
return $this;
}
/**
* Get Major Gridlines.
*
* @deprecated 1.24.0 Use Axis->getMajorGridlines()
* @see Axis::getMajorGridlines()
*
* @codeCoverageIgnore
*/
public function getMajorGridlines(): ?GridLines
{
return $this->yAxis->getMajorGridLines();
}
/**
* Get Minor Gridlines.
*
* @deprecated 1.24.0 Use Axis->getMinorGridlines()
* @see Axis::getMinorGridlines()
*
* @codeCoverageIgnore
*/
public function getMinorGridlines(): ?GridLines
{
return $this->yAxis->getMinorGridLines();
}
/**
* Set the Top Left position for the chart.
*
* @param string $cellAddress
* @param int $xOffset
* @param int $yOffset
*
* @return $this
*/
public function setTopLeftPosition($cellAddress, $xOffset = null, $yOffset = null)
{
$this->topLeftCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setTopLeftXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setTopLeftYOffset($yOffset);
}
return $this;
}
/**
* Get the top left position of the chart.
*
* Returns ['cell' => string cell address, 'xOffset' => int, 'yOffset' => int].
*
* @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
*/
public function getTopLeftPosition(): array
{
return [
'cell' => $this->topLeftCellRef,
'xOffset' => $this->topLeftXOffset,
'yOffset' => $this->topLeftYOffset,
];
}
/**
* Get the cell address where the top left of the chart is fixed.
*
* @return string
*/
public function getTopLeftCell()
{
return $this->topLeftCellRef;
}
/**
* Set the Top Left cell position for the chart.
*
* @param string $cellAddress
*
* @return $this
*/
public function setTopLeftCell($cellAddress)
{
$this->topLeftCellRef = $cellAddress;
return $this;
}
/**
* Set the offset position within the Top Left cell for the chart.
*
* @param ?int $xOffset
* @param ?int $yOffset
*
* @return $this
*/
public function setTopLeftOffset($xOffset, $yOffset)
{
if ($xOffset !== null) {
$this->setTopLeftXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setTopLeftYOffset($yOffset);
}
return $this;
}
/**
* Get the offset position within the Top Left cell for the chart.
*
* @return int[]
*/
public function getTopLeftOffset()
{
return [
'X' => $this->topLeftXOffset,
'Y' => $this->topLeftYOffset,
];
}
/**
* @param int $xOffset
*
* @return $this
*/
public function setTopLeftXOffset($xOffset)
{
$this->topLeftXOffset = $xOffset;
return $this;
}
public function getTopLeftXOffset(): int
{
return $this->topLeftXOffset;
}
/**
* @param int $yOffset
*
* @return $this
*/
public function setTopLeftYOffset($yOffset)
{
$this->topLeftYOffset = $yOffset;
return $this;
}
public function getTopLeftYOffset(): int
{
return $this->topLeftYOffset;
}
/**
* Set the Bottom Right position of the chart.
*
* @param string $cellAddress
* @param int $xOffset
* @param int $yOffset
*
* @return $this
*/
public function setBottomRightPosition($cellAddress = '', $xOffset = null, $yOffset = null)
{
$this->bottomRightCellRef = $cellAddress;
if ($xOffset !== null) {
$this->setBottomRightXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setBottomRightYOffset($yOffset);
}
return $this;
}
/**
* Get the bottom right position of the chart.
*
* @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell
*/
public function getBottomRightPosition()
{
return [
'cell' => $this->bottomRightCellRef,
'xOffset' => $this->bottomRightXOffset,
'yOffset' => $this->bottomRightYOffset,
];
}
/**
* Set the Bottom Right cell for the chart.
*
* @return $this
*/
public function setBottomRightCell(string $cellAddress = '')
{
$this->bottomRightCellRef = $cellAddress;
return $this;
}
/**
* Get the cell address where the bottom right of the chart is fixed.
*/
public function getBottomRightCell(): string
{
return $this->bottomRightCellRef;
}
/**
* Set the offset position within the Bottom Right cell for the chart.
*
* @param ?int $xOffset
* @param ?int $yOffset
*
* @return $this
*/
public function setBottomRightOffset($xOffset, $yOffset)
{
if ($xOffset !== null) {
$this->setBottomRightXOffset($xOffset);
}
if ($yOffset !== null) {
$this->setBottomRightYOffset($yOffset);
}
return $this;
}
/**
* Get the offset position within the Bottom Right cell for the chart.
*
* @return int[]
*/
public function getBottomRightOffset()
{
return [
'X' => $this->bottomRightXOffset,
'Y' => $this->bottomRightYOffset,
];
}
/**
* @param int $xOffset
*
* @return $this
*/
public function setBottomRightXOffset($xOffset)
{
$this->bottomRightXOffset = $xOffset;
return $this;
}
public function getBottomRightXOffset(): int
{
return $this->bottomRightXOffset;
}
/**
* @param int $yOffset
*
* @return $this
*/
public function setBottomRightYOffset($yOffset)
{
$this->bottomRightYOffset = $yOffset;
return $this;
}
public function getBottomRightYOffset(): int
{
return $this->bottomRightYOffset;
}
public function refresh(): void
{
if ($this->worksheet !== null && $this->plotArea !== null) {
$this->plotArea->refresh($this->worksheet);
}
}
/**
* Render the chart to given file (or stream).
*
* @param string $outputDestination Name of the file render to
*
* @return bool true on success
*/
public function render($outputDestination = null)
{
if ($outputDestination == 'php://output') {
$outputDestination = null;
}
$libraryName = Settings::getChartRenderer();
if ($libraryName === null) {
return false;
}
// Ensure that data series values are up-to-date before we render
$this->refresh();
$renderer = new $libraryName($this);
return $renderer->render($outputDestination); // @phpstan-ignore-line
}
public function getRotX(): ?int
{
return $this->rotX;
}
public function setRotX(?int $rotX): self
{
$this->rotX = $rotX;
return $this;
}
public function getRotY(): ?int
{
return $this->rotY;
}
public function setRotY(?int $rotY): self
{
$this->rotY = $rotY;
return $this;
}
public function getRAngAx(): ?int
{
return $this->rAngAx;
}
public function setRAngAx(?int $rAngAx): self
{
$this->rAngAx = $rAngAx;
return $this;
}
public function getPerspective(): ?int
{
return $this->perspective;
}
public function setPerspective(?int $perspective): self
{
$this->perspective = $perspective;
return $this;
}
public function getOneCellAnchor(): bool
{
return $this->oneCellAnchor;
}
public function setOneCellAnchor(bool $oneCellAnchor): self
{
$this->oneCellAnchor = $oneCellAnchor;
return $this;
}
public function getAutoTitleDeleted(): bool
{
return $this->autoTitleDeleted;
}
public function setAutoTitleDeleted(bool $autoTitleDeleted): self
{
$this->autoTitleDeleted = $autoTitleDeleted;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getRoundedCorners(): bool
{
return $this->roundedCorners;
}
public function setRoundedCorners(?bool $roundedCorners): self
{
if ($roundedCorners !== null) {
$this->roundedCorners = $roundedCorners;
}
return $this;
}
public function getBorderLines(): GridLines
{
return $this->borderLines;
}
public function setBorderLines(GridLines $borderLines): self
{
$this->borderLines = $borderLines;
return $this;
}
public function getFillColor(): ChartColor
{
return $this->fillColor;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php 0000644 00000010406 15243417764 0016257 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class Legend
{
/** Legend positions */
const XL_LEGEND_POSITION_BOTTOM = -4107; // Below the chart.
const XL_LEGEND_POSITION_CORNER = 2; // In the upper right-hand corner of the chart border.
const XL_LEGEND_POSITION_CUSTOM = -4161; // A custom position.
const XL_LEGEND_POSITION_LEFT = -4131; // Left of the chart.
const XL_LEGEND_POSITION_RIGHT = -4152; // Right of the chart.
const XL_LEGEND_POSITION_TOP = -4160; // Above the chart.
const POSITION_RIGHT = 'r';
const POSITION_LEFT = 'l';
const POSITION_BOTTOM = 'b';
const POSITION_TOP = 't';
const POSITION_TOPRIGHT = 'tr';
const POSITION_XLREF = [
self::XL_LEGEND_POSITION_BOTTOM => self::POSITION_BOTTOM,
self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT,
self::XL_LEGEND_POSITION_CUSTOM => '??',
self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT,
self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT,
self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP,
];
/**
* Legend position.
*
* @var string
*/
private $position = self::POSITION_RIGHT;
/**
* Allow overlay of other elements?
*
* @var bool
*/
private $overlay = true;
/**
* Legend Layout.
*
* @var ?Layout
*/
private $layout;
/** @var GridLines */
private $borderLines;
/** @var ChartColor */
private $fillColor;
/** @var ?AxisText */
private $legendText;
/**
* Create a new Legend.
*
* @param string $position
* @param ?Layout $layout
* @param bool $overlay
*/
public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $overlay = false)
{
$this->setPosition($position);
$this->layout = $layout;
$this->setOverlay($overlay);
$this->borderLines = new GridLines();
$this->fillColor = new ChartColor();
}
public function getFillColor(): ChartColor
{
return $this->fillColor;
}
/**
* Get legend position as an excel string value.
*
* @return string
*/
public function getPosition()
{
return $this->position;
}
/**
* Get legend position using an excel string value.
*
* @param string $position see self::POSITION_*
*
* @return bool
*/
public function setPosition($position)
{
if (!in_array($position, self::POSITION_XLREF)) {
return false;
}
$this->position = $position;
return true;
}
/**
* Get legend position as an Excel internal numeric value.
*
* @return false|int
*/
public function getPositionXL()
{
// Scrutinizer thinks the following could return string. It is wrong.
return array_search($this->position, self::POSITION_XLREF);
}
/**
* Set legend position using an Excel internal numeric value.
*
* @param int $positionXL see self::XL_LEGEND_POSITION_*
*
* @return bool
*/
public function setPositionXL($positionXL)
{
if (!isset(self::POSITION_XLREF[$positionXL])) {
return false;
}
$this->position = self::POSITION_XLREF[$positionXL];
return true;
}
/**
* Get allow overlay of other elements?
*
* @return bool
*/
public function getOverlay()
{
return $this->overlay;
}
/**
* Set allow overlay of other elements?
*
* @param bool $overlay
*/
public function setOverlay($overlay): void
{
$this->overlay = $overlay;
}
/**
* Get Layout.
*
* @return ?Layout
*/
public function getLayout()
{
return $this->layout;
}
public function getLegendText(): ?AxisText
{
return $this->legendText;
}
public function setLegendText(?AxisText $legendText): self
{
$this->legendText = $legendText;
return $this;
}
public function getBorderLines(): GridLines
{
return $this->borderLines;
}
public function setBorderLines(GridLines $borderLines): self
{
$this->borderLines = $borderLines;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php 0000644 00000004222 15243417764 0016141 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
class Title
{
/**
* Title Caption.
*
* @var array|RichText|string
*/
private $caption = '';
/**
* Allow overlay of other elements?
*
* @var bool
*/
private $overlay = true;
/**
* Title Layout.
*
* @var ?Layout
*/
private $layout;
/**
* Create a new Title.
*
* @param array|RichText|string $caption
* @param ?Layout $layout
* @param bool $overlay
*/
public function __construct($caption = '', ?Layout $layout = null, $overlay = false)
{
$this->caption = $caption;
$this->layout = $layout;
$this->setOverlay($overlay);
}
/**
* Get caption.
*
* @return array|RichText|string
*/
public function getCaption()
{
return $this->caption;
}
public function getCaptionText(): string
{
$caption = $this->caption;
if (is_string($caption)) {
return $caption;
}
if ($caption instanceof RichText) {
return $caption->getPlainText();
}
$retVal = '';
foreach ($caption as $textx) {
/** @var RichText|string */
$text = $textx;
if ($text instanceof RichText) {
$retVal .= $text->getPlainText();
} else {
$retVal .= $text;
}
}
return $retVal;
}
/**
* Set caption.
*
* @param array|RichText|string $caption
*
* @return $this
*/
public function setCaption($caption)
{
$this->caption = $caption;
return $this;
}
/**
* Get allow overlay of other elements?
*
* @return bool
*/
public function getOverlay()
{
return $this->overlay;
}
/**
* Set allow overlay of other elements?
*
* @param bool $overlay
*/
public function setOverlay($overlay): void
{
$this->overlay = $overlay;
}
public function getLayout(): ?Layout
{
return $this->layout;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php 0000644 00000011243 15243417764 0016745 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class TrendLine extends Properties
{
const TRENDLINE_EXPONENTIAL = 'exp';
const TRENDLINE_LINEAR = 'linear';
const TRENDLINE_LOGARITHMIC = 'log';
const TRENDLINE_POLYNOMIAL = 'poly'; // + 'order'
const TRENDLINE_POWER = 'power';
const TRENDLINE_MOVING_AVG = 'movingAvg'; // + 'period'
const TRENDLINE_TYPES = [
self::TRENDLINE_EXPONENTIAL,
self::TRENDLINE_LINEAR,
self::TRENDLINE_LOGARITHMIC,
self::TRENDLINE_POLYNOMIAL,
self::TRENDLINE_POWER,
self::TRENDLINE_MOVING_AVG,
];
/** @var string */
private $trendLineType = 'linear'; // TRENDLINE_LINEAR
/** @var int */
private $order = 2;
/** @var int */
private $period = 3;
/** @var bool */
private $dispRSqr = false;
/** @var bool */
private $dispEq = false;
/** @var string */
private $name = '';
/** @var float */
private $backward = 0.0;
/** @var float */
private $forward = 0.0;
/** @var float */
private $intercept = 0.0;
/**
* Create a new TrendLine object.
*/
public function __construct(
string $trendLineType = '',
?int $order = null,
?int $period = null,
bool $dispRSqr = false,
bool $dispEq = false,
?float $backward = null,
?float $forward = null,
?float $intercept = null,
?string $name = null
) {
parent::__construct();
$this->setTrendLineProperties(
$trendLineType,
$order,
$period,
$dispRSqr,
$dispEq,
$backward,
$forward,
$intercept,
$name
);
}
public function getTrendLineType(): string
{
return $this->trendLineType;
}
public function setTrendLineType(string $trendLineType): self
{
$this->trendLineType = $trendLineType;
return $this;
}
public function getOrder(): int
{
return $this->order;
}
public function setOrder(int $order): self
{
$this->order = $order;
return $this;
}
public function getPeriod(): int
{
return $this->period;
}
public function setPeriod(int $period): self
{
$this->period = $period;
return $this;
}
public function getDispRSqr(): bool
{
return $this->dispRSqr;
}
public function setDispRSqr(bool $dispRSqr): self
{
$this->dispRSqr = $dispRSqr;
return $this;
}
public function getDispEq(): bool
{
return $this->dispEq;
}
public function setDispEq(bool $dispEq): self
{
$this->dispEq = $dispEq;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getBackward(): float
{
return $this->backward;
}
public function setBackward(float $backward): self
{
$this->backward = $backward;
return $this;
}
public function getForward(): float
{
return $this->forward;
}
public function setForward(float $forward): self
{
$this->forward = $forward;
return $this;
}
public function getIntercept(): float
{
return $this->intercept;
}
public function setIntercept(float $intercept): self
{
$this->intercept = $intercept;
return $this;
}
public function setTrendLineProperties(
?string $trendLineType = null,
?int $order = 0,
?int $period = 0,
?bool $dispRSqr = false,
?bool $dispEq = false,
?float $backward = null,
?float $forward = null,
?float $intercept = null,
?string $name = null
): self {
if (!empty($trendLineType)) {
$this->setTrendLineType($trendLineType);
}
if ($order !== null) {
$this->setOrder($order);
}
if ($period !== null) {
$this->setPeriod($period);
}
if ($dispRSqr !== null) {
$this->setDispRSqr($dispRSqr);
}
if ($dispEq !== null) {
$this->setDispEq($dispEq);
}
if ($backward !== null) {
$this->setBackward($backward);
}
if ($forward !== null) {
$this->setForward($forward);
}
if ($intercept !== null) {
$this->setIntercept($intercept);
}
if ($name !== null) {
$this->setName($name);
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php 0000644 00000000267 15243417764 0016745 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: Wiktor Trzonkowski
* Date: 7/2/14
* Time: 2:36 PM.
*/
class GridLines extends Properties
{
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php 0000644 00000000662 15243417764 0020511 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
interface IRenderer
{
/**
* IRenderer constructor.
*/
public function __construct(Chart $chart);
/**
* Render the chart to given file (or stream).
*
* @param string $filename Name of the file render to
*
* @return bool true on success
*/
public function render($filename);
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php 0000644 00000002323 15243417764 0020161 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
/**
* Jpgraph is not oficially maintained in Composer, so the version there
* could be out of date. For that reason, all unit test requiring Jpgraph
* are skipped. So, do not measure code coverage for this class till that
* is fixed.
*
* This implementation uses abandoned package
* https://packagist.org/packages/jpgraph/jpgraph
*
* @codeCoverageIgnore
*/
class JpGraph extends JpGraphRendererBase
{
protected static function init(): void
{
static $loaded = false;
if ($loaded) {
return;
}
// JpGraph is no longer included with distribution, but user may install it.
// So Scrutinizer's complaint that it can't find it is reasonable, but unfixable.
\JpGraph\JpGraph::load();
\JpGraph\JpGraph::module('bar');
\JpGraph\JpGraph::module('contour');
\JpGraph\JpGraph::module('line');
\JpGraph\JpGraph::module('pie');
\JpGraph\JpGraph::module('pie3d');
\JpGraph\JpGraph::module('radar');
\JpGraph\JpGraph::module('regstat');
\JpGraph\JpGraph::module('scatter');
\JpGraph\JpGraph::module('stock');
$loaded = true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php 0000644 00000077201 15243417764 0022452 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
use AccBarPlot;
use AccLinePlot;
use BarPlot;
use ContourPlot;
use Graph;
use GroupBarPlot;
use LinePlot;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PieGraph;
use PiePlot;
use PiePlot3D;
use PiePlotC;
use RadarGraph;
use RadarPlot;
use ScatterPlot;
use Spline;
use StockPlot;
/**
* Base class for different Jpgraph implementations as charts renderer.
*/
abstract class JpGraphRendererBase implements IRenderer
{
private static $width = 640;
private static $height = 480;
private static $colourSet = [
'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
'darkmagenta', 'coral', 'dodgerblue3', 'eggplant',
'mediumblue', 'magenta', 'sandybrown', 'cyan',
'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen',
'goldenrod2',
];
private static $markSet;
private $chart;
private $graph;
private static $plotColour = 0;
private static $plotMark = 0;
/**
* Create a new jpgraph.
*/
public function __construct(Chart $chart)
{
static::init();
$this->graph = null;
$this->chart = $chart;
self::$markSet = [
'diamond' => MARK_DIAMOND,
'square' => MARK_SQUARE,
'triangle' => MARK_UTRIANGLE,
'x' => MARK_X,
'star' => MARK_STAR,
'dot' => MARK_FILLEDCIRCLE,
'dash' => MARK_DTRIANGLE,
'circle' => MARK_CIRCLE,
'plus' => MARK_CROSS,
];
}
/**
* This method should be overriden in descendants to do real JpGraph library initialization.
*/
abstract protected static function init(): void;
private function formatPointMarker($seriesPlot, $markerID)
{
$plotMarkKeys = array_keys(self::$markSet);
if ($markerID === null) {
// Use default plot marker (next marker in the series)
self::$plotMark %= count(self::$markSet);
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
} elseif ($markerID !== 'none') {
// Use specified plot marker (if it exists)
if (isset(self::$markSet[$markerID])) {
$seriesPlot->mark->SetType(self::$markSet[$markerID]);
} else {
// If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
self::$plotMark %= count(self::$markSet);
$seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
}
} else {
// Hide plot marker
$seriesPlot->mark->Hide();
}
$seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
$seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
return $seriesPlot;
}
private function formatDataSetLabels($groupID, $datasetLabels, $rotation = '')
{
$datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? '';
// Retrieve any label formatting code
$datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
$testCurrentIndex = 0;
foreach ($datasetLabels as $i => $datasetLabel) {
if (is_array($datasetLabel)) {
if ($rotation == 'bar') {
$datasetLabels[$i] = implode(' ', $datasetLabel);
} else {
$datasetLabel = array_reverse($datasetLabel);
$datasetLabels[$i] = implode("\n", $datasetLabel);
}
} else {
// Format labels according to any formatting code
if ($datasetLabelFormatCode !== null) {
$datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
}
}
++$testCurrentIndex;
}
return $datasetLabels;
}
private function percentageSumCalculation($groupID, $seriesCount)
{
$sumValues = [];
// Adjust our values to a percentage value across all series in the group
for ($i = 0; $i < $seriesCount; ++$i) {
if ($i == 0) {
$sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
} else {
$nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
foreach ($nextValues as $k => $value) {
if (isset($sumValues[$k])) {
$sumValues[$k] += $value;
} else {
$sumValues[$k] = $value;
}
}
}
}
return $sumValues;
}
private function percentageAdjustValues($dataValues, $sumValues)
{
foreach ($dataValues as $k => $dataValue) {
$dataValues[$k] = $dataValue / $sumValues[$k] * 100;
}
return $dataValues;
}
private function getCaption($captionElement)
{
// Read any caption
$caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
// Test if we have a title caption to display
if ($caption !== null) {
// If we do, it could be a plain string or an array
if (is_array($caption)) {
// Implode an array to a plain string
$caption = implode('', $caption);
}
}
return $caption;
}
private function renderTitle(): void
{
$title = $this->getCaption($this->chart->getTitle());
if ($title !== null) {
$this->graph->title->Set($title);
}
}
private function renderLegend(): void
{
$legend = $this->chart->getLegend();
if ($legend !== null) {
$legendPosition = $legend->getPosition();
switch ($legendPosition) {
case 'r':
$this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right
$this->graph->legend->SetColumns(1);
break;
case 'l':
$this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left
$this->graph->legend->SetColumns(1);
break;
case 't':
$this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top
break;
case 'b':
$this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom
break;
default:
$this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right
$this->graph->legend->SetColumns(1);
break;
}
} else {
$this->graph->legend->Hide();
}
}
private function renderCartesianPlotArea($type = 'textlin'): void
{
$this->graph = new Graph(self::$width, self::$height);
$this->graph->SetScale($type);
$this->renderTitle();
// Rotate for bar rather than column chart
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
$reverse = $rotation == 'bar';
$xAxisLabel = $this->chart->getXAxisLabel();
if ($xAxisLabel !== null) {
$title = $this->getCaption($xAxisLabel);
if ($title !== null) {
$this->graph->xaxis->SetTitle($title, 'center');
$this->graph->xaxis->title->SetMargin(35);
if ($reverse) {
$this->graph->xaxis->title->SetAngle(90);
$this->graph->xaxis->title->SetMargin(90);
}
}
}
$yAxisLabel = $this->chart->getYAxisLabel();
if ($yAxisLabel !== null) {
$title = $this->getCaption($yAxisLabel);
if ($title !== null) {
$this->graph->yaxis->SetTitle($title, 'center');
if ($reverse) {
$this->graph->yaxis->title->SetAngle(0);
$this->graph->yaxis->title->SetMargin(-55);
}
}
}
}
private function renderPiePlotArea(): void
{
$this->graph = new PieGraph(self::$width, self::$height);
$this->renderTitle();
}
private function renderRadarPlotArea(): void
{
$this->graph = new RadarGraph(self::$width, self::$height);
$this->graph->SetScale('lin');
$this->renderTitle();
}
private function renderPlotLine($groupID, $filled = false, $combination = false): void
{
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$seriesPlots = [];
if ($grouping == 'percentStacked') {
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
} else {
$sumValues = [];
}
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i];
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker();
if ($grouping == 'percentStacked') {
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
}
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
$seriesPlot = new LinePlot($dataValues);
if ($combination) {
$seriesPlot->SetBarCenter();
}
if ($filled) {
$seriesPlot->SetFilled(true);
$seriesPlot->SetColor('black');
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
} else {
// Set the appropriate plot marker
$this->formatPointMarker($seriesPlot, $marker);
}
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue();
$seriesPlot->SetLegend($dataLabel);
$seriesPlots[] = $seriesPlot;
}
if ($grouping == 'standard') {
$groupPlot = $seriesPlots;
} else {
$groupPlot = new AccLinePlot($seriesPlots);
}
$this->graph->Add($groupPlot);
}
private function renderPlotBar($groupID, $dimensions = '2d'): void
{
$rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
// Rotate for bar rather than column chart
if (($groupID == 0) && ($rotation == 'bar')) {
$this->graph->Set90AndMargin();
}
$grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0];
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation);
// Rotate for bar rather than column chart
if ($rotation == 'bar') {
$datasetLabels = array_reverse($datasetLabels);
$this->graph->yaxis->SetPos('max');
$this->graph->yaxis->SetLabelAlign('center', 'top');
$this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
}
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$seriesPlots = [];
if ($grouping == 'percentStacked') {
$sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
} else {
$sumValues = [];
}
// Loop through each data series in turn
for ($j = 0; $j < $seriesCount; ++$j) {
$index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j];
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues();
if ($grouping == 'percentStacked') {
$dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
}
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
// Reverse the $dataValues order for bar rather than column chart
if ($rotation == 'bar') {
$dataValues = array_reverse($dataValues);
}
$seriesPlot = new BarPlot($dataValues);
$seriesPlot->SetColor('black');
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
if ($dimensions == '3d') {
$seriesPlot->SetShadow();
}
if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
$dataLabel = '';
} else {
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
}
$seriesPlot->SetLegend($dataLabel);
$seriesPlots[] = $seriesPlot;
}
// Reverse the plot order for bar rather than column chart
if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
$seriesPlots = array_reverse($seriesPlots);
}
if ($grouping == 'clustered') {
$groupPlot = new GroupBarPlot($seriesPlots);
} elseif ($grouping == 'standard') {
$groupPlot = new GroupBarPlot($seriesPlots);
} else {
$groupPlot = new AccBarPlot($seriesPlots);
if ($dimensions == '3d') {
$groupPlot->SetShadow();
}
}
$this->graph->Add($groupPlot);
}
private function renderPlotScatter($groupID, $bubble): void
{
$scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i);
if ($plotCategoryByIndex === false) {
$plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0);
}
$dataValuesY = $plotCategoryByIndex->getDataValues();
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$redoDataValuesY = true;
if ($bubble) {
if (!$bubbleSize) {
$bubbleSize = '10';
}
$redoDataValuesY = false;
foreach ($dataValuesY as $dataValueY) {
if (!is_int($dataValueY) && !is_float($dataValueY)) {
$redoDataValuesY = true;
break;
}
}
}
if ($redoDataValuesY) {
foreach ($dataValuesY as $k => $dataValueY) {
$dataValuesY[$k] = $k;
}
}
//var_dump($dataValuesY, $dataValuesX, $bubbleSize);
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
if ($scatterStyle == 'lineMarker') {
$seriesPlot->SetLinkPoints();
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
} elseif ($scatterStyle == 'smoothMarker') {
$spline = new Spline($dataValuesY, $dataValuesX);
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
$lplot = new LinePlot($splineDataX, $splineDataY);
$lplot->SetColor(self::$colourSet[self::$plotColour]);
$this->graph->Add($lplot);
}
if ($bubble) {
$this->formatPointMarker($seriesPlot, 'dot');
$seriesPlot->mark->SetColor('black');
$seriesPlot->mark->SetSize($bubbleSize);
} else {
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
$this->formatPointMarker($seriesPlot, $marker);
}
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
$seriesPlot->SetLegend($dataLabel);
$this->graph->Add($seriesPlot);
}
}
private function renderPlotRadar($groupID): void
{
$radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
$dataValues = [];
foreach ($dataValuesY as $k => $dataValueY) {
$dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY;
}
$tmp = array_shift($dataValues);
$dataValues[] = $tmp;
$tmp = array_shift($dataValuesX);
$dataValuesX[] = $tmp;
$this->graph->SetTitles(array_reverse($dataValues));
$seriesPlot = new RadarPlot(array_reverse($dataValuesX));
$dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
if ($radarStyle == 'filled') {
$seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
}
$this->formatPointMarker($seriesPlot, $marker);
$seriesPlot->SetLegend($dataLabel);
$this->graph->Add($seriesPlot);
}
}
private function renderPlotContour($groupID): void
{
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$dataValues = [];
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
$dataValues[$i] = $dataValuesX;
}
$seriesPlot = new ContourPlot($dataValues);
$this->graph->Add($seriesPlot);
}
private function renderPlotStock($groupID): void
{
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
$plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
$dataValues = [];
// Loop through each data series in turn and build the plot arrays
foreach ($plotOrder as $i => $v) {
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v);
if ($dataValuesX === false) {
continue;
}
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
foreach ($dataValuesX as $j => $dataValueX) {
$dataValues[$plotOrder[$i]][$j] = $dataValueX;
}
}
if (empty($dataValues)) {
return;
}
$dataValuesPlot = [];
// Flatten the plot arrays to a single dimensional array to work with jpgraph
$jMax = count($dataValues[0]);
for ($j = 0; $j < $jMax; ++$j) {
for ($i = 0; $i < $seriesCount; ++$i) {
$dataValuesPlot[] = $dataValues[$i][$j] ?? null;
}
}
// Set the x-axis labels
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
$this->graph->xaxis->SetTickLabels($datasetLabels);
}
$seriesPlot = new StockPlot($dataValuesPlot);
$seriesPlot->SetWidth(20);
$this->graph->Add($seriesPlot);
}
private function renderAreaChart($groupCount): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotLine($i, true, false);
}
}
private function renderLineChart($groupCount): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotLine($i, false, false);
}
}
private function renderBarChart($groupCount, $dimensions = '2d'): void
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotBar($i, $dimensions);
}
}
private function renderScatterChart($groupCount): void
{
$this->renderCartesianPlotArea('linlin');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotScatter($i, false);
}
}
private function renderBubbleChart($groupCount): void
{
$this->renderCartesianPlotArea('linlin');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotScatter($i, true);
}
}
private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void
{
$this->renderPiePlotArea();
$iLimit = ($multiplePlots) ? $groupCount : 1;
for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
$exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
$datasetLabels = [];
if ($groupID == 0) {
$labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
if ($labelCount > 0) {
$datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
$datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels);
}
}
$seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
// For pie charts, we only display the first series: doughnut charts generally display all series
$jLimit = ($multiplePlots) ? $seriesCount : 1;
// Loop through each data series in turn
for ($j = 0; $j < $jLimit; ++$j) {
$dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
// Fill in any missing values in the $dataValues array
$testCurrentIndex = 0;
foreach ($dataValues as $k => $dataValue) {
while ($k != $testCurrentIndex) {
$dataValues[$testCurrentIndex] = null;
++$testCurrentIndex;
}
++$testCurrentIndex;
}
if ($dimensions == '3d') {
$seriesPlot = new PiePlot3D($dataValues);
} else {
if ($doughnut) {
$seriesPlot = new PiePlotC($dataValues);
} else {
$seriesPlot = new PiePlot($dataValues);
}
}
if ($multiplePlots) {
$seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
}
if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) {
$seriesPlot->SetMidColor('white');
}
$seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
if (count($datasetLabels) > 0) {
$seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
}
if ($dimensions != '3d') {
$seriesPlot->SetGuideLines(false);
}
if ($j == 0) {
if ($exploded) {
$seriesPlot->ExplodeAll();
}
$seriesPlot->SetLegends($datasetLabels);
}
$this->graph->Add($seriesPlot);
}
}
}
private function renderRadarChart($groupCount): void
{
$this->renderRadarPlotArea();
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
$this->renderPlotRadar($groupID);
}
}
private function renderStockChart($groupCount): void
{
$this->renderCartesianPlotArea('intint');
for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
$this->renderPlotStock($groupID);
}
}
private function renderContourChart($groupCount): void
{
$this->renderCartesianPlotArea('intint');
for ($i = 0; $i < $groupCount; ++$i) {
$this->renderPlotContour($i);
}
}
private function renderCombinationChart($groupCount, $outputDestination)
{
$this->renderCartesianPlotArea();
for ($i = 0; $i < $groupCount; ++$i) {
$dimensions = null;
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
switch ($chartType) {
case 'area3DChart':
case 'areaChart':
$this->renderPlotLine($i, true, true);
break;
case 'bar3DChart':
$dimensions = '3d';
// no break
case 'barChart':
$this->renderPlotBar($i, $dimensions);
break;
case 'line3DChart':
case 'lineChart':
$this->renderPlotLine($i, false, true);
break;
case 'scatterChart':
$this->renderPlotScatter($i, false);
break;
case 'bubbleChart':
$this->renderPlotScatter($i, true);
break;
default:
$this->graph = null;
return false;
}
}
$this->renderLegend();
$this->graph->Stroke($outputDestination);
return true;
}
public function render($outputDestination)
{
self::$plotColour = 0;
$groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
$dimensions = null;
if ($groupCount == 1) {
$chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
} else {
$chartTypes = [];
for ($i = 0; $i < $groupCount; ++$i) {
$chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
}
$chartTypes = array_unique($chartTypes);
if (count($chartTypes) == 1) {
$chartType = array_pop($chartTypes);
} elseif (count($chartTypes) == 0) {
echo 'Chart is not yet implemented<br />';
return false;
} else {
return $this->renderCombinationChart($groupCount, $outputDestination);
}
}
switch ($chartType) {
case 'area3DChart':
$dimensions = '3d';
// no break
case 'areaChart':
$this->renderAreaChart($groupCount);
break;
case 'bar3DChart':
$dimensions = '3d';
// no break
case 'barChart':
$this->renderBarChart($groupCount, $dimensions);
break;
case 'line3DChart':
$dimensions = '3d';
// no break
case 'lineChart':
$this->renderLineChart($groupCount);
break;
case 'pie3DChart':
$dimensions = '3d';
// no break
case 'pieChart':
$this->renderPieChart($groupCount, $dimensions, false, false);
break;
case 'doughnut3DChart':
$dimensions = '3d';
// no break
case 'doughnutChart':
$this->renderPieChart($groupCount, $dimensions, true, true);
break;
case 'scatterChart':
$this->renderScatterChart($groupCount);
break;
case 'bubbleChart':
$this->renderBubbleChart($groupCount);
break;
case 'radarChart':
$this->renderRadarChart($groupCount);
break;
case 'surface3DChart':
case 'surfaceChart':
$this->renderContourChart($groupCount);
break;
case 'stockChart':
$this->renderStockChart($groupCount);
break;
default:
echo $chartType . ' is not yet implemented<br />';
return false;
}
$this->renderLegend();
$this->graph->Stroke($outputDestination);
return true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php 0000644 00000001446 15243417764 0022156 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
/**
* Jpgraph is not officially maintained by Composer at packagist.org.
*
* This renderer implementation uses package
* https://packagist.org/packages/mitoteam/jpgraph
*
* This package is up to date for June 2023 and has PHP 8.2 support.
*/
class MtJpGraphRenderer extends JpGraphRendererBase
{
protected static function init(): void
{
static $loaded = false;
if ($loaded) {
return;
}
\mitoteam\jpgraph\MtJpGraph::load([
'bar',
'contour',
'line',
'pie',
'pie3d',
'radar',
'regstat',
'scatter',
'stock',
], true); // enable Extended mode
$loaded = true;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt 0000644 00000001076 15243417764 0022726 0 ustar 00 ChartDirector
https://www.advsofteng.com/cdphp.html
GraPHPite
http://graphpite.sourceforge.net/
JpGraph
https://jpgraph.net/
Used composer packages:
https://packagist.org/packages/jpgraph/jpgraph (\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph)
https://packagist.org/packages/mitoteam/jpgraph (\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer)
LibChart
https://naku.dohcrew.com/libchart/pages/introduction/
pChart
http://pchart.sourceforge.net/
TeeChart
https://www.steema.com/
PHPGraphLib
http://www.ebrueggeman.com/phpgraphlib
phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php 0000644 00000010272 15243417764 0017122 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
class ChartColor
{
const EXCEL_COLOR_TYPE_STANDARD = 'prstClr';
const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr';
const EXCEL_COLOR_TYPE_RGB = 'srgbClr';
/** @deprecated 1.24 use EXCEL_COLOR_TYPE_RGB instead */
const EXCEL_COLOR_TYPE_ARGB = 'srgbClr';
const EXCEL_COLOR_TYPES = [
self::EXCEL_COLOR_TYPE_ARGB,
self::EXCEL_COLOR_TYPE_SCHEME,
self::EXCEL_COLOR_TYPE_STANDARD,
];
/** @var string */
private $value = '';
/** @var string */
private $type = '';
/** @var ?int */
private $alpha;
/** @var ?int */
private $brightness;
/**
* @param string|string[] $value
*/
public function __construct($value = '', ?int $alpha = null, ?string $type = null, ?int $brightness = null)
{
if (is_array($value)) {
$this->setColorPropertiesArray($value);
} else {
$this->setColorProperties($value, $alpha, $type, $brightness);
}
}
public function getValue(): string
{
return $this->value;
}
public function setValue(string $value): self
{
$this->value = $value;
return $this;
}
public function getType(): string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
public function getAlpha(): ?int
{
return $this->alpha;
}
public function setAlpha(?int $alpha): self
{
$this->alpha = $alpha;
return $this;
}
public function getBrightness(): ?int
{
return $this->brightness;
}
public function setBrightness(?int $brightness): self
{
$this->brightness = $brightness;
return $this;
}
/**
* @param null|float|int|string $alpha
* @param null|float|int|string $brightness
*/
public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self
{
if (empty($type) && !empty($color)) {
if (substr($color, 0, 1) === '*') {
$type = 'schemeClr';
$color = substr($color, 1);
} elseif (substr($color, 0, 1) === '/') {
$type = 'prstClr';
$color = substr($color, 1);
} elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) {
$type = 'srgbClr';
}
}
if ($color !== null) {
$this->setValue("$color");
}
if ($type !== null) {
$this->setType($type);
}
if ($alpha === null) {
$this->setAlpha(null);
} elseif (is_numeric($alpha)) {
$this->setAlpha((int) $alpha);
}
if ($brightness === null) {
$this->setBrightness(null);
} elseif (is_numeric($brightness)) {
$this->setBrightness((int) $brightness);
}
return $this;
}
public function setColorPropertiesArray(array $color): self
{
return $this->setColorProperties(
$color['value'] ?? '',
$color['alpha'] ?? null,
$color['type'] ?? null,
$color['brightness'] ?? null
);
}
public function isUsable(): bool
{
return $this->type !== '' && $this->value !== '';
}
/**
* Get Color Property.
*
* @param string $propertyName
*
* @return null|int|string
*/
public function getColorProperty($propertyName)
{
$retVal = null;
if ($propertyName === 'value') {
$retVal = $this->value;
} elseif ($propertyName === 'type') {
$retVal = $this->type;
} elseif ($propertyName === 'alpha') {
$retVal = $this->alpha;
} elseif ($propertyName === 'brightness') {
$retVal = $this->brightness;
}
return $retVal;
}
public static function alphaToXml(int $alpha): string
{
return (string) (100 - $alpha) . '000';
}
/**
* @param float|int|string $alpha
*/
public static function alphaFromXml($alpha): int
{
return 100 - ((int) $alpha / 1000);
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php 0000644 00000000252 15243417764 0017015 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Exception extends PhpSpreadsheetException
{
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php 0000644 00000020373 15243417764 0015771 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: Wiktor Trzonkowski
* Date: 6/17/14
* Time: 12:11 PM.
*/
class Axis extends Properties
{
const AXIS_TYPE_CATEGORY = 'catAx';
const AXIS_TYPE_DATE = 'dateAx';
const AXIS_TYPE_VALUE = 'valAx';
const TIME_UNIT_DAYS = 'days';
const TIME_UNIT_MONTHS = 'months';
const TIME_UNIT_YEARS = 'years';
public function __construct()
{
parent::__construct();
$this->fillColor = new ChartColor();
}
/**
* Chart Major Gridlines as.
*
* @var ?GridLines
*/
private $majorGridlines;
/**
* Chart Minor Gridlines as.
*
* @var ?GridLines
*/
private $minorGridlines;
/**
* Axis Number.
*
* @var mixed[]
*/
private $axisNumber = [
'format' => self::FORMAT_CODE_GENERAL,
'source_linked' => 1,
'numeric' => null,
];
/** @var string */
private $axisType = '';
/** @var ?AxisText */
private $axisText;
/**
* Axis Options.
*
* @var mixed[]
*/
private $axisOptions = [
'minimum' => null,
'maximum' => null,
'major_unit' => null,
'minor_unit' => null,
'orientation' => self::ORIENTATION_NORMAL,
'minor_tick_mark' => self::TICK_MARK_NONE,
'major_tick_mark' => self::TICK_MARK_NONE,
'axis_labels' => self::AXIS_LABELS_NEXT_TO,
'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO,
'horizontal_crosses_value' => null,
'textRotation' => null,
'hidden' => null,
'majorTimeUnit' => self::TIME_UNIT_YEARS,
'minorTimeUnit' => self::TIME_UNIT_MONTHS,
'baseTimeUnit' => self::TIME_UNIT_DAYS,
];
/**
* Fill Properties.
*
* @var ChartColor
*/
private $fillColor;
private const NUMERIC_FORMAT = [
Properties::FORMAT_CODE_NUMBER,
Properties::FORMAT_CODE_DATE,
Properties::FORMAT_CODE_DATE_ISO8601,
];
/** @var bool */
private $noFill = false;
/**
* Get Series Data Type.
*
* @param mixed $format_code
*/
public function setAxisNumberProperties($format_code, ?bool $numeric = null, int $sourceLinked = 0): void
{
$format = (string) $format_code;
$this->axisNumber['format'] = $format;
$this->axisNumber['source_linked'] = $sourceLinked;
if (is_bool($numeric)) {
$this->axisNumber['numeric'] = $numeric;
} elseif (in_array($format, self::NUMERIC_FORMAT, true)) {
$this->axisNumber['numeric'] = true;
}
}
/**
* Get Axis Number Format Data Type.
*
* @return string
*/
public function getAxisNumberFormat()
{
return $this->axisNumber['format'];
}
/**
* Get Axis Number Source Linked.
*
* @return string
*/
public function getAxisNumberSourceLinked()
{
return (string) $this->axisNumber['source_linked'];
}
public function getAxisIsNumericFormat(): bool
{
return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric'];
}
public function setAxisOption(string $key, ?string $value): void
{
if ($value !== null && $value !== '') {
$this->axisOptions[$key] = $value;
}
}
/**
* Set Axis Options Properties.
*/
public function setAxisOptionsProperties(
string $axisLabels,
?string $horizontalCrossesValue = null,
?string $horizontalCrosses = null,
?string $axisOrientation = null,
?string $majorTmt = null,
?string $minorTmt = null,
?string $minimum = null,
?string $maximum = null,
?string $majorUnit = null,
?string $minorUnit = null,
?string $textRotation = null,
?string $hidden = null,
?string $baseTimeUnit = null,
?string $majorTimeUnit = null,
?string $minorTimeUnit = null
): void {
$this->axisOptions['axis_labels'] = $axisLabels;
$this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue);
$this->setAxisOption('horizontal_crosses', $horizontalCrosses);
$this->setAxisOption('orientation', $axisOrientation);
$this->setAxisOption('major_tick_mark', $majorTmt);
$this->setAxisOption('minor_tick_mark', $minorTmt);
$this->setAxisOption('minimum', $minimum);
$this->setAxisOption('maximum', $maximum);
$this->setAxisOption('major_unit', $majorUnit);
$this->setAxisOption('minor_unit', $minorUnit);
$this->setAxisOption('textRotation', $textRotation);
$this->setAxisOption('hidden', $hidden);
$this->setAxisOption('baseTimeUnit', $baseTimeUnit);
$this->setAxisOption('majorTimeUnit', $majorTimeUnit);
$this->setAxisOption('minorTimeUnit', $minorTimeUnit);
}
/**
* Get Axis Options Property.
*
* @param string $property
*
* @return ?string
*/
public function getAxisOptionsProperty($property)
{
if ($property === 'textRotation') {
if ($this->axisText !== null) {
if ($this->axisText->getRotation() !== null) {
return (string) $this->axisText->getRotation();
}
}
}
return $this->axisOptions[$property];
}
/**
* Set Axis Orientation Property.
*
* @param string $orientation
*/
public function setAxisOrientation($orientation): void
{
$this->axisOptions['orientation'] = (string) $orientation;
}
public function getAxisType(): string
{
return $this->axisType;
}
public function setAxisType(string $type): self
{
if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) {
$this->axisType = $type;
} else {
$this->axisType = '';
}
return $this;
}
/**
* Set Fill Property.
*
* @param ?string $color
* @param ?int $alpha
* @param ?string $AlphaType
*/
public function setFillParameters($color, $alpha = null, $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void
{
$this->fillColor->setColorProperties($color, $alpha, $AlphaType);
}
/**
* Get Fill Property.
*
* @param string $property
*
* @return string
*/
public function getFillProperty($property)
{
return (string) $this->fillColor->getColorProperty($property);
}
public function getFillColorObject(): ChartColor
{
return $this->fillColor;
}
/**
* Get Line Color Property.
*
* @deprecated 1.24.0
* Use the getLineColor property in the Properties class instead
* @see Properties::getLineColorProperty()
*
* @param string $propertyName
*
* @return null|int|string
*/
public function getLineProperty($propertyName)
{
return $this->getLineColorProperty($propertyName);
}
/** @var string */
private $crossBetween = ''; // 'between' or 'midCat' might be better
public function setCrossBetween(string $crossBetween): self
{
$this->crossBetween = $crossBetween;
return $this;
}
public function getCrossBetween(): string
{
return $this->crossBetween;
}
public function getMajorGridlines(): ?GridLines
{
return $this->majorGridlines;
}
public function getMinorGridlines(): ?GridLines
{
return $this->minorGridlines;
}
public function setMajorGridlines(?GridLines $gridlines): self
{
$this->majorGridlines = $gridlines;
return $this;
}
public function setMinorGridlines(?GridLines $gridlines): self
{
$this->minorGridlines = $gridlines;
return $this;
}
public function getAxisText(): ?AxisText
{
return $this->axisText;
}
public function setAxisText(?AxisText $axisText): self
{
$this->axisText = $axisText;
return $this;
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php 0000644 00000002050 15243417764 0016626 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Style\Font;
class AxisText extends Properties
{
/** @var ?int */
private $rotation;
/** @var Font */
private $font;
public function __construct()
{
parent::__construct();
$this->font = new Font();
$this->font->setSize(null, true);
}
public function setRotation(?int $rotation): self
{
$this->rotation = $rotation;
return $this;
}
public function getRotation(): ?int
{
return $this->rotation;
}
public function getFillColorObject(): ChartColor
{
$fillColor = $this->font->getChartColor();
if ($fillColor === null) {
$fillColor = new ChartColor();
$this->font->setChartColorFromObject($fillColor);
}
return $fillColor;
}
public function getFont(): Font
{
return $this->font;
}
public function setFont(Font $font): self
{
$this->font = $font;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php 0000644 00000021445 15243417764 0017112 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataSeries
{
const TYPE_BARCHART = 'barChart';
const TYPE_BARCHART_3D = 'bar3DChart';
const TYPE_LINECHART = 'lineChart';
const TYPE_LINECHART_3D = 'line3DChart';
const TYPE_AREACHART = 'areaChart';
const TYPE_AREACHART_3D = 'area3DChart';
const TYPE_PIECHART = 'pieChart';
const TYPE_PIECHART_3D = 'pie3DChart';
const TYPE_DOUGHNUTCHART = 'doughnutChart';
const TYPE_DONUTCHART = self::TYPE_DOUGHNUTCHART; // Synonym
const TYPE_SCATTERCHART = 'scatterChart';
const TYPE_SURFACECHART = 'surfaceChart';
const TYPE_SURFACECHART_3D = 'surface3DChart';
const TYPE_RADARCHART = 'radarChart';
const TYPE_BUBBLECHART = 'bubbleChart';
const TYPE_STOCKCHART = 'stockChart';
const TYPE_CANDLECHART = self::TYPE_STOCKCHART; // Synonym
const GROUPING_CLUSTERED = 'clustered';
const GROUPING_STACKED = 'stacked';
const GROUPING_PERCENT_STACKED = 'percentStacked';
const GROUPING_STANDARD = 'standard';
const DIRECTION_BAR = 'bar';
const DIRECTION_HORIZONTAL = self::DIRECTION_BAR;
const DIRECTION_COL = 'col';
const DIRECTION_COLUMN = self::DIRECTION_COL;
const DIRECTION_VERTICAL = self::DIRECTION_COL;
const STYLE_LINEMARKER = 'lineMarker';
const STYLE_SMOOTHMARKER = 'smoothMarker';
const STYLE_MARKER = 'marker';
const STYLE_FILLED = 'filled';
const EMPTY_AS_GAP = 'gap';
const EMPTY_AS_ZERO = 'zero';
const EMPTY_AS_SPAN = 'span';
/**
* Series Plot Type.
*
* @var string
*/
private $plotType;
/**
* Plot Grouping Type.
*
* @var string
*/
private $plotGrouping;
/**
* Plot Direction.
*
* @var string
*/
private $plotDirection;
/**
* Plot Style.
*
* @var null|string
*/
private $plotStyle;
/**
* Order of plots in Series.
*
* @var int[]
*/
private $plotOrder = [];
/**
* Plot Label.
*
* @var DataSeriesValues[]
*/
private $plotLabel = [];
/**
* Plot Category.
*
* @var DataSeriesValues[]
*/
private $plotCategory = [];
/**
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*
* @var bool
*/
private $smoothLine;
/**
* Plot Values.
*
* @var DataSeriesValues[]
*/
private $plotValues = [];
/**
* Plot Bubble Sizes.
*
* @var DataSeriesValues[]
*/
private $plotBubbleSizes = [];
/**
* Create a new DataSeries.
*
* @param null|mixed $plotType
* @param null|mixed $plotGrouping
* @param int[] $plotOrder
* @param DataSeriesValues[] $plotLabel
* @param DataSeriesValues[] $plotCategory
* @param DataSeriesValues[] $plotValues
* @param null|string $plotDirection
* @param bool $smoothLine
* @param null|string $plotStyle
*/
public function __construct($plotType = null, $plotGrouping = null, array $plotOrder = [], array $plotLabel = [], array $plotCategory = [], array $plotValues = [], $plotDirection = null, $smoothLine = false, $plotStyle = null)
{
$this->plotType = $plotType;
$this->plotGrouping = $plotGrouping;
$this->plotOrder = $plotOrder;
$keys = array_keys($plotValues);
$this->plotValues = $plotValues;
if (!isset($plotLabel[$keys[0]])) {
$plotLabel[$keys[0]] = new DataSeriesValues();
}
$this->plotLabel = $plotLabel;
if (!isset($plotCategory[$keys[0]])) {
$plotCategory[$keys[0]] = new DataSeriesValues();
}
$this->plotCategory = $plotCategory;
$this->smoothLine = $smoothLine;
$this->plotStyle = $plotStyle;
if ($plotDirection === null) {
$plotDirection = self::DIRECTION_COL;
}
$this->plotDirection = $plotDirection;
}
/**
* Get Plot Type.
*
* @return string
*/
public function getPlotType()
{
return $this->plotType;
}
/**
* Set Plot Type.
*
* @param string $plotType
*
* @return $this
*/
public function setPlotType($plotType)
{
$this->plotType = $plotType;
return $this;
}
/**
* Get Plot Grouping Type.
*
* @return string
*/
public function getPlotGrouping()
{
return $this->plotGrouping;
}
/**
* Set Plot Grouping Type.
*
* @param string $groupingType
*
* @return $this
*/
public function setPlotGrouping($groupingType)
{
$this->plotGrouping = $groupingType;
return $this;
}
/**
* Get Plot Direction.
*
* @return string
*/
public function getPlotDirection()
{
return $this->plotDirection;
}
/**
* Set Plot Direction.
*
* @param string $plotDirection
*
* @return $this
*/
public function setPlotDirection($plotDirection)
{
$this->plotDirection = $plotDirection;
return $this;
}
/**
* Get Plot Order.
*
* @return int[]
*/
public function getPlotOrder()
{
return $this->plotOrder;
}
/**
* Get Plot Labels.
*
* @return DataSeriesValues[]
*/
public function getPlotLabels()
{
return $this->plotLabel;
}
/**
* Get Plot Label by Index.
*
* @param mixed $index
*
* @return DataSeriesValues|false
*/
public function getPlotLabelByIndex($index)
{
$keys = array_keys($this->plotLabel);
if (in_array($index, $keys)) {
return $this->plotLabel[$index];
}
return false;
}
/**
* Get Plot Categories.
*
* @return DataSeriesValues[]
*/
public function getPlotCategories()
{
return $this->plotCategory;
}
/**
* Get Plot Category by Index.
*
* @param mixed $index
*
* @return DataSeriesValues|false
*/
public function getPlotCategoryByIndex($index)
{
$keys = array_keys($this->plotCategory);
if (in_array($index, $keys)) {
return $this->plotCategory[$index];
} elseif (isset($keys[$index])) {
return $this->plotCategory[$keys[$index]];
}
return false;
}
/**
* Get Plot Style.
*
* @return null|string
*/
public function getPlotStyle()
{
return $this->plotStyle;
}
/**
* Set Plot Style.
*
* @param null|string $plotStyle
*
* @return $this
*/
public function setPlotStyle($plotStyle)
{
$this->plotStyle = $plotStyle;
return $this;
}
/**
* Get Plot Values.
*
* @return DataSeriesValues[]
*/
public function getPlotValues()
{
return $this->plotValues;
}
/**
* Get Plot Values by Index.
*
* @param mixed $index
*
* @return DataSeriesValues|false
*/
public function getPlotValuesByIndex($index)
{
$keys = array_keys($this->plotValues);
if (in_array($index, $keys)) {
return $this->plotValues[$index];
}
return false;
}
/**
* Get Plot Bubble Sizes.
*
* @return DataSeriesValues[]
*/
public function getPlotBubbleSizes(): array
{
return $this->plotBubbleSizes;
}
/**
* Set Plot Bubble Sizes.
*
* @param DataSeriesValues[] $plotBubbleSizes
*/
public function setPlotBubbleSizes(array $plotBubbleSizes): self
{
$this->plotBubbleSizes = $plotBubbleSizes;
return $this;
}
/**
* Get Number of Plot Series.
*
* @return int
*/
public function getPlotSeriesCount()
{
return count($this->plotValues);
}
/**
* Get Smooth Line.
*
* @return bool
*/
public function getSmoothLine()
{
return $this->smoothLine;
}
/**
* Set Smooth Line.
*
* @param bool $smoothLine
*
* @return $this
*/
public function setSmoothLine($smoothLine)
{
$this->smoothLine = $smoothLine;
return $this;
}
public function refresh(Worksheet $worksheet): void
{
foreach ($this->plotValues as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, true);
}
}
foreach ($this->plotLabel as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, true);
}
}
foreach ($this->plotCategory as $plotValues) {
if ($plotValues !== null) {
$plotValues->refresh($worksheet, false);
}
}
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php 0000644 00000007561 15243417764 0016600 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class PlotArea
{
/**
* No fill in plot area (show Excel gridlines through chart).
*
* @var bool
*/
private $noFill = false;
/**
* PlotArea Gradient Stop list.
* Each entry is a 2-element array.
* First is position in %.
* Second is ChartColor.
*
* @var array[]
*/
private $gradientFillStops = [];
/**
* PlotArea Gradient Angle.
*
* @var ?float
*/
private $gradientFillAngle;
/**
* PlotArea Layout.
*
* @var ?Layout
*/
private $layout;
/**
* Plot Series.
*
* @var DataSeries[]
*/
private $plotSeries = [];
/**
* Create a new PlotArea.
*
* @param DataSeries[] $plotSeries
*/
public function __construct(?Layout $layout = null, array $plotSeries = [])
{
$this->layout = $layout;
$this->plotSeries = $plotSeries;
}
public function getLayout(): ?Layout
{
return $this->layout;
}
/**
* Get Number of Plot Groups.
*/
public function getPlotGroupCount(): int
{
return count($this->plotSeries);
}
/**
* Get Number of Plot Series.
*
* @return int
*/
public function getPlotSeriesCount()
{
$seriesCount = 0;
foreach ($this->plotSeries as $plot) {
$seriesCount += $plot->getPlotSeriesCount();
}
return $seriesCount;
}
/**
* Get Plot Series.
*
* @return DataSeries[]
*/
public function getPlotGroup()
{
return $this->plotSeries;
}
/**
* Get Plot Series by Index.
*
* @param mixed $index
*
* @return DataSeries
*/
public function getPlotGroupByIndex($index)
{
return $this->plotSeries[$index];
}
/**
* Set Plot Series.
*
* @param DataSeries[] $plotSeries
*
* @return $this
*/
public function setPlotSeries(array $plotSeries)
{
$this->plotSeries = $plotSeries;
return $this;
}
public function refresh(Worksheet $worksheet): void
{
foreach ($this->plotSeries as $plotSeries) {
$plotSeries->refresh($worksheet);
}
}
public function setNoFill(bool $noFill): self
{
$this->noFill = $noFill;
return $this;
}
public function getNoFill(): bool
{
return $this->noFill;
}
public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self
{
$this->gradientFillStops = $gradientFillStops;
$this->gradientFillAngle = $gradientFillAngle;
return $this;
}
/**
* Get gradientFillAngle.
*/
public function getGradientFillAngle(): ?float
{
return $this->gradientFillAngle;
}
/**
* Get gradientFillStops.
*
* @return array
*/
public function getGradientFillStops()
{
return $this->gradientFillStops;
}
/** @var ?int */
private $gapWidth;
/** @var bool */
private $useUpBars = false;
/** @var bool */
private $useDownBars = false;
public function getGapWidth(): ?int
{
return $this->gapWidth;
}
public function setGapWidth(?int $gapWidth): self
{
$this->gapWidth = $gapWidth;
return $this;
}
public function getUseUpBars(): bool
{
return $this->useUpBars;
}
public function setUseUpBars(bool $useUpBars): self
{
$this->useUpBars = $useUpBars;
return $this;
}
public function getUseDownBars(): bool
{
return $this->useDownBars;
}
public function setUseDownBars(bool $useDownBars): self
{
$this->useDownBars = $useDownBars;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php 0000644 00000033161 15243417764 0020270 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class DataSeriesValues extends Properties
{
const DATASERIES_TYPE_STRING = 'String';
const DATASERIES_TYPE_NUMBER = 'Number';
private const DATA_TYPE_VALUES = [
self::DATASERIES_TYPE_STRING,
self::DATASERIES_TYPE_NUMBER,
];
/**
* Series Data Type.
*
* @var string
*/
private $dataType;
/**
* Series Data Source.
*
* @var ?string
*/
private $dataSource;
/**
* Format Code.
*
* @var string
*/
private $formatCode;
/**
* Series Point Marker.
*
* @var string
*/
private $pointMarker;
/** @var ChartColor */
private $markerFillColor;
/** @var ChartColor */
private $markerBorderColor;
/**
* Series Point Size.
*
* @var int
*/
private $pointSize = 3;
/**
* Point Count (The number of datapoints in the dataseries).
*
* @var int
*/
private $pointCount = 0;
/**
* Data Values.
*
* @var mixed[]
*/
private $dataValues = [];
/**
* Fill color (can be array with colors if dataseries have custom colors).
*
* @var null|ChartColor|ChartColor[]
*/
private $fillColor;
/** @var bool */
private $scatterLines = true;
/** @var bool */
private $bubble3D = false;
/** @var ?Layout */
private $labelLayout;
/** @var TrendLine[] */
private $trendLines = [];
/**
* Create a new DataSeriesValues object.
*
* @param string $dataType
* @param string $dataSource
* @param null|mixed $formatCode
* @param int $pointCount
* @param mixed $dataValues
* @param null|mixed $marker
* @param null|ChartColor|ChartColor[]|string|string[] $fillColor
* @param string $pointSize
*/
public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null, $pointSize = '3')
{
parent::__construct();
$this->markerFillColor = new ChartColor();
$this->markerBorderColor = new ChartColor();
$this->setDataType($dataType);
$this->dataSource = $dataSource;
$this->formatCode = $formatCode;
$this->pointCount = $pointCount;
$this->dataValues = $dataValues;
$this->pointMarker = $marker;
if ($fillColor !== null) {
$this->setFillColor($fillColor);
}
if (is_numeric($pointSize)) {
$this->pointSize = (int) $pointSize;
}
}
/**
* Get Series Data Type.
*
* @return string
*/
public function getDataType()
{
return $this->dataType;
}
/**
* Set Series Data Type.
*
* @param string $dataType Datatype of this data series
* Typical values are:
* DataSeriesValues::DATASERIES_TYPE_STRING
* Normally used for axis point values
* DataSeriesValues::DATASERIES_TYPE_NUMBER
* Normally used for chart data values
*
* @return $this
*/
public function setDataType($dataType)
{
if (!in_array($dataType, self::DATA_TYPE_VALUES)) {
throw new Exception('Invalid datatype for chart data series values');
}
$this->dataType = $dataType;
return $this;
}
/**
* Get Series Data Source (formula).
*
* @return ?string
*/
public function getDataSource()
{
return $this->dataSource;
}
/**
* Set Series Data Source (formula).
*
* @param ?string $dataSource
*
* @return $this
*/
public function setDataSource($dataSource)
{
$this->dataSource = $dataSource;
return $this;
}
/**
* Get Point Marker.
*
* @return string
*/
public function getPointMarker()
{
return $this->pointMarker;
}
/**
* Set Point Marker.
*
* @param string $marker
*
* @return $this
*/
public function setPointMarker($marker)
{
$this->pointMarker = $marker;
return $this;
}
public function getMarkerFillColor(): ChartColor
{
return $this->markerFillColor;
}
public function getMarkerBorderColor(): ChartColor
{
return $this->markerBorderColor;
}
/**
* Get Point Size.
*/
public function getPointSize(): int
{
return $this->pointSize;
}
/**
* Set Point Size.
*
* @return $this
*/
public function setPointSize(int $size = 3)
{
$this->pointSize = $size;
return $this;
}
/**
* Get Series Format Code.
*
* @return string
*/
public function getFormatCode()
{
return $this->formatCode;
}
/**
* Set Series Format Code.
*
* @param string $formatCode
*
* @return $this
*/
public function setFormatCode($formatCode)
{
$this->formatCode = $formatCode;
return $this;
}
/**
* Get Series Point Count.
*
* @return int
*/
public function getPointCount()
{
return $this->pointCount;
}
/**
* Get fill color object.
*
* @return null|ChartColor|ChartColor[]
*/
public function getFillColorObject()
{
return $this->fillColor;
}
private function stringToChartColor(string $fillString): ChartColor
{
$value = $type = '';
if (substr($fillString, 0, 1) === '*') {
$type = 'schemeClr';
$value = substr($fillString, 1);
} elseif (substr($fillString, 0, 1) === '/') {
$type = 'prstClr';
$value = substr($fillString, 1);
} elseif ($fillString !== '') {
$type = 'srgbClr';
$value = $fillString;
$this->validateColor($value);
}
return new ChartColor($value, null, $type);
}
private function chartColorToString(ChartColor $chartColor): string
{
$type = (string) $chartColor->getColorProperty('type');
$value = (string) $chartColor->getColorProperty('value');
if ($type === '' || $value === '') {
return '';
}
if ($type === 'schemeClr') {
return "*$value";
}
if ($type === 'prstClr') {
return "/$value";
}
return $value;
}
/**
* Get fill color.
*
* @return string|string[] HEX color or array with HEX colors
*/
public function getFillColor()
{
if ($this->fillColor === null) {
return '';
}
if (is_array($this->fillColor)) {
$array = [];
foreach ($this->fillColor as $chartColor) {
$array[] = $this->chartColorToString($chartColor);
}
return $array;
}
return $this->chartColorToString($this->fillColor);
}
/**
* Set fill color for series.
*
* @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors
*
* @return DataSeriesValues
*/
public function setFillColor($color)
{
if (is_array($color)) {
$this->fillColor = [];
foreach ($color as $fillString) {
if ($fillString instanceof ChartColor) {
$this->fillColor[] = $fillString;
} else {
$this->fillColor[] = $this->stringToChartColor($fillString);
}
}
} elseif ($color instanceof ChartColor) {
$this->fillColor = $color;
} else {
$this->fillColor = $this->stringToChartColor($color);
}
return $this;
}
/**
* Method for validating hex color.
*
* @param string $color value for color
*
* @return bool true if validation was successful
*/
private function validateColor($color)
{
if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
}
return true;
}
/**
* Get line width for series.
*
* @return null|float|int
*/
public function getLineWidth()
{
return $this->lineStyleProperties['width'];
}
/**
* Set line width for the series.
*
* @param null|float|int $width
*
* @return $this
*/
public function setLineWidth($width)
{
$this->lineStyleProperties['width'] = $width;
return $this;
}
/**
* Identify if the Data Series is a multi-level or a simple series.
*
* @return null|bool
*/
public function isMultiLevelSeries()
{
if (!empty($this->dataValues)) {
return is_array(array_values($this->dataValues)[0]);
}
return null;
}
/**
* Return the level count of a multi-level Data Series.
*
* @return int
*/
public function multiLevelCount()
{
$levelCount = 0;
foreach ($this->dataValues as $dataValueSet) {
$levelCount = max($levelCount, count($dataValueSet));
}
return $levelCount;
}
/**
* Get Series Data Values.
*
* @return mixed[]
*/
public function getDataValues()
{
return $this->dataValues;
}
/**
* Get the first Series Data value.
*
* @return mixed
*/
public function getDataValue()
{
$count = count($this->dataValues);
if ($count == 0) {
return null;
} elseif ($count == 1) {
return $this->dataValues[0];
}
return $this->dataValues;
}
/**
* Set Series Data Values.
*
* @param array $dataValues
*
* @return $this
*/
public function setDataValues($dataValues)
{
$this->dataValues = Functions::flattenArray($dataValues);
$this->pointCount = count($dataValues);
return $this;
}
public function refresh(Worksheet $worksheet, bool $flatten = true): void
{
if ($this->dataSource !== null) {
$calcEngine = Calculation::getInstance($worksheet->getParent());
$newDataValues = Calculation::unwrapResult(
$calcEngine->_calculateFormulaValue(
'=' . $this->dataSource,
null,
$worksheet->getCell('A1')
)
);
if ($flatten) {
$this->dataValues = Functions::flattenArray($newDataValues);
foreach ($this->dataValues as &$dataValue) {
if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
$dataValue = 0.0;
}
}
unset($dataValue);
} else {
[$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
$dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange));
if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
$this->dataValues = Functions::flattenArray($newDataValues);
} else {
$newArray = array_values(array_shift(/** @scrutinizer ignore-type */ $newDataValues));
foreach ($newArray as $i => $newDataSet) {
$newArray[$i] = [$newDataSet];
}
foreach ($newDataValues as $newDataSet) {
$i = 0;
foreach ($newDataSet as $newDataVal) {
array_unshift($newArray[$i++], $newDataVal);
}
}
$this->dataValues = $newArray;
}
}
$this->pointCount = count($this->dataValues);
}
}
public function getScatterLines(): bool
{
return $this->scatterLines;
}
public function setScatterLines(bool $scatterLines): self
{
$this->scatterLines = $scatterLines;
return $this;
}
public function getBubble3D(): bool
{
return $this->bubble3D;
}
public function setBubble3D(bool $bubble3D): self
{
$this->bubble3D = $bubble3D;
return $this;
}
/**
* Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
*
* @var bool
*/
private $smoothLine;
/**
* Get Smooth Line.
*
* @return bool
*/
public function getSmoothLine()
{
return $this->smoothLine;
}
/**
* Set Smooth Line.
*
* @param bool $smoothLine
*
* @return $this
*/
public function setSmoothLine($smoothLine)
{
$this->smoothLine = $smoothLine;
return $this;
}
public function getLabelLayout(): ?Layout
{
return $this->labelLayout;
}
public function setLabelLayout(?Layout $labelLayout): self
{
$this->labelLayout = $labelLayout;
return $this;
}
public function setTrendLines(array $trendLines): self
{
$this->trendLines = $trendLines;
return $this;
}
public function getTrendLines(): array
{
return $this->trendLines;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php 0000644 00000026334 15243417764 0016345 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
use PhpOffice\PhpSpreadsheet\Style\Font;
class Layout
{
/**
* layoutTarget.
*
* @var ?string
*/
private $layoutTarget;
/**
* X Mode.
*
* @var ?string
*/
private $xMode;
/**
* Y Mode.
*
* @var ?string
*/
private $yMode;
/**
* X-Position.
*
* @var ?float
*/
private $xPos;
/**
* Y-Position.
*
* @var ?float
*/
private $yPos;
/**
* width.
*
* @var ?float
*/
private $width;
/**
* height.
*
* @var ?float
*/
private $height;
/**
* Position - t=top.
*
* @var string
*/
private $dLblPos = '';
/** @var string */
private $numFmtCode = '';
/** @var bool */
private $numFmtLinked = false;
/**
* show legend key
* Specifies that legend keys should be shown in data labels.
*
* @var ?bool
*/
private $showLegendKey;
/**
* show value
* Specifies that the value should be shown in a data label.
*
* @var ?bool
*/
private $showVal;
/**
* show category name
* Specifies that the category name should be shown in the data label.
*
* @var ?bool
*/
private $showCatName;
/**
* show data series name
* Specifies that the series name should be shown in the data label.
*
* @var ?bool
*/
private $showSerName;
/**
* show percentage
* Specifies that the percentage should be shown in the data label.
*
* @var ?bool
*/
private $showPercent;
/**
* show bubble size.
*
* @var ?bool
*/
private $showBubbleSize;
/**
* show leader lines
* Specifies that leader lines should be shown for the data label.
*
* @var ?bool
*/
private $showLeaderLines;
/** @var ?ChartColor */
private $labelFillColor;
/** @var ?ChartColor */
private $labelBorderColor;
/** @var ?Font */
private $labelFont;
/** @var Properties */
private $labelEffects;
/**
* Create a new Layout.
*/
public function __construct(array $layout = [])
{
if (isset($layout['layoutTarget'])) {
$this->layoutTarget = $layout['layoutTarget'];
}
if (isset($layout['xMode'])) {
$this->xMode = $layout['xMode'];
}
if (isset($layout['yMode'])) {
$this->yMode = $layout['yMode'];
}
if (isset($layout['x'])) {
$this->xPos = (float) $layout['x'];
}
if (isset($layout['y'])) {
$this->yPos = (float) $layout['y'];
}
if (isset($layout['w'])) {
$this->width = (float) $layout['w'];
}
if (isset($layout['h'])) {
$this->height = (float) $layout['h'];
}
if (isset($layout['dLblPos'])) {
$this->dLblPos = (string) $layout['dLblPos'];
}
if (isset($layout['numFmtCode'])) {
$this->numFmtCode = (string) $layout['numFmtCode'];
}
$this->initBoolean($layout, 'showLegendKey');
$this->initBoolean($layout, 'showVal');
$this->initBoolean($layout, 'showCatName');
$this->initBoolean($layout, 'showSerName');
$this->initBoolean($layout, 'showPercent');
$this->initBoolean($layout, 'showBubbleSize');
$this->initBoolean($layout, 'showLeaderLines');
$this->initBoolean($layout, 'numFmtLinked');
$this->initColor($layout, 'labelFillColor');
$this->initColor($layout, 'labelBorderColor');
$labelFont = $layout['labelFont'] ?? null;
if ($labelFont instanceof Font) {
$this->labelFont = $labelFont;
}
$labelFontColor = $layout['labelFontColor'] ?? null;
if ($labelFontColor instanceof ChartColor) {
$this->setLabelFontColor($labelFontColor);
}
$labelEffects = $layout['labelEffects'] ?? null;
if ($labelEffects instanceof Properties) {
$this->labelEffects = $labelEffects;
}
}
private function initBoolean(array $layout, string $name): void
{
if (isset($layout[$name])) {
$this->$name = (bool) $layout[$name];
}
}
private function initColor(array $layout, string $name): void
{
if (isset($layout[$name]) && $layout[$name] instanceof ChartColor) {
$this->$name = $layout[$name];
}
}
/**
* Get Layout Target.
*
* @return ?string
*/
public function getLayoutTarget()
{
return $this->layoutTarget;
}
/**
* Set Layout Target.
*
* @param ?string $target
*
* @return $this
*/
public function setLayoutTarget($target)
{
$this->layoutTarget = $target;
return $this;
}
/**
* Get X-Mode.
*
* @return ?string
*/
public function getXMode()
{
return $this->xMode;
}
/**
* Set X-Mode.
*
* @param ?string $mode
*
* @return $this
*/
public function setXMode($mode)
{
$this->xMode = (string) $mode;
return $this;
}
/**
* Get Y-Mode.
*
* @return ?string
*/
public function getYMode()
{
return $this->yMode;
}
/**
* Set Y-Mode.
*
* @param ?string $mode
*
* @return $this
*/
public function setYMode($mode)
{
$this->yMode = (string) $mode;
return $this;
}
/**
* Get X-Position.
*
* @return null|float|int
*/
public function getXPosition()
{
return $this->xPos;
}
/**
* Set X-Position.
*
* @param ?float $position
*
* @return $this
*/
public function setXPosition($position)
{
$this->xPos = (float) $position;
return $this;
}
/**
* Get Y-Position.
*
* @return null|float
*/
public function getYPosition()
{
return $this->yPos;
}
/**
* Set Y-Position.
*
* @param ?float $position
*
* @return $this
*/
public function setYPosition($position)
{
$this->yPos = (float) $position;
return $this;
}
/**
* Get Width.
*
* @return ?float
*/
public function getWidth()
{
return $this->width;
}
/**
* Set Width.
*
* @param ?float $width
*
* @return $this
*/
public function setWidth($width)
{
$this->width = $width;
return $this;
}
/**
* Get Height.
*
* @return null|float
*/
public function getHeight()
{
return $this->height;
}
/**
* Set Height.
*
* @param ?float $height
*
* @return $this
*/
public function setHeight($height)
{
$this->height = $height;
return $this;
}
public function getShowLegendKey(): ?bool
{
return $this->showLegendKey;
}
/**
* Set show legend key
* Specifies that legend keys should be shown in data labels.
*/
public function setShowLegendKey(?bool $showLegendKey): self
{
$this->showLegendKey = $showLegendKey;
return $this;
}
public function getShowVal(): ?bool
{
return $this->showVal;
}
/**
* Set show val
* Specifies that the value should be shown in data labels.
*/
public function setShowVal(?bool $showDataLabelValues): self
{
$this->showVal = $showDataLabelValues;
return $this;
}
public function getShowCatName(): ?bool
{
return $this->showCatName;
}
/**
* Set show cat name
* Specifies that the category name should be shown in data labels.
*/
public function setShowCatName(?bool $showCategoryName): self
{
$this->showCatName = $showCategoryName;
return $this;
}
public function getShowSerName(): ?bool
{
return $this->showSerName;
}
/**
* Set show data series name.
* Specifies that the series name should be shown in data labels.
*/
public function setShowSerName(?bool $showSeriesName): self
{
$this->showSerName = $showSeriesName;
return $this;
}
public function getShowPercent(): ?bool
{
return $this->showPercent;
}
/**
* Set show percentage.
* Specifies that the percentage should be shown in data labels.
*/
public function setShowPercent(?bool $showPercentage): self
{
$this->showPercent = $showPercentage;
return $this;
}
public function getShowBubbleSize(): ?bool
{
return $this->showBubbleSize;
}
/**
* Set show bubble size.
* Specifies that the bubble size should be shown in data labels.
*/
public function setShowBubbleSize(?bool $showBubbleSize): self
{
$this->showBubbleSize = $showBubbleSize;
return $this;
}
public function getShowLeaderLines(): ?bool
{
return $this->showLeaderLines;
}
/**
* Set show leader lines.
* Specifies that leader lines should be shown in data labels.
*/
public function setShowLeaderLines(?bool $showLeaderLines): self
{
$this->showLeaderLines = $showLeaderLines;
return $this;
}
public function getLabelFillColor(): ?ChartColor
{
return $this->labelFillColor;
}
public function setLabelFillColor(?ChartColor $chartColor): self
{
$this->labelFillColor = $chartColor;
return $this;
}
public function getLabelBorderColor(): ?ChartColor
{
return $this->labelBorderColor;
}
public function setLabelBorderColor(?ChartColor $chartColor): self
{
$this->labelBorderColor = $chartColor;
return $this;
}
public function getLabelFont(): ?Font
{
return $this->labelFont;
}
public function getLabelEffects(): ?Properties
{
return $this->labelEffects;
}
public function getLabelFontColor(): ?ChartColor
{
if ($this->labelFont === null) {
return null;
}
return $this->labelFont->getChartColor();
}
public function setLabelFontColor(?ChartColor $chartColor): self
{
if ($this->labelFont === null) {
$this->labelFont = new Font();
$this->labelFont->setSize(null, true);
}
$this->labelFont->setChartColorFromObject($chartColor);
return $this;
}
public function getDLblPos(): string
{
return $this->dLblPos;
}
public function setDLblPos(string $dLblPos): self
{
$this->dLblPos = $dLblPos;
return $this;
}
public function getNumFmtCode(): string
{
return $this->numFmtCode;
}
public function setNumFmtCode(string $numFmtCode): self
{
$this->numFmtCode = $numFmtCode;
return $this;
}
public function getNumFmtLinked(): bool
{
return $this->numFmtLinked;
}
public function setNumFmtLinked(bool $numFmtLinked): self
{
$this->numFmtLinked = $numFmtLinked;
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php 0000644 00000075237 15243417764 0017232 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet\Chart;
/**
* Created by PhpStorm.
* User: nhw2h8s
* Date: 7/2/14
* Time: 5:45 PM.
*/
abstract class Properties
{
/** @deprecated 1.24 use constant from ChartColor instead */
const EXCEL_COLOR_TYPE_STANDARD = ChartColor::EXCEL_COLOR_TYPE_STANDARD;
/** @deprecated 1.24 use constant from ChartColor instead */
const EXCEL_COLOR_TYPE_SCHEME = ChartColor::EXCEL_COLOR_TYPE_SCHEME;
/** @deprecated 1.24 use constant from ChartColor instead */
const EXCEL_COLOR_TYPE_ARGB = ChartColor::EXCEL_COLOR_TYPE_ARGB;
const
AXIS_LABELS_LOW = 'low';
const AXIS_LABELS_HIGH = 'high';
const AXIS_LABELS_NEXT_TO = 'nextTo';
const AXIS_LABELS_NONE = 'none';
const
TICK_MARK_NONE = 'none';
const TICK_MARK_INSIDE = 'in';
const TICK_MARK_OUTSIDE = 'out';
const TICK_MARK_CROSS = 'cross';
const
HORIZONTAL_CROSSES_AUTOZERO = 'autoZero';
const HORIZONTAL_CROSSES_MAXIMUM = 'max';
const
FORMAT_CODE_GENERAL = 'General';
const FORMAT_CODE_NUMBER = '#,##0.00';
const FORMAT_CODE_CURRENCY = '$#,##0.00';
const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)';
const FORMAT_CODE_DATE = 'm/d/yyyy';
const FORMAT_CODE_DATE_ISO8601 = 'yyyy-mm-dd';
const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM';
const FORMAT_CODE_PERCENTAGE = '0.00%';
const FORMAT_CODE_FRACTION = '# ?/?';
const FORMAT_CODE_SCIENTIFIC = '0.00E+00';
const FORMAT_CODE_TEXT = '@';
const FORMAT_CODE_SPECIAL = '00000';
const
ORIENTATION_NORMAL = 'minMax';
const ORIENTATION_REVERSED = 'maxMin';
const
LINE_STYLE_COMPOUND_SIMPLE = 'sng';
const LINE_STYLE_COMPOUND_DOUBLE = 'dbl';
const LINE_STYLE_COMPOUND_THICKTHIN = 'thickThin';
const LINE_STYLE_COMPOUND_THINTHICK = 'thinThick';
const LINE_STYLE_COMPOUND_TRIPLE = 'tri';
const LINE_STYLE_DASH_SOLID = 'solid';
const LINE_STYLE_DASH_ROUND_DOT = 'sysDot';
const LINE_STYLE_DASH_SQUARE_DOT = 'sysDash';
/** @deprecated 1.24 use LINE_STYLE_DASH_SQUARE_DOT instead */
const LINE_STYLE_DASH_SQUERE_DOT = 'sysDash';
const LINE_STYPE_DASH_DASH = 'dash';
const LINE_STYLE_DASH_DASH_DOT = 'dashDot';
const LINE_STYLE_DASH_LONG_DASH = 'lgDash';
const LINE_STYLE_DASH_LONG_DASH_DOT = 'lgDashDot';
const LINE_STYLE_DASH_LONG_DASH_DOT_DOT = 'lgDashDotDot';
const LINE_STYLE_CAP_SQUARE = 'sq';
const LINE_STYLE_CAP_ROUND = 'rnd';
const LINE_STYLE_CAP_FLAT = 'flat';
const LINE_STYLE_JOIN_ROUND = 'round';
const LINE_STYLE_JOIN_MITER = 'miter';
const LINE_STYLE_JOIN_BEVEL = 'bevel';
const LINE_STYLE_ARROW_TYPE_NOARROW = null;
const LINE_STYLE_ARROW_TYPE_ARROW = 'triangle';
const LINE_STYLE_ARROW_TYPE_OPEN = 'arrow';
const LINE_STYLE_ARROW_TYPE_STEALTH = 'stealth';
const LINE_STYLE_ARROW_TYPE_DIAMOND = 'diamond';
const LINE_STYLE_ARROW_TYPE_OVAL = 'oval';
const LINE_STYLE_ARROW_SIZE_1 = 1;
const LINE_STYLE_ARROW_SIZE_2 = 2;
const LINE_STYLE_ARROW_SIZE_3 = 3;
const LINE_STYLE_ARROW_SIZE_4 = 4;
const LINE_STYLE_ARROW_SIZE_5 = 5;
const LINE_STYLE_ARROW_SIZE_6 = 6;
const LINE_STYLE_ARROW_SIZE_7 = 7;
const LINE_STYLE_ARROW_SIZE_8 = 8;
const LINE_STYLE_ARROW_SIZE_9 = 9;
const
SHADOW_PRESETS_NOSHADOW = null;
const SHADOW_PRESETS_OUTER_BOTTTOM_RIGHT = 1;
const SHADOW_PRESETS_OUTER_BOTTOM = 2;
const SHADOW_PRESETS_OUTER_BOTTOM_LEFT = 3;
const SHADOW_PRESETS_OUTER_RIGHT = 4;
const SHADOW_PRESETS_OUTER_CENTER = 5;
const SHADOW_PRESETS_OUTER_LEFT = 6;
const SHADOW_PRESETS_OUTER_TOP_RIGHT = 7;
const SHADOW_PRESETS_OUTER_TOP = 8;
const SHADOW_PRESETS_OUTER_TOP_LEFT = 9;
const SHADOW_PRESETS_INNER_BOTTTOM_RIGHT = 10;
const SHADOW_PRESETS_INNER_BOTTOM = 11;
const SHADOW_PRESETS_INNER_BOTTOM_LEFT = 12;
const SHADOW_PRESETS_INNER_RIGHT = 13;
const SHADOW_PRESETS_INNER_CENTER = 14;
const SHADOW_PRESETS_INNER_LEFT = 15;
const SHADOW_PRESETS_INNER_TOP_RIGHT = 16;
const SHADOW_PRESETS_INNER_TOP = 17;
const SHADOW_PRESETS_INNER_TOP_LEFT = 18;
const SHADOW_PRESETS_PERSPECTIVE_BELOW = 19;
const SHADOW_PRESETS_PERSPECTIVE_UPPER_RIGHT = 20;
const SHADOW_PRESETS_PERSPECTIVE_UPPER_LEFT = 21;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22;
const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23;
const POINTS_WIDTH_MULTIPLIER = 12700;
const ANGLE_MULTIPLIER = 60000; // direction and size-kx size-ky
const PERCENTAGE_MULTIPLIER = 100000; // size sx and sy
/** @var bool */
protected $objectState = false; // used only for minor gridlines
/** @var ?float */
protected $glowSize;
/** @var ChartColor */
protected $glowColor;
/** @var array */
protected $softEdges = [
'size' => null,
];
/** @var array */
protected $shadowProperties = self::PRESETS_OPTIONS[0];
/** @var ChartColor */
protected $shadowColor;
public function __construct()
{
$this->lineColor = new ChartColor();
$this->glowColor = new ChartColor();
$this->shadowColor = new ChartColor();
$this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD);
$this->shadowColor->setValue('black');
$this->shadowColor->setAlpha(40);
}
/**
* Get Object State.
*
* @return bool
*/
public function getObjectState()
{
return $this->objectState;
}
/**
* Change Object State to True.
*
* @return $this
*/
public function activateObject()
{
$this->objectState = true;
return $this;
}
public static function pointsToXml(float $width): string
{
return (string) (int) ($width * self::POINTS_WIDTH_MULTIPLIER);
}
public static function xmlToPoints(string $width): float
{
return ((float) $width) / self::POINTS_WIDTH_MULTIPLIER;
}
public static function angleToXml(float $angle): string
{
return (string) (int) ($angle * self::ANGLE_MULTIPLIER);
}
public static function xmlToAngle(string $angle): float
{
return ((float) $angle) / self::ANGLE_MULTIPLIER;
}
public static function tenthOfPercentToXml(float $value): string
{
return (string) (int) ($value * self::PERCENTAGE_MULTIPLIER);
}
public static function xmlToTenthOfPercent(string $value): float
{
return ((float) $value) / self::PERCENTAGE_MULTIPLIER;
}
/**
* @param null|float|int|string $alpha
*/
protected function setColorProperties(?string $color, $alpha, ?string $colorType): array
{
return [
'type' => $colorType,
'value' => $color,
'alpha' => ($alpha === null) ? null : (int) $alpha,
];
}
protected const PRESETS_OPTIONS = [
//NONE
0 => [
'presets' => self::SHADOW_PRESETS_NOSHADOW,
'effect' => null,
//'color' => [
// 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD,
// 'value' => 'black',
// 'alpha' => 40,
//],
'size' => [
'sx' => null,
'sy' => null,
'kx' => null,
'ky' => null,
],
'blur' => null,
'direction' => null,
'distance' => null,
'algn' => null,
'rotWithShape' => null,
],
//OUTER
1 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
'algn' => 'tl',
'rotWithShape' => '0',
],
2 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
'algn' => 't',
'rotWithShape' => '0',
],
3 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
'algn' => 'tr',
'rotWithShape' => '0',
],
4 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'algn' => 'l',
'rotWithShape' => '0',
],
5 => [
'effect' => 'outerShdw',
'size' => [
'sx' => 102000 / self::PERCENTAGE_MULTIPLIER,
'sy' => 102000 / self::PERCENTAGE_MULTIPLIER,
],
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'algn' => 'ctr',
'rotWithShape' => '0',
],
6 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 10800000 / self::ANGLE_MULTIPLIER,
'algn' => 'r',
'rotWithShape' => '0',
],
7 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
'algn' => 'bl',
'rotWithShape' => '0',
],
8 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 16200000 / self::ANGLE_MULTIPLIER,
'rotWithShape' => '0',
],
9 => [
'effect' => 'outerShdw',
'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
'algn' => 'br',
'rotWithShape' => '0',
],
//INNER
10 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
],
11 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
],
12 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
],
13 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
],
14 => [
'effect' => 'innerShdw',
'blur' => 114300 / self::POINTS_WIDTH_MULTIPLIER,
],
15 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 10800000 / self::ANGLE_MULTIPLIER,
],
16 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
],
17 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 16200000 / self::ANGLE_MULTIPLIER,
],
18 => [
'effect' => 'innerShdw',
'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
],
//perspective
19 => [
'effect' => 'outerShdw',
'blur' => 152400 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 317500 / self::POINTS_WIDTH_MULTIPLIER,
'size' => [
'sx' => 90000 / self::PERCENTAGE_MULTIPLIER,
'sy' => -19000 / self::PERCENTAGE_MULTIPLIER,
],
'direction' => 5400000 / self::ANGLE_MULTIPLIER,
'rotWithShape' => '0',
],
20 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 18900000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => 23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => -1200000 / self::ANGLE_MULTIPLIER,
],
'algn' => 'bl',
'rotWithShape' => '0',
],
21 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 13500000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => 23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => 1200000 / self::ANGLE_MULTIPLIER,
],
'algn' => 'br',
'rotWithShape' => '0',
],
22 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 2700000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => -23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => -800400 / self::ANGLE_MULTIPLIER,
],
'algn' => 'bl',
'rotWithShape' => '0',
],
23 => [
'effect' => 'outerShdw',
'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER,
'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER,
'direction' => 8100000 / self::ANGLE_MULTIPLIER,
'size' => [
'sy' => -23000 / self::PERCENTAGE_MULTIPLIER,
'kx' => 800400 / self::ANGLE_MULTIPLIER,
],
'algn' => 'br',
'rotWithShape' => '0',
],
];
protected function getShadowPresetsMap(int $presetsOption): array
{
return self::PRESETS_OPTIONS[$presetsOption] ?? self::PRESETS_OPTIONS[0];
}
/**
* Get value of array element.
*
* @param mixed $properties
* @param mixed $elements
*
* @return mixed
*/
protected function getArrayElementsValue($properties, $elements)
{
$reference = &$properties;
if (!is_array($elements)) {
return $reference[$elements];
}
foreach ($elements as $keys) {
$reference = &$reference[$keys];
}
return $reference;
}
/**
* Set Glow Properties.
*
* @param float $size
* @param ?string $colorValue
* @param ?int $colorAlpha
* @param ?string $colorType
*/
public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void
{
$this
->activateObject()
->setGlowSize($size);
$this->glowColor->setColorPropertiesArray(
[
'value' => $colorValue,
'type' => $colorType,
'alpha' => $colorAlpha,
]
);
}
/**
* Get Glow Property.
*
* @param array|string $property
*
* @return null|array|float|int|string
*/
public function getGlowProperty($property)
{
$retVal = null;
if ($property === 'size') {
$retVal = $this->glowSize;
} elseif ($property === 'color') {
$retVal = [
'value' => $this->glowColor->getColorProperty('value'),
'type' => $this->glowColor->getColorProperty('type'),
'alpha' => $this->glowColor->getColorProperty('alpha'),
];
} elseif (is_array($property) && count($property) >= 2 && $property[0] === 'color') {
$retVal = $this->glowColor->getColorProperty($property[1]);
}
return $retVal;
}
/**
* Get Glow Color Property.
*
* @param string $propertyName
*
* @return null|int|string
*/
public function getGlowColor($propertyName)
{
return $this->glowColor->getColorProperty($propertyName);
}
public function getGlowColorObject(): ChartColor
{
return $this->glowColor;
}
/**
* Get Glow Size.
*
* @return ?float
*/
public function getGlowSize()
{
return $this->glowSize;
}
/**
* Set Glow Size.
*
* @param ?float $size
*
* @return $this
*/
protected function setGlowSize($size)
{
$this->glowSize = $size;
return $this;
}
/**
* Set Soft Edges Size.
*
* @param ?float $size
*/
public function setSoftEdges($size): void
{
if ($size !== null) {
$this->activateObject();
$this->softEdges['size'] = $size;
}
}
/**
* Get Soft Edges Size.
*
* @return string
*/
public function getSoftEdgesSize()
{
return $this->softEdges['size'];
}
/**
* @param mixed $value
*/
public function setShadowProperty(string $propertyName, $value): self
{
$this->activateObject();
if ($propertyName === 'color' && is_array($value)) {
$this->shadowColor->setColorPropertiesArray($value);
} else {
$this->shadowProperties[$propertyName] = $value;
}
return $this;
}
/**
* Set Shadow Properties.
*
* @param int $presets
* @param string $colorValue
* @param string $colorType
* @param null|float|int|string $colorAlpha
* @param null|float $blur
* @param null|int $angle
* @param null|float $distance
*/
public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void
{
$this->activateObject()->setShadowPresetsProperties((int) $presets);
if ($presets === 0) {
$this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD);
$this->shadowColor->setValue('black');
$this->shadowColor->setAlpha(40);
}
if ($colorValue !== null) {
$this->shadowColor->setValue($colorValue);
}
if ($colorType !== null) {
$this->shadowColor->setType($colorType);
}
if (is_numeric($colorAlpha)) {
$this->shadowColor->setAlpha((int) $colorAlpha);
}
$this
->setShadowBlur($blur)
->setShadowAngle($angle)
->setShadowDistance($distance);
}
/**
* Set Shadow Presets Properties.
*
* @param int $presets
*
* @return $this
*/
protected function setShadowPresetsProperties($presets)
{
$this->shadowProperties['presets'] = $presets;
$this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets));
return $this;
}
protected const SHADOW_ARRAY_KEYS = ['size', 'color'];
/**
* Set Shadow Properties Values.
*
* @param mixed $reference
*
* @return $this
*/
protected function setShadowPropertiesMapValues(array $propertiesMap, &$reference = null)
{
$base_reference = $reference;
foreach ($propertiesMap as $property_key => $property_val) {
if (is_array($property_val)) {
if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) {
$reference = &$this->shadowProperties[$property_key];
$this->setShadowPropertiesMapValues($property_val, $reference);
}
} else {
if ($base_reference === null) {
$this->shadowProperties[$property_key] = $property_val;
} else {
$reference[$property_key] = $property_val;
}
}
}
return $this;
}
/**
* Set Shadow Blur.
*
* @param ?float $blur
*
* @return $this
*/
protected function setShadowBlur($blur)
{
if ($blur !== null) {
$this->shadowProperties['blur'] = $blur;
}
return $this;
}
/**
* Set Shadow Angle.
*
* @param null|float|int|string $angle
*
* @return $this
*/
protected function setShadowAngle($angle)
{
if (is_numeric($angle)) {
$this->shadowProperties['direction'] = $angle;
}
return $this;
}
/**
* Set Shadow Distance.
*
* @param ?float $distance
*
* @return $this
*/
protected function setShadowDistance($distance)
{
if ($distance !== null) {
$this->shadowProperties['distance'] = $distance;
}
return $this;
}
public function getShadowColorObject(): ChartColor
{
return $this->shadowColor;
}
/**
* Get Shadow Property.
*
* @param string|string[] $elements
*
* @return array|string
*/
public function getShadowProperty($elements)
{
if ($elements === 'color') {
return [
'value' => $this->shadowColor->getValue(),
'type' => $this->shadowColor->getType(),
'alpha' => $this->shadowColor->getAlpha(),
];
}
return $this->getArrayElementsValue($this->shadowProperties, $elements);
}
public function getShadowArray(): array
{
$array = $this->shadowProperties;
if ($this->getShadowColorObject()->isUsable()) {
$array['color'] = $this->getShadowProperty('color');
}
return $array;
}
/** @var ChartColor */
protected $lineColor;
/** @var array */
protected $lineStyleProperties = [
'width' => null, //'9525',
'compound' => '', //self::LINE_STYLE_COMPOUND_SIMPLE,
'dash' => '', //self::LINE_STYLE_DASH_SOLID,
'cap' => '', //self::LINE_STYLE_CAP_FLAT,
'join' => '', //self::LINE_STYLE_JOIN_BEVEL,
'arrow' => [
'head' => [
'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW,
'size' => '', //self::LINE_STYLE_ARROW_SIZE_5,
'w' => '',
'len' => '',
],
'end' => [
'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW,
'size' => '', //self::LINE_STYLE_ARROW_SIZE_8,
'w' => '',
'len' => '',
],
],
];
public function copyLineStyles(self $otherProperties): void
{
$this->lineStyleProperties = $otherProperties->lineStyleProperties;
$this->lineColor = $otherProperties->lineColor;
$this->glowSize = $otherProperties->glowSize;
$this->glowColor = $otherProperties->glowColor;
$this->softEdges = $otherProperties->softEdges;
$this->shadowProperties = $otherProperties->shadowProperties;
}
public function getLineColor(): ChartColor
{
return $this->lineColor;
}
/**
* Set Line Color Properties.
*
* @param string $value
* @param ?int $alpha
* @param ?string $colorType
*/
public function setLineColorProperties($value, $alpha = null, $colorType = null): void
{
$this->activateObject();
$this->lineColor->setColorPropertiesArray(
$this->setColorProperties(
$value,
$alpha,
$colorType
)
);
}
/**
* Get Line Color Property.
*
* @param string $propertyName
*
* @return null|int|string
*/
public function getLineColorProperty($propertyName)
{
return $this->lineColor->getColorProperty($propertyName);
}
/**
* Set Line Style Properties.
*
* @param null|float|int|string $lineWidth
* @param string $compoundType
* @param string $dashType
* @param string $capType
* @param string $joinType
* @param string $headArrowType
* @param string $headArrowSize
* @param string $endArrowType
* @param string $endArrowSize
* @param string $headArrowWidth
* @param string $headArrowLength
* @param string $endArrowWidth
* @param string $endArrowLength
*/
public function setLineStyleProperties($lineWidth = null, $compoundType = '', $dashType = '', $capType = '', $joinType = '', $headArrowType = '', $headArrowSize = '', $endArrowType = '', $endArrowSize = '', $headArrowWidth = '', $headArrowLength = '', $endArrowWidth = '', $endArrowLength = ''): void
{
$this->activateObject();
if (is_numeric($lineWidth)) {
$this->lineStyleProperties['width'] = $lineWidth;
}
if ($compoundType !== '') {
$this->lineStyleProperties['compound'] = $compoundType;
}
if ($dashType !== '') {
$this->lineStyleProperties['dash'] = $dashType;
}
if ($capType !== '') {
$this->lineStyleProperties['cap'] = $capType;
}
if ($joinType !== '') {
$this->lineStyleProperties['join'] = $joinType;
}
if ($headArrowType !== '') {
$this->lineStyleProperties['arrow']['head']['type'] = $headArrowType;
}
if (array_key_exists($headArrowSize, self::ARROW_SIZES)) {
$this->lineStyleProperties['arrow']['head']['size'] = $headArrowSize;
$this->lineStyleProperties['arrow']['head']['w'] = self::ARROW_SIZES[$headArrowSize]['w'];
$this->lineStyleProperties['arrow']['head']['len'] = self::ARROW_SIZES[$headArrowSize]['len'];
}
if ($endArrowType !== '') {
$this->lineStyleProperties['arrow']['end']['type'] = $endArrowType;
}
if (array_key_exists($endArrowSize, self::ARROW_SIZES)) {
$this->lineStyleProperties['arrow']['end']['size'] = $endArrowSize;
$this->lineStyleProperties['arrow']['end']['w'] = self::ARROW_SIZES[$endArrowSize]['w'];
$this->lineStyleProperties['arrow']['end']['len'] = self::ARROW_SIZES[$endArrowSize]['len'];
}
if ($headArrowWidth !== '') {
$this->lineStyleProperties['arrow']['head']['w'] = $headArrowWidth;
}
if ($headArrowLength !== '') {
$this->lineStyleProperties['arrow']['head']['len'] = $headArrowLength;
}
if ($endArrowWidth !== '') {
$this->lineStyleProperties['arrow']['end']['w'] = $endArrowWidth;
}
if ($endArrowLength !== '') {
$this->lineStyleProperties['arrow']['end']['len'] = $endArrowLength;
}
}
public function getLineStyleArray(): array
{
return $this->lineStyleProperties;
}
public function setLineStyleArray(array $lineStyleProperties = []): self
{
$this->activateObject();
$this->lineStyleProperties['width'] = $lineStyleProperties['width'] ?? null;
$this->lineStyleProperties['compound'] = $lineStyleProperties['compound'] ?? '';
$this->lineStyleProperties['dash'] = $lineStyleProperties['dash'] ?? '';
$this->lineStyleProperties['cap'] = $lineStyleProperties['cap'] ?? '';
$this->lineStyleProperties['join'] = $lineStyleProperties['join'] ?? '';
$this->lineStyleProperties['arrow']['head']['type'] = $lineStyleProperties['arrow']['head']['type'] ?? '';
$this->lineStyleProperties['arrow']['head']['size'] = $lineStyleProperties['arrow']['head']['size'] ?? '';
$this->lineStyleProperties['arrow']['head']['w'] = $lineStyleProperties['arrow']['head']['w'] ?? '';
$this->lineStyleProperties['arrow']['head']['len'] = $lineStyleProperties['arrow']['head']['len'] ?? '';
$this->lineStyleProperties['arrow']['end']['type'] = $lineStyleProperties['arrow']['end']['type'] ?? '';
$this->lineStyleProperties['arrow']['end']['size'] = $lineStyleProperties['arrow']['end']['size'] ?? '';
$this->lineStyleProperties['arrow']['end']['w'] = $lineStyleProperties['arrow']['end']['w'] ?? '';
$this->lineStyleProperties['arrow']['end']['len'] = $lineStyleProperties['arrow']['end']['len'] ?? '';
return $this;
}
/**
* @param mixed $value
*/
public function setLineStyleProperty(string $propertyName, $value): self
{
$this->activateObject();
$this->lineStyleProperties[$propertyName] = $value;
return $this;
}
/**
* Get Line Style Property.
*
* @param array|string $elements
*
* @return string
*/
public function getLineStyleProperty($elements)
{
return $this->getArrayElementsValue($this->lineStyleProperties, $elements);
}
protected const ARROW_SIZES = [
1 => ['w' => 'sm', 'len' => 'sm'],
2 => ['w' => 'sm', 'len' => 'med'],
3 => ['w' => 'sm', 'len' => 'lg'],
4 => ['w' => 'med', 'len' => 'sm'],
5 => ['w' => 'med', 'len' => 'med'],
6 => ['w' => 'med', 'len' => 'lg'],
7 => ['w' => 'lg', 'len' => 'sm'],
8 => ['w' => 'lg', 'len' => 'med'],
9 => ['w' => 'lg', 'len' => 'lg'],
];
/**
* Get Line Style Arrow Size.
*
* @param int $arraySelector
* @param string $arrayKaySelector
*
* @return string
*/
protected function getLineStyleArrowSize($arraySelector, $arrayKaySelector)
{
return self::ARROW_SIZES[$arraySelector][$arrayKaySelector] ?? '';
}
/**
* Get Line Style Arrow Parameters.
*
* @param string $arrowSelector
* @param string $propertySelector
*
* @return string
*/
public function getLineStyleArrowParameters($arrowSelector, $propertySelector)
{
return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrowSelector]['size'], $propertySelector);
}
/**
* Get Line Style Arrow Width.
*
* @param string $arrow
*
* @return string
*/
public function getLineStyleArrowWidth($arrow)
{
return $this->getLineStyleProperty(['arrow', $arrow, 'w']);
}
/**
* Get Line Style Arrow Excel Length.
*
* @param string $arrow
*
* @return string
*/
public function getLineStyleArrowLength($arrow)
{
return $this->getLineStyleProperty(['arrow', $arrow, 'len']);
}
}
phpspreadsheet/src/PhpSpreadsheet/Comment.php 0000644 00000016553 15243417764 0015433 0 ustar 00 <?php
namespace PhpOffice\PhpSpreadsheet;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Helper\Size;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
class Comment implements IComparable
{
/**
* Author.
*
* @var string
*/
private $author;
/**
* Rich text comment.
*
* @var RichText
*/
private $text;
/**
* Comment width (CSS style, i.e. XXpx or YYpt).
*
* @var string
*/
private $width = '96pt';
/**
* Left margin (CSS style, i.e. XXpx or YYpt).
*
* @var string
*/
private $marginLeft = '59.25pt';
/**
* Top margin (CSS style, i.e. XXpx or YYpt).
*
* @var string
*/
private $marginTop = '1.5pt';
/**
* Visible.
*
* @var bool
*/
private $visible = false;
/**
* Comment height (CSS style, i.e. XXpx or YYpt).
*
* @var string
*/
private $height = '55.5pt';
/**
* Comment fill color.
*
* @var Color
*/
private $fillColor;
/**
* Alignment.
*
* @var string
*/
private $alignment;
/**
* Background image in comment.
*
* @var Drawing
*/
private $backgroundImage;
/**
* Create a new Comment.
*/
public function __construct()
{
// Initialise variables
$this->author = 'Author';
$this->text = new RichText();
$this->fillColor = new Color('FFFFFFE1');
$this->alignment = Alignment::HORIZONTAL_GENERAL;
$this->backgroundImage = new Drawing();
}
/**
* Get Author.
*/
public function getAuthor(): string
{
return $this->author;
}
/**
* Set Author.
*/
public function setAuthor(string $author): self
{
$this->author = $author;
return $this;
}
/**
* Get Rich text comment.
*/
public function getText(): RichText
{
return $this->text;
}
/**
* Set Rich text comment.
*/
public function setText(RichText $text): self
{
$this->text = $text;
return $this;
}
/**
* Get comment width (CSS style, i.e. XXpx or YYpt).
*/
public function getWidth(): string
{
return $this->width;
}
/**
* Set comment width (CSS style, i.e. XXpx or YYpt). Default unit is pt.
*/
public function setWidth(string $width): self
{
$width = new Size($width);
if ($width->valid()) {
$this->width = (string) $width;
}
return $this;
}
/**
* Get comment height (CSS style, i.e. XXpx or YYpt).
*/
public function getHeight(): string
{
return $this->height;
}
/**
* Set comment height (CSS style, i.e. XXpx or YYpt). Default unit is pt.
*/
public function setHeight(string $height): self
{
$height = new Size($height);
if ($height->valid()) {
$this->height = (string) $height;
}
return $this;
}
/**
* Get left margin (CSS style, i.e. XXpx or YYpt).
*/
public function getMarginLeft(): string
{
return $this->marginLeft;
}
/**
* Set left margin (CSS style, i.e. XXpx or YYpt). Default unit is pt.
*/
public function setMarginLeft(string $margin): self
{
$margin = new Size($margin);
if ($margin->valid()) {
$this->marginLeft = (string) $margin;
}
return $this;
}
/**
* Get top margin (CSS style, i.e. XXpx or YYpt).
*/
public function getMarginTop(): string
{
return $this->marginTop;
}
/**
* Set top margin (CSS style, i.e. XXpx or YYpt). Default unit is pt.
*/
public function setMarginTop(string $margin): self
{
$margin = new Size($margin);
if ($margin->valid()) {
$this->marginTop = (string) $margin;
}
return $this;
}
/**
* Is the comment visible by default?
*/
public function getVisible(): bool
{
return $this->visible;
}
/**
* Set comment default visibility.
*/
public function setVisible(bool $visibility): self
{
$this->visible = $visibility;
return $this;
}
/**
* Set fill color.
*/
public function setFillColor(Color $color): self
{
$this->fillColor = $color;
return $this;
}
/**
* Get fill color.
*/
public function getFillColor(): Color
{
return $this->fillColor;
}
/**
* Set Alignment.
*/
public function setAlignment(string $alignment): self
{
$this->alignment = $alignment;
return $this;
}
/**
* Get Alignment.
*/
public function getAlignment(): string
{
return $this->alignment;
}
/**
* Get hash code.
*/
public function getHashCode(): string
{
return md5(
$this->author .
$this->text->getHashCode() .
$this->width .
$this->height .
$this->marginLeft .
$this->marginTop .
($this->visible ? 1 : 0) .
$this->fillColor->getHashCode() .
$this->alignment .
($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') .
__CLASS__
);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
/**
* Convert to string.
*/
public function __toString(): string
{
return $this->text->getPlainText();
}
/**
* Check is background image exists.
*/
public function hasBackgroundImage(): bool
{
$path = $this->backgroundImage->getPath();
if (empty($path)) {
return false;
}
return getimagesize($path) !== false;
}
/**
* Returns background image.
*/
public function getBackgroundImage(): Drawing
{
return $this->backgroundImage;
}
/**
* Sets background image.
*/
public function setBackgroundImage(Drawing $objDrawing): self
{
if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) {
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
}
$this->backgroundImage = $objDrawing;
return $this;
}
/**
* Sets size of comment as size of background image.
*/
public function setSizeAsBackgroundImage(): self
{
if ($this->hasBackgroundImage()) {
$this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt');
$this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt');
}
return $this;
}
}
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config 0000644 00000000542 15243417764 0020624 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Svenska (Swedish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #SKÄRNING!
DIV0 = #DIVISION/0!
VALUE = #VÄRDEFEL!
REF = #REFERENS!
NAME = #NAMN?
NUM = #OGILTIGT!
NA = #SAKNAS!
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions 0000644 00000023241 15243417764 0021370 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Svenska (Swedish)
##
############################################################
##
## Kubfunktioner (Cube Functions)
##
CUBEKPIMEMBER = KUBKPIMEDLEM
CUBEMEMBER = KUBMEDLEM
CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP
CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM
CUBESET = KUBUPPSÄTTNING
CUBESETCOUNT = KUBUPPSÄTTNINGANTAL
CUBEVALUE = KUBVÄRDE
##
## Databasfunktioner (Database Functions)
##
DAVERAGE = DMEDEL
DCOUNT = DANTAL
DCOUNTA = DANTALV
DGET = DHÄMTA
DMAX = DMAX
DMIN = DMIN
DPRODUCT = DPRODUKT
DSTDEV = DSTDAV
DSTDEVP = DSTDAVP
DSUM = DSUMMA
DVAR = DVARIANS
DVARP = DVARIANSP
##
## Tid- och datumfunktioner (Date & Time Functions)
##
DATE = DATUM
DATEVALUE = DATUMVÄRDE
DAY = DAG
DAYS = DAGAR
DAYS360 = DAGAR360
EDATE = EDATUM
EOMONTH = SLUTMÅNAD
HOUR = TIMME
ISOWEEKNUM = ISOVECKONR
MINUTE = MINUT
MONTH = MÅNAD
NETWORKDAYS = NETTOARBETSDAGAR
NETWORKDAYS.INTL = NETTOARBETSDAGAR.INT
NOW = NU
SECOND = SEKUND
THAIDAYOFWEEK = THAIVECKODAG
THAIMONTHOFYEAR = THAIMÅNAD
THAIYEAR = THAIÅR
TIME = KLOCKSLAG
TIMEVALUE = TIDVÄRDE
TODAY = IDAG
WEEKDAY = VECKODAG
WEEKNUM = VECKONR
WORKDAY = ARBETSDAGAR
WORKDAY.INTL = ARBETSDAGAR.INT
YEAR = ÅR
YEARFRAC = ÅRDEL
##
## Tekniska funktioner (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.TILL.DEC
BIN2HEX = BIN.TILL.HEX
BIN2OCT = BIN.TILL.OKT
BITAND = BITOCH
BITLSHIFT = BITVSKIFT
BITOR = BITELLER
BITRSHIFT = BITHSKIFT
BITXOR = BITXELLER
COMPLEX = KOMPLEX
CONVERT = KONVERTERA
DEC2BIN = DEC.TILL.BIN
DEC2HEX = DEC.TILL.HEX
DEC2OCT = DEC.TILL.OKT
DELTA = DELTA
ERF = FELF
ERF.PRECISE = FELF.EXAKT
ERFC = FELFK
ERFC.PRECISE = FELFK.EXAKT
GESTEP = SLSTEG
HEX2BIN = HEX.TILL.BIN
HEX2DEC = HEX.TILL.DEC
HEX2OCT = HEX.TILL.OKT
IMABS = IMABS
IMAGINARY = IMAGINÄR
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMKONJUGAT
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEUPPHÖJT
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMUPPHÖJT
IMPRODUCT = IMPRODUKT
IMREAL = IMREAL
IMSEC = IMSEK
IMSECH = IMSEKH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMROT
IMSUB = IMDIFF
IMSUM = IMSUM
IMTAN = IMTAN
OCT2BIN = OKT.TILL.BIN
OCT2DEC = OKT.TILL.DEC
OCT2HEX = OKT.TILL.HEX
##
## Finansiella funktioner (Financial Functions)
##
ACCRINT = UPPLRÄNTA
ACCRINTM = UPPLOBLRÄNTA
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = KUPDAGBB
COUPDAYS = KUPDAGB
COUPDAYSNC = KUPDAGNK
COUPNCD = KUPNKD
COUPNUM = KUPANT
COUPPCD = KUPFKD
CUMIPMT = KUMRÄNTA
CUMPRINC = KUMPRIS
DB = DB
DDB = DEGAVSKR
DISC = DISK
DOLLARDE = DECTAL
DOLLARFR = BRÅK
DURATION = LÖPTID
EFFECT = EFFRÄNTA
FV = SLUTVÄRDE
FVSCHEDULE = FÖRRÄNTNING
INTRATE = ÅRSRÄNTA
IPMT = RBETALNING
IRR = IR
ISPMT = RALÅN
MDURATION = MLÖPTID
MIRR = MODIR
NOMINAL = NOMRÄNTA
NPER = PERIODER
NPV = NETNUVÄRDE
ODDFPRICE = UDDAFPRIS
ODDFYIELD = UDDAFAVKASTNING
ODDLPRICE = UDDASPRIS
ODDLYIELD = UDDASAVKASTNING
PDURATION = PLÖPTID
PMT = BETALNING
PPMT = AMORT
PRICE = PRIS
PRICEDISC = PRISDISK
PRICEMAT = PRISFÖRF
PV = NUVÄRDE
RATE = RÄNTA
RECEIVED = BELOPP
RRI = AVKPÅINVEST
SLN = LINAVSKR
SYD = ÅRSAVSKR
TBILLEQ = SSVXEKV
TBILLPRICE = SSVXPRIS
TBILLYIELD = SSVXRÄNTA
VDB = VDEGRAVSKR
XIRR = XIRR
XNPV = XNUVÄRDE
YIELD = NOMAVK
YIELDDISC = NOMAVKDISK
YIELDMAT = NOMAVKFÖRF
##
## Informationsfunktioner (Information Functions)
##
CELL = CELL
ERROR.TYPE = FEL.TYP
INFO = INFO
ISBLANK = ÄRTOM
ISERR = ÄRF
ISERROR = ÄRFEL
ISEVEN = ÄRJÄMN
ISFORMULA = ÄRFORMEL
ISLOGICAL = ÄRLOGISK
ISNA = ÄRSAKNAD
ISNONTEXT = ÄREJTEXT
ISNUMBER = ÄRTAL
ISODD = ÄRUDDA
ISREF = ÄRREF
ISTEXT = ÄRTEXT
N = N
NA = SAKNAS
SHEET = BLAD
SHEETS = ANTALBLAD
TYPE = VÄRDETYP
##
## Logiska funktioner (Logical Functions)
##
AND = OCH
FALSE = FALSKT
IF = OM
IFERROR = OMFEL
IFNA = OMSAKNAS
IFS = IFS
NOT = ICKE
OR = ELLER
SWITCH = VÄXLA
TRUE = SANT
XOR = XELLER
##
## Sök- och referensfunktioner (Lookup & Reference Functions)
##
ADDRESS = ADRESS
AREAS = OMRÅDEN
CHOOSE = VÄLJ
COLUMN = KOLUMN
COLUMNS = KOLUMNER
FORMULATEXT = FORMELTEXT
GETPIVOTDATA = HÄMTA.PIVOTDATA
HLOOKUP = LETAKOLUMN
HYPERLINK = HYPERLÄNK
INDEX = INDEX
INDIRECT = INDIREKT
LOOKUP = LETAUPP
MATCH = PASSA
OFFSET = FÖRSKJUTNING
ROW = RAD
ROWS = RADER
RTD = RTD
TRANSPOSE = TRANSPONERA
VLOOKUP = LETARAD
*RC = RK
##
## Matematiska och trigonometriska funktioner (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = MÄNGD
ARABIC = ARABISKA
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANH
BASE = BAS
CEILING.MATH = RUNDA.UPP.MATEMATISKT
CEILING.PRECISE = RUNDA.UPP.EXAKT
COMBIN = KOMBIN
COMBINA = KOMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = GRADER
ECMA.CEILING = ECMA.RUNDA.UPP
EVEN = JÄMN
EXP = EXP
FACT = FAKULTET
FACTDOUBLE = DUBBELFAKULTET
FLOOR.MATH = RUNDA.NER.MATEMATISKT
FLOOR.PRECISE = RUNDA.NER.EXAKT
GCD = SGD
INT = HELTAL
ISO.CEILING = ISO.RUNDA.UPP
LCM = MGM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERT
MMULT = MMULT
MOD = REST
MROUND = MAVRUNDA
MULTINOMIAL = MULTINOMIAL
MUNIT = MENHET
ODD = UDDA
PI = PI
POWER = UPPHÖJT.TILL
PRODUCT = PRODUKT
QUOTIENT = KVOT
RADIANS = RADIANER
RAND = SLUMP
RANDBETWEEN = SLUMP.MELLAN
ROMAN = ROMERSK
ROUND = AVRUNDA
ROUNDBAHTDOWN = AVRUNDABAHTNEDÅT
ROUNDBAHTUP = AVRUNDABAHTUPPÅT
ROUNDDOWN = AVRUNDA.NEDÅT
ROUNDUP = AVRUNDA.UPPÅT
SEC = SEK
SECH = SEKH
SERIESSUM = SERIESUMMA
SIGN = TECKEN
SIN = SIN
SINH = SINH
SQRT = ROT
SQRTPI = ROTPI
SUBTOTAL = DELSUMMA
SUM = SUMMA
SUMIF = SUMMA.OM
SUMIFS = SUMMA.OMF
SUMPRODUCT = PRODUKTSUMMA
SUMSQ = KVADRATSUMMA
SUMX2MY2 = SUMMAX2MY2
SUMX2PY2 = SUMMAX2PY2
SUMXMY2 = SUMMAXMY2
TAN = TAN
TANH = TANH
TRUNC = AVKORTA
##
## Statistiska funktioner (Statistical Functions)
##
AVEDEV = MEDELAVV
AVERAGE = MEDEL
AVERAGEA = AVERAGEA
AVERAGEIF = MEDEL.OM
AVERAGEIFS = MEDEL.OMF
BETA.DIST = BETA.FÖRD
BETA.INV = BETA.INV
BINOM.DIST = BINOM.FÖRD
BINOM.DIST.RANGE = BINOM.FÖRD.INTERVALL
BINOM.INV = BINOM.INV
CHISQ.DIST = CHI2.FÖRD
CHISQ.DIST.RT = CHI2.FÖRD.RT
CHISQ.INV = CHI2.INV
CHISQ.INV.RT = CHI2.INV.RT
CHISQ.TEST = CHI2.TEST
CONFIDENCE.NORM = KONFIDENS.NORM
CONFIDENCE.T = KONFIDENS.T
CORREL = KORREL
COUNT = ANTAL
COUNTA = ANTALV
COUNTBLANK = ANTAL.TOMMA
COUNTIF = ANTAL.OM
COUNTIFS = ANTAL.OMF
COVARIANCE.P = KOVARIANS.P
COVARIANCE.S = KOVARIANS.S
DEVSQ = KVADAVV
EXPON.DIST = EXPON.FÖRD
F.DIST = F.FÖRD
F.DIST.RT = F.FÖRD.RT
F.INV = F.INV
F.INV.RT = F.INV.RT
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOS.ETS
FORECAST.ETS.CONFINT = PROGNOS.ETS.KONFINT
FORECAST.ETS.SEASONALITY = PROGNOS.ETS.SÄSONGSBEROENDE
FORECAST.ETS.STAT = PROGNOS.ETS.STAT
FORECAST.LINEAR = PROGNOS.LINJÄR
FREQUENCY = FREKVENS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.FÖRD
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.EXAKT
GAUSS = GAUSS
GEOMEAN = GEOMEDEL
GROWTH = EXPTREND
HARMEAN = HARMMEDEL
HYPGEOM.DIST = HYPGEOM.FÖRD
INTERCEPT = SKÄRNINGSPUNKT
KURT = TOPPIGHET
LARGE = STÖRSTA
LINEST = REGR
LOGEST = EXPREGR
LOGNORM.DIST = LOGNORM.FÖRD
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAXIFS
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MINIFS
MODE.MULT = TYPVÄRDE.FLERA
MODE.SNGL = TYPVÄRDE.ETT
NEGBINOM.DIST = NEGBINOM.FÖRD
NORM.DIST = NORM.FÖRD
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.FÖRD
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXK
PERCENTILE.INC = PERCENTIL.INK
PERCENTRANK.EXC = PROCENTRANG.EXK
PERCENTRANK.INC = PROCENTRANG.INK
PERMUT = PERMUT
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = POISSON.FÖRD
PROB = SANNOLIKHET
QUARTILE.EXC = KVARTIL.EXK
QUARTILE.INC = KVARTIL.INK
RANK.AVG = RANG.MED
RANK.EQ = RANG.EKV
RSQ = RKV
SKEW = SNEDHET
SKEW.P = SNEDHET.P
SLOPE = LUTNING
SMALL = MINSTA
STANDARDIZE = STANDARDISERA
STDEV.P = STDAV.P
STDEV.S = STDAV.S
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = STDFELYX
T.DIST = T.FÖRD
T.DIST.2T = T.FÖRD.2T
T.DIST.RT = T.FÖRD.RT
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = TRIMMEDEL
VAR.P = VARIANS.P
VAR.S = VARIANS.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.FÖRD
Z.TEST = Z.TEST
##
## Textfunktioner (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = TECKENKOD
CLEAN = STÄDA
CODE = KOD
CONCAT = SAMMAN
DOLLAR = VALUTA
EXACT = EXAKT
FIND = HITTA
FIXED = FASTTAL
LEFT = VÄNSTER
LEN = LÄNGD
LOWER = GEMENER
MID = EXTEXT
NUMBERVALUE = TALVÄRDE
PROPER = INITIAL
REPLACE = ERSÄTT
REPT = REP
RIGHT = HÖGER
SEARCH = SÖK
SUBSTITUTE = BYT.UT
T = T
TEXT = TEXT
TEXTJOIN = TEXTJOIN
THAIDIGIT = THAISIFFRA
THAINUMSOUND = THAITALLJUD
THAINUMSTRING = THAITALSTRÄNG
THAISTRINGLENGTH = THAISTRÄNGLÄNGD
TRIM = RENSA
UNICHAR = UNITECKENKOD
UNICODE = UNICODE
UPPER = VERSALER
VALUE = TEXTNUM
##
## Webbfunktioner (Web Functions)
##
ENCODEURL = KODAWEBBADRESS
FILTERXML = FILTRERAXML
WEBSERVICE = WEBBTJÄNST
##
## Kompatibilitetsfunktioner (Compatibility Functions)
##
BETADIST = BETAFÖRD
BETAINV = BETAINV
BINOMDIST = BINOMFÖRD
CEILING = RUNDA.UPP
CHIDIST = CHI2FÖRD
CHIINV = CHI2INV
CHITEST = CHI2TEST
CONCATENATE = SAMMANFOGA
CONFIDENCE = KONFIDENS
COVAR = KOVAR
CRITBINOM = KRITBINOM
EXPONDIST = EXPONFÖRD
FDIST = FFÖRD
FINV = FINV
FLOOR = RUNDA.NER
FORECAST = PREDIKTION
FTEST = FTEST
GAMMADIST = GAMMAFÖRD
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOMFÖRD
LOGINV = LOGINV
LOGNORMDIST = LOGNORMFÖRD
MODE = TYPVÄRDE
NEGBINOMDIST = NEGBINOMFÖRD
NORMDIST = NORMFÖRD
NORMINV = NORMINV
NORMSDIST = NORMSFÖRD
NORMSINV = NORMSINV
PERCENTILE = PERCENTIL
PERCENTRANK = PROCENTRANG
POISSON = POISSON
QUARTILE = KVARTIL
RANK = RANG
STDEV = STDAV
STDEVP = STDAVP
TDIST = TFÖRD
TINV = TINV
TTEST = TTEST
VAR = VARIANS
VARP = VARIANSP
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config 0000644 00000000520 15243417764 0021216 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Português Brasileiro (Brazilian Portuguese)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULO!
DIV0
VALUE = #VALOR!
REF
NAME = #NOME?
NUM = #NÚM!
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions 0000644 00000023147 15243417764 0021773 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Português Brasileiro (Brazilian Portuguese)
##
############################################################
##
## Funções de cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBROKPICUBO
CUBEMEMBER = MEMBROCUBO
CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO
CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = CONTAGEMCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funções de banco de dados (Database Functions)
##
DAVERAGE = BDMÉDIA
DCOUNT = BDCONTAR
DCOUNTA = BDCONTARA
DGET = BDEXTRAIR
DMAX = BDMÁX
DMIN = BDMÍN
DPRODUCT = BDMULTIPL
DSTDEV = BDEST
DSTDEVP = BDDESVPA
DSUM = BDSOMA
DVAR = BDVAREST
DVARP = BDVARP
##
## Funções de data e hora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATADIF
DATESTRING = DATA.SÉRIE
DATEVALUE = DATA.VALOR
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = DATAM
EOMONTH = FIMMÊS
HOUR = HORA
ISOWEEKNUM = NÚMSEMANAISO
MINUTE = MINUTO
MONTH = MÊS
NETWORKDAYS = DIATRABALHOTOTAL
NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL
NOW = AGORA
SECOND = SEGUNDO
TIME = TEMPO
TIMEVALUE = VALOR.TEMPO
TODAY = HOJE
WEEKDAY = DIA.DA.SEMANA
WEEKNUM = NÚMSEMANA
WORKDAY = DIATRABALHO
WORKDAY.INTL = DIATRABALHO.INTL
YEAR = ANO
YEARFRAC = FRAÇÃOANO
##
## Funções de engenharia (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINADEC
BIN2HEX = BINAHEX
BIN2OCT = BINAOCT
BITAND = BITAND
BITLSHIFT = DESLOCESQBIT
BITOR = BITOR
BITRSHIFT = DESLOCDIRBIT
BITXOR = BITXOR
COMPLEX = COMPLEXO
CONVERT = CONVERTER
DEC2BIN = DECABIN
DEC2HEX = DECAHEX
DEC2OCT = DECAOCT
DELTA = DELTA
ERF = FUNERRO
ERF.PRECISE = FUNERRO.PRECISO
ERFC = FUNERROCOMPL
ERFC.PRECISE = FUNERROCOMPL.PRECISO
GESTEP = DEGRAU
HEX2BIN = HEXABIN
HEX2DEC = HEXADEC
HEX2OCT = HEXAOCT
IMABS = IMABS
IMAGINARY = IMAGINÁRIO
IMARGUMENT = IMARG
IMCONJUGATE = IMCONJ
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCOSEC
IMCSCH = IMCOSECH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOT
IMPRODUCT = IMPROD
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSENO
IMSINH = IMSENH
IMSQRT = IMRAIZ
IMSUB = IMSUBTR
IMSUM = IMSOMA
IMTAN = IMTAN
OCT2BIN = OCTABIN
OCT2DEC = OCTADEC
OCT2HEX = OCTAHEX
##
## Funções financeiras (Financial Functions)
##
ACCRINT = JUROSACUM
ACCRINTM = JUROSACUMV
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = CUPDIASINLIQ
COUPDAYS = CUPDIAS
COUPDAYSNC = CUPDIASPRÓX
COUPNCD = CUPDATAPRÓX
COUPNUM = CUPNÚM
COUPPCD = CUPDATAANT
CUMIPMT = PGTOJURACUM
CUMPRINC = PGTOCAPACUM
DB = BD
DDB = BDD
DISC = DESC
DOLLARDE = MOEDADEC
DOLLARFR = MOEDAFRA
DURATION = DURAÇÃO
EFFECT = EFETIVA
FV = VF
FVSCHEDULE = VFPLANO
INTRATE = TAXAJUROS
IPMT = IPGTO
IRR = TIR
ISPMT = ÉPGTO
MDURATION = MDURAÇÃO
MIRR = MTIR
NOMINAL = NOMINAL
NPER = NPER
NPV = VPL
ODDFPRICE = PREÇOPRIMINC
ODDFYIELD = LUCROPRIMINC
ODDLPRICE = PREÇOÚLTINC
ODDLYIELD = LUCROÚLTINC
PDURATION = DURAÇÃOP
PMT = PGTO
PPMT = PPGTO
PRICE = PREÇO
PRICEDISC = PREÇODESC
PRICEMAT = PREÇOVENC
PV = VP
RATE = TAXA
RECEIVED = RECEBER
RRI = TAXAJURO
SLN = DPD
SYD = SDA
TBILLEQ = OTN
TBILLPRICE = OTNVALOR
TBILLYIELD = OTNLUCRO
VDB = BDV
XIRR = XTIR
XNPV = XVPL
YIELD = LUCRO
YIELDDISC = LUCRODESC
YIELDMAT = LUCROVENC
##
## Funções de informação (Information Functions)
##
CELL = CÉL
ERROR.TYPE = TIPO.ERRO
INFO = INFORMAÇÃO
ISBLANK = ÉCÉL.VAZIA
ISERR = ÉERRO
ISERROR = ÉERROS
ISEVEN = ÉPAR
ISFORMULA = ÉFÓRMULA
ISLOGICAL = ÉLÓGICO
ISNA = É.NÃO.DISP
ISNONTEXT = É.NÃO.TEXTO
ISNUMBER = ÉNÚM
ISODD = ÉIMPAR
ISREF = ÉREF
ISTEXT = ÉTEXTO
N = N
NA = NÃO.DISP
SHEET = PLAN
SHEETS = PLANS
TYPE = TIPO
##
## Funções lógicas (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SEERRO
IFNA = SENÃODISP
IFS = SES
NOT = NÃO
OR = OU
SWITCH = PARÂMETRO
TRUE = VERDADEIRO
XOR = XOR
##
## Funções de pesquisa e referência (Lookup & Reference Functions)
##
ADDRESS = ENDEREÇO
AREAS = ÁREAS
CHOOSE = ESCOLHER
COLUMN = COL
COLUMNS = COLS
FORMULATEXT = FÓRMULATEXTO
GETPIVOTDATA = INFODADOSTABELADINÂMICA
HLOOKUP = PROCH
HYPERLINK = HIPERLINK
INDEX = ÍNDICE
INDIRECT = INDIRETO
LOOKUP = PROC
MATCH = CORRESP
OFFSET = DESLOC
ROW = LIN
ROWS = LINS
RTD = RTD
TRANSPOSE = TRANSPOR
VLOOKUP = PROCV
*RC = LC
##
## Funções matemáticas e trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = ARÁBICO
ASIN = ASEN
ASINH = ASENH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = TETO.MAT
CEILING.PRECISE = TETO.PRECISO
COMBIN = COMBIN
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = COSEC
CSCH = COSECH
DECIMAL = DECIMAL
DEGREES = GRAUS
ECMA.CEILING = ECMA.TETO
EVEN = PAR
EXP = EXP
FACT = FATORIAL
FACTDOUBLE = FATDUPLO
FLOOR.MATH = ARREDMULTB.MAT
FLOOR.PRECISE = ARREDMULTB.PRECISO
GCD = MDC
INT = INT
ISO.CEILING = ISO.TETO
LCM = MMC
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATRIZ.DETERM
MINVERSE = MATRIZ.INVERSO
MMULT = MATRIZ.MULT
MOD = MOD
MROUND = MARRED
MULTINOMIAL = MULTINOMIAL
MUNIT = MUNIT
ODD = ÍMPAR
PI = PI
POWER = POTÊNCIA
PRODUCT = MULT
QUOTIENT = QUOCIENTE
RADIANS = RADIANOS
RAND = ALEATÓRIO
RANDBETWEEN = ALEATÓRIOENTRE
ROMAN = ROMANO
ROUND = ARRED
ROUNDDOWN = ARREDONDAR.PARA.BAIXO
ROUNDUP = ARREDONDAR.PARA.CIMA
SEC = SEC
SECH = SECH
SERIESSUM = SOMASEQÜÊNCIA
SIGN = SINAL
SIN = SEN
SINH = SENH
SQRT = RAIZ
SQRTPI = RAIZPI
SUBTOTAL = SUBTOTAL
SUM = SOMA
SUMIF = SOMASE
SUMIFS = SOMASES
SUMPRODUCT = SOMARPRODUTO
SUMSQ = SOMAQUAD
SUMX2MY2 = SOMAX2DY2
SUMX2PY2 = SOMAX2SY2
SUMXMY2 = SOMAXMY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funções estatísticas (Statistical Functions)
##
AVEDEV = DESV.MÉDIO
AVERAGE = MÉDIA
AVERAGEA = MÉDIAA
AVERAGEIF = MÉDIASE
AVERAGEIFS = MÉDIASES
BETA.DIST = DIST.BETA
BETA.INV = INV.BETA
BINOM.DIST = DISTR.BINOM
BINOM.DIST.RANGE = INTERV.DISTR.BINOM
BINOM.INV = INV.BINOM
CHISQ.DIST = DIST.QUIQUA
CHISQ.DIST.RT = DIST.QUIQUA.CD
CHISQ.INV = INV.QUIQUA
CHISQ.INV.RT = INV.QUIQUA.CD
CHISQ.TEST = TESTE.QUIQUA
CONFIDENCE.NORM = INT.CONFIANÇA.NORM
CONFIDENCE.T = INT.CONFIANÇA.T
CORREL = CORREL
COUNT = CONT.NÚM
COUNTA = CONT.VALORES
COUNTBLANK = CONTAR.VAZIO
COUNTIF = CONT.SE
COUNTIFS = CONT.SES
COVARIANCE.P = COVARIAÇÃO.P
COVARIANCE.S = COVARIAÇÃO.S
DEVSQ = DESVQ
EXPON.DIST = DISTR.EXPON
F.DIST = DIST.F
F.DIST.RT = DIST.F.CD
F.INV = INV.F
F.INV.RT = INV.F.CD
F.TEST = TESTE.F
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PREVISÃO.ETS
FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE
FORECAST.ETS.STAT = PREVISÃO.ETS.STAT
FORECAST.LINEAR = PREVISÃO.LINEAR
FREQUENCY = FREQÜÊNCIA
GAMMA = GAMA
GAMMA.DIST = DIST.GAMA
GAMMA.INV = INV.GAMA
GAMMALN = LNGAMA
GAMMALN.PRECISE = LNGAMA.PRECISO
GAUSS = GAUSS
GEOMEAN = MÉDIA.GEOMÉTRICA
GROWTH = CRESCIMENTO
HARMEAN = MÉDIA.HARMÔNICA
HYPGEOM.DIST = DIST.HIPERGEOM.N
INTERCEPT = INTERCEPÇÃO
KURT = CURT
LARGE = MAIOR
LINEST = PROJ.LIN
LOGEST = PROJ.LOG
LOGNORM.DIST = DIST.LOGNORMAL.N
LOGNORM.INV = INV.LOGNORMAL
MAX = MÁXIMO
MAXA = MÁXIMOA
MAXIFS = MÁXIMOSES
MEDIAN = MED
MIN = MÍNIMO
MINA = MÍNIMOA
MINIFS = MÍNIMOSES
MODE.MULT = MODO.MULT
MODE.SNGL = MODO.ÚNICO
NEGBINOM.DIST = DIST.BIN.NEG.N
NORM.DIST = DIST.NORM.N
NORM.INV = INV.NORM.N
NORM.S.DIST = DIST.NORMP.N
NORM.S.INV = INV.NORMP.N
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = ORDEM.PORCENTUAL.EXC
PERCENTRANK.INC = ORDEM.PORCENTUAL.INC
PERMUT = PERMUT
PERMUTATIONA = PERMUTAS
PHI = PHI
POISSON.DIST = DIST.POISSON
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = ORDEM.MÉD
RANK.EQ = ORDEM.EQ
RSQ = RQUAD
SKEW = DISTORÇÃO
SKEW.P = DISTORÇÃO.P
SLOPE = INCLINAÇÃO
SMALL = MENOR
STANDARDIZE = PADRONIZAR
STDEV.P = DESVPAD.P
STDEV.S = DESVPAD.A
STDEVA = DESVPADA
STDEVPA = DESVPADPA
STEYX = EPADYX
T.DIST = DIST.T
T.DIST.2T = DIST.T.BC
T.DIST.RT = DIST.T.CD
T.INV = INV.T
T.INV.2T = INV.T.BC
T.TEST = TESTE.T
TREND = TENDÊNCIA
TRIMMEAN = MÉDIA.INTERNA
VAR.P = VAR.P
VAR.S = VAR.A
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DIST.WEIBULL
Z.TEST = TESTE.Z
##
## Funções de texto (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = CARACT
CLEAN = TIRAR
CODE = CÓDIGO
CONCAT = CONCAT
DOLLAR = MOEDA
EXACT = EXATO
FIND = PROCURAR
FIXED = DEF.NÚM.DEC
LEFT = ESQUERDA
LEN = NÚM.CARACT
LOWER = MINÚSCULA
MID = EXT.TEXTO
NUMBERSTRING = SEQÜÊNCIA.NÚMERO
NUMBERVALUE = VALORNUMÉRICO
PHONETIC = FONÉTICA
PROPER = PRI.MAIÚSCULA
REPLACE = MUDAR
REPT = REPT
RIGHT = DIREITA
SEARCH = LOCALIZAR
SUBSTITUTE = SUBSTITUIR
T = T
TEXT = TEXTO
TEXTJOIN = UNIRTEXTO
TRIM = ARRUMAR
UNICHAR = CARACTUNICODE
UNICODE = UNICODE
UPPER = MAIÚSCULA
VALUE = VALOR
##
## Funções da Web (Web Functions)
##
ENCODEURL = CODIFURL
FILTERXML = FILTROXML
WEBSERVICE = SERVIÇOWEB
##
## Funções de compatibilidade (Compatibility Functions)
##
BETADIST = DISTBETA
BETAINV = BETA.ACUM.INV
BINOMDIST = DISTRBINOM
CEILING = TETO
CHIDIST = DIST.QUI
CHIINV = INV.QUI
CHITEST = TESTE.QUI
CONCATENATE = CONCATENAR
CONFIDENCE = INT.CONFIANÇA
COVAR = COVAR
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTEXPON
FDIST = DISTF
FINV = INVF
FLOOR = ARREDMULTB
FORECAST = PREVISÃO
FTEST = TESTEF
GAMMADIST = DISTGAMA
GAMMAINV = INVGAMA
HYPGEOMDIST = DIST.HIPERGEOM
LOGINV = INVLOG
LOGNORMDIST = DIST.LOGNORMAL
MODE = MODO
NEGBINOMDIST = DIST.BIN.NEG
NORMDIST = DISTNORM
NORMINV = INV.NORM
NORMSDIST = DISTNORMP
NORMSINV = INV.NORMP
PERCENTILE = PERCENTIL
PERCENTRANK = ORDEM.PORCENTUAL
POISSON = POISSON
QUARTILE = QUARTIL
RANK = ORDEM
STDEV = DESVPAD
STDEVP = DESVPADP
TDIST = DISTT
TINV = INVT
TTEST = TESTET
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = TESTEZ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config 0000644 00000000473 15243417764 0020622 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Português (Portuguese)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULO!
DIV0
VALUE = #VALOR!
REF
NAME = #NOME?
NUM = #NÚM!
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions 0000644 00000023777 15243417764 0021401 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Português (Portuguese)
##
############################################################
##
## Funções de cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBROKPICUBO
CUBEMEMBER = MEMBROCUBO
CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO
CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = CONTARCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funções de base de dados (Database Functions)
##
DAVERAGE = BDMÉDIA
DCOUNT = BDCONTAR
DCOUNTA = BDCONTAR.VAL
DGET = BDOBTER
DMAX = BDMÁX
DMIN = BDMÍN
DPRODUCT = BDMULTIPL
DSTDEV = BDDESVPAD
DSTDEVP = BDDESVPADP
DSUM = BDSOMA
DVAR = BDVAR
DVARP = BDVARP
##
## Funções de data e hora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATADIF
DATESTRING = DATA.CADEIA
DATEVALUE = DATA.VALOR
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = DATAM
EOMONTH = FIMMÊS
HOUR = HORA
ISOWEEKNUM = NUMSEMANAISO
MINUTE = MINUTO
MONTH = MÊS
NETWORKDAYS = DIATRABALHOTOTAL
NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL
NOW = AGORA
SECOND = SEGUNDO
THAIDAYOFWEEK = DIA.DA.SEMANA.TAILANDÊS
THAIMONTHOFYEAR = MÊS.DO.ANO.TAILANDÊS
THAIYEAR = ANO.TAILANDÊS
TIME = TEMPO
TIMEVALUE = VALOR.TEMPO
TODAY = HOJE
WEEKDAY = DIA.SEMANA
WEEKNUM = NÚMSEMANA
WORKDAY = DIATRABALHO
WORKDAY.INTL = DIATRABALHO.INTL
YEAR = ANO
YEARFRAC = FRAÇÃOANO
##
## Funções de engenharia (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINADEC
BIN2HEX = BINAHEX
BIN2OCT = BINAOCT
BITAND = BIT.E
BITLSHIFT = BITDESL.ESQ
BITOR = BIT.OU
BITRSHIFT = BITDESL.DIR
BITXOR = BIT.XOU
COMPLEX = COMPLEXO
CONVERT = CONVERTER
DEC2BIN = DECABIN
DEC2HEX = DECAHEX
DEC2OCT = DECAOCT
DELTA = DELTA
ERF = FUNCERRO
ERF.PRECISE = FUNCERRO.PRECISO
ERFC = FUNCERROCOMPL
ERFC.PRECISE = FUNCERROCOMPL.PRECISO
GESTEP = DEGRAU
HEX2BIN = HEXABIN
HEX2DEC = HEXADEC
HEX2OCT = HEXAOCT
IMABS = IMABS
IMAGINARY = IMAGINÁRIO
IMARGUMENT = IMARG
IMCONJUGATE = IMCONJ
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOT
IMPRODUCT = IMPROD
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSENO
IMSINH = IMSENOH
IMSQRT = IMRAIZ
IMSUB = IMSUBTR
IMSUM = IMSOMA
IMTAN = IMTAN
OCT2BIN = OCTABIN
OCT2DEC = OCTADEC
OCT2HEX = OCTAHEX
##
## Funções financeiras (Financial Functions)
##
ACCRINT = JUROSACUM
ACCRINTM = JUROSACUMV
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = CUPDIASINLIQ
COUPDAYS = CUPDIAS
COUPDAYSNC = CUPDIASPRÓX
COUPNCD = CUPDATAPRÓX
COUPNUM = CUPNÚM
COUPPCD = CUPDATAANT
CUMIPMT = PGTOJURACUM
CUMPRINC = PGTOCAPACUM
DB = BD
DDB = BDD
DISC = DESC
DOLLARDE = MOEDADEC
DOLLARFR = MOEDAFRA
DURATION = DURAÇÃO
EFFECT = EFETIVA
FV = VF
FVSCHEDULE = VFPLANO
INTRATE = TAXAJUROS
IPMT = IPGTO
IRR = TIR
ISPMT = É.PGTO
MDURATION = MDURAÇÃO
MIRR = MTIR
NOMINAL = NOMINAL
NPER = NPER
NPV = VAL
ODDFPRICE = PREÇOPRIMINC
ODDFYIELD = LUCROPRIMINC
ODDLPRICE = PREÇOÚLTINC
ODDLYIELD = LUCROÚLTINC
PDURATION = PDURAÇÃO
PMT = PGTO
PPMT = PPGTO
PRICE = PREÇO
PRICEDISC = PREÇODESC
PRICEMAT = PREÇOVENC
PV = VA
RATE = TAXA
RECEIVED = RECEBER
RRI = DEVOLVERTAXAJUROS
SLN = AMORT
SYD = AMORTD
TBILLEQ = OTN
TBILLPRICE = OTNVALOR
TBILLYIELD = OTNLUCRO
VDB = BDV
XIRR = XTIR
XNPV = XVAL
YIELD = LUCRO
YIELDDISC = LUCRODESC
YIELDMAT = LUCROVENC
##
## Funções de informação (Information Functions)
##
CELL = CÉL
ERROR.TYPE = TIPO.ERRO
INFO = INFORMAÇÃO
ISBLANK = É.CÉL.VAZIA
ISERR = É.ERROS
ISERROR = É.ERRO
ISEVEN = ÉPAR
ISFORMULA = É.FORMULA
ISLOGICAL = É.LÓGICO
ISNA = É.NÃO.DISP
ISNONTEXT = É.NÃO.TEXTO
ISNUMBER = É.NÚM
ISODD = ÉÍMPAR
ISREF = É.REF
ISTEXT = É.TEXTO
N = N
NA = NÃO.DISP
SHEET = FOLHA
SHEETS = FOLHAS
TYPE = TIPO
##
## Funções lógicas (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SE.ERRO
IFNA = SEND
IFS = SE.S
NOT = NÃO
OR = OU
SWITCH = PARÂMETRO
TRUE = VERDADEIRO
XOR = XOU
##
## Funções de pesquisa e referência (Lookup & Reference Functions)
##
ADDRESS = ENDEREÇO
AREAS = ÁREAS
CHOOSE = SELECIONAR
COLUMN = COL
COLUMNS = COLS
FORMULATEXT = FÓRMULA.TEXTO
GETPIVOTDATA = OBTERDADOSDIN
HLOOKUP = PROCH
HYPERLINK = HIPERLIGAÇÃO
INDEX = ÍNDICE
INDIRECT = INDIRETO
LOOKUP = PROC
MATCH = CORRESP
OFFSET = DESLOCAMENTO
ROW = LIN
ROWS = LINS
RTD = RTD
TRANSPOSE = TRANSPOR
VLOOKUP = PROCV
*RC = LC
##
## Funções matemáticas e trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = ÁRABE
ASIN = ASEN
ASINH = ASENH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = ARRED.EXCESSO.MAT
CEILING.PRECISE = ARRED.EXCESSO.PRECISO
COMBIN = COMBIN
COMBINA = COMBIN.R
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = GRAUS
ECMA.CEILING = ARRED.EXCESSO.ECMA
EVEN = PAR
EXP = EXP
FACT = FATORIAL
FACTDOUBLE = FATDUPLO
FLOOR.MATH = ARRED.DEFEITO.MAT
FLOOR.PRECISE = ARRED.DEFEITO.PRECISO
GCD = MDC
INT = INT
ISO.CEILING = ARRED.EXCESSO.ISO
LCM = MMC
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATRIZ.DETERM
MINVERSE = MATRIZ.INVERSA
MMULT = MATRIZ.MULT
MOD = RESTO
MROUND = MARRED
MULTINOMIAL = POLINOMIAL
MUNIT = UNIDM
ODD = ÍMPAR
PI = PI
POWER = POTÊNCIA
PRODUCT = PRODUTO
QUOTIENT = QUOCIENTE
RADIANS = RADIANOS
RAND = ALEATÓRIO
RANDBETWEEN = ALEATÓRIOENTRE
ROMAN = ROMANO
ROUND = ARRED
ROUNDBAHTDOWN = ARREDOND.BAHT.BAIXO
ROUNDBAHTUP = ARREDOND.BAHT.CIMA
ROUNDDOWN = ARRED.PARA.BAIXO
ROUNDUP = ARRED.PARA.CIMA
SEC = SEC
SECH = SECH
SERIESSUM = SOMASÉRIE
SIGN = SINAL
SIN = SEN
SINH = SENH
SQRT = RAIZQ
SQRTPI = RAIZPI
SUBTOTAL = SUBTOTAL
SUM = SOMA
SUMIF = SOMA.SE
SUMIFS = SOMA.SE.S
SUMPRODUCT = SOMARPRODUTO
SUMSQ = SOMARQUAD
SUMX2MY2 = SOMAX2DY2
SUMX2PY2 = SOMAX2SY2
SUMXMY2 = SOMAXMY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funções estatísticas (Statistical Functions)
##
AVEDEV = DESV.MÉDIO
AVERAGE = MÉDIA
AVERAGEA = MÉDIAA
AVERAGEIF = MÉDIA.SE
AVERAGEIFS = MÉDIA.SE.S
BETA.DIST = DIST.BETA
BETA.INV = INV.BETA
BINOM.DIST = DISTR.BINOM
BINOM.DIST.RANGE = DIST.BINOM.INTERVALO
BINOM.INV = INV.BINOM
CHISQ.DIST = DIST.CHIQ
CHISQ.DIST.RT = DIST.CHIQ.DIR
CHISQ.INV = INV.CHIQ
CHISQ.INV.RT = INV.CHIQ.DIR
CHISQ.TEST = TESTE.CHIQ
CONFIDENCE.NORM = INT.CONFIANÇA.NORM
CONFIDENCE.T = INT.CONFIANÇA.T
CORREL = CORREL
COUNT = CONTAR
COUNTA = CONTAR.VAL
COUNTBLANK = CONTAR.VAZIO
COUNTIF = CONTAR.SE
COUNTIFS = CONTAR.SE.S
COVARIANCE.P = COVARIÂNCIA.P
COVARIANCE.S = COVARIÂNCIA.S
DEVSQ = DESVQ
EXPON.DIST = DIST.EXPON
F.DIST = DIST.F
F.DIST.RT = DIST.F.DIR
F.INV = INV.F
F.INV.RT = INV.F.DIR
F.TEST = TESTE.F
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PREVISÃO.ETS
FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE
FORECAST.ETS.STAT = PREVISÃO.ETS.ESTATÍSTICA
FORECAST.LINEAR = PREVISÃO.LINEAR
FREQUENCY = FREQUÊNCIA
GAMMA = GAMA
GAMMA.DIST = DIST.GAMA
GAMMA.INV = INV.GAMA
GAMMALN = LNGAMA
GAMMALN.PRECISE = LNGAMA.PRECISO
GAUSS = GAUSS
GEOMEAN = MÉDIA.GEOMÉTRICA
GROWTH = CRESCIMENTO
HARMEAN = MÉDIA.HARMÓNICA
HYPGEOM.DIST = DIST.HIPGEOM
INTERCEPT = INTERCETAR
KURT = CURT
LARGE = MAIOR
LINEST = PROJ.LIN
LOGEST = PROJ.LOG
LOGNORM.DIST = DIST.NORMLOG
LOGNORM.INV = INV.NORMALLOG
MAX = MÁXIMO
MAXA = MÁXIMOA
MAXIFS = MÁXIMO.SE.S
MEDIAN = MED
MIN = MÍNIMO
MINA = MÍNIMOA
MINIFS = MÍNIMO.SE.S
MODE.MULT = MODO.MÚLT
MODE.SNGL = MODO.SIMPLES
NEGBINOM.DIST = DIST.BINOM.NEG
NORM.DIST = DIST.NORMAL
NORM.INV = INV.NORMAL
NORM.S.DIST = DIST.S.NORM
NORM.S.INV = INV.S.NORM
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = ORDEM.PERCENTUAL.EXC
PERCENTRANK.INC = ORDEM.PERCENTUAL.INC
PERMUT = PERMUTAR
PERMUTATIONA = PERMUTAR.R
PHI = PHI
POISSON.DIST = DIST.POISSON
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = ORDEM.MÉD
RANK.EQ = ORDEM.EQ
RSQ = RQUAD
SKEW = DISTORÇÃO
SKEW.P = DISTORÇÃO.P
SLOPE = DECLIVE
SMALL = MENOR
STANDARDIZE = NORMALIZAR
STDEV.P = DESVPAD.P
STDEV.S = DESVPAD.S
STDEVA = DESVPADA
STDEVPA = DESVPADPA
STEYX = EPADYX
T.DIST = DIST.T
T.DIST.2T = DIST.T.2C
T.DIST.RT = DIST.T.DIR
T.INV = INV.T
T.INV.2T = INV.T.2C
T.TEST = TESTE.T
TREND = TENDÊNCIA
TRIMMEAN = MÉDIA.INTERNA
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DIST.WEIBULL
Z.TEST = TESTE.Z
##
## Funções de texto (Text Functions)
##
BAHTTEXT = TEXTO.BAHT
CHAR = CARÁT
CLEAN = LIMPARB
CODE = CÓDIGO
CONCAT = CONCAT
DOLLAR = MOEDA
EXACT = EXATO
FIND = LOCALIZAR
FIXED = FIXA
ISTHAIDIGIT = É.DÍGITO.TAILANDÊS
LEFT = ESQUERDA
LEN = NÚM.CARAT
LOWER = MINÚSCULAS
MID = SEG.TEXTO
NUMBERSTRING = NÚMERO.CADEIA
NUMBERVALUE = VALOR.NÚMERO
PHONETIC = FONÉTICA
PROPER = INICIAL.MAIÚSCULA
REPLACE = SUBSTITUIR
REPT = REPETIR
RIGHT = DIREITA
SEARCH = PROCURAR
SUBSTITUTE = SUBST
T = T
TEXT = TEXTO
TEXTJOIN = UNIRTEXTO
THAIDIGIT = DÍGITO.TAILANDÊS
THAINUMSOUND = SOM.NÚM.TAILANDÊS
THAINUMSTRING = CADEIA.NÚM.TAILANDÊS
THAISTRINGLENGTH = COMP.CADEIA.TAILANDÊS
TRIM = COMPACTAR
UNICHAR = UNICARÁT
UNICODE = UNICODE
UPPER = MAIÚSCULAS
VALUE = VALOR
##
## Funções da Web (Web Functions)
##
ENCODEURL = CODIFICAÇÃOURL
FILTERXML = FILTRARXML
WEBSERVICE = SERVIÇOWEB
##
## Funções de compatibilidade (Compatibility Functions)
##
BETADIST = DISTBETA
BETAINV = BETA.ACUM.INV
BINOMDIST = DISTRBINOM
CEILING = ARRED.EXCESSO
CHIDIST = DIST.CHI
CHIINV = INV.CHI
CHITEST = TESTE.CHI
CONCATENATE = CONCATENAR
CONFIDENCE = INT.CONFIANÇA
COVAR = COVAR
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTEXPON
FDIST = DISTF
FINV = INVF
FLOOR = ARRED.DEFEITO
FORECAST = PREVISÃO
FTEST = TESTEF
GAMMADIST = DISTGAMA
GAMMAINV = INVGAMA
HYPGEOMDIST = DIST.HIPERGEOM
LOGINV = INVLOG
LOGNORMDIST = DIST.NORMALLOG
MODE = MODA
NEGBINOMDIST = DIST.BIN.NEG
NORMDIST = DIST.NORM
NORMINV = INV.NORM
NORMSDIST = DIST.NORMP
NORMSINV = INV.NORMP
PERCENTILE = PERCENTIL
PERCENTRANK = ORDEM.PERCENTUAL
POISSON = POISSON
QUARTILE = QUARTIL
RANK = ORDEM
STDEV = DESVPAD
STDEVP = DESVPADP
TDIST = DISTT
TINV = INVT
TTEST = TESTET
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = TESTEZ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config 0000644 00000000535 15243417764 0020603 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Ceština (Czech)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0 = #DĚLENÍ_NULOU!
VALUE = #HODNOTA!
REF = #ODKAZ!
NAME = #NÁZEV?
NUM = #ČÍSLO!
NA = #NENÍ_K_DISPOZICI
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions 0000644 00000022455 15243417764 0021353 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Ceština (Czech)
##
############################################################
##
## Funkce pro práci s datovými krychlemi (Cube Functions)
##
CUBEKPIMEMBER = CUBEKPIMEMBER
CUBEMEMBER = CUBEMEMBER
CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY
CUBERANKEDMEMBER = CUBERANKEDMEMBER
CUBESET = CUBESET
CUBESETCOUNT = CUBESETCOUNT
CUBEVALUE = CUBEVALUE
##
## Funkce databáze (Database Functions)
##
DAVERAGE = DPRŮMĚR
DCOUNT = DPOČET
DCOUNTA = DPOČET2
DGET = DZÍSKAT
DMAX = DMAX
DMIN = DMIN
DPRODUCT = DSOUČIN
DSTDEV = DSMODCH.VÝBĚR
DSTDEVP = DSMODCH
DSUM = DSUMA
DVAR = DVAR.VÝBĚR
DVARP = DVAR
##
## Funkce data a času (Date & Time Functions)
##
DATE = DATUM
DATEVALUE = DATUMHODN
DAY = DEN
DAYS = DAYS
DAYS360 = ROK360
EDATE = EDATE
EOMONTH = EOMONTH
HOUR = HODINA
ISOWEEKNUM = ISOWEEKNUM
MINUTE = MINUTA
MONTH = MĚSÍC
NETWORKDAYS = NETWORKDAYS
NETWORKDAYS.INTL = NETWORKDAYS.INTL
NOW = NYNÍ
SECOND = SEKUNDA
TIME = ČAS
TIMEVALUE = ČASHODN
TODAY = DNES
WEEKDAY = DENTÝDNE
WEEKNUM = WEEKNUM
WORKDAY = WORKDAY
WORKDAY.INTL = WORKDAY.INTL
YEAR = ROK
YEARFRAC = YEARFRAC
##
## Inženýrské funkce (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN2DEC
BIN2HEX = BIN2HEX
BIN2OCT = BIN2OCT
BITAND = BITAND
BITLSHIFT = BITLSHIFT
BITOR = BITOR
BITRSHIFT = BITRSHIFT
BITXOR = BITXOR
COMPLEX = COMPLEX
CONVERT = CONVERT
DEC2BIN = DEC2BIN
DEC2HEX = DEC2HEX
DEC2OCT = DEC2OCT
DELTA = DELTA
ERF = ERF
ERF.PRECISE = ERF.PRECISE
ERFC = ERFC
ERFC.PRECISE = ERFC.PRECISE
GESTEP = GESTEP
HEX2BIN = HEX2BIN
HEX2DEC = HEX2DEC
HEX2OCT = HEX2OCT
IMABS = IMABS
IMAGINARY = IMAGINARY
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMCONJUGATE
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEXP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMPOWER
IMPRODUCT = IMPRODUCT
IMREAL = IMREAL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMSQRT
IMSUB = IMSUB
IMSUM = IMSUM
IMTAN = IMTAN
OCT2BIN = OCT2BIN
OCT2DEC = OCT2DEC
OCT2HEX = OCT2HEX
##
## Finanční funkce (Financial Functions)
##
ACCRINT = ACCRINT
ACCRINTM = ACCRINTM
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = COUPDAYBS
COUPDAYS = COUPDAYS
COUPDAYSNC = COUPDAYSNC
COUPNCD = COUPNCD
COUPNUM = COUPNUM
COUPPCD = COUPPCD
CUMIPMT = CUMIPMT
CUMPRINC = CUMPRINC
DB = ODPIS.ZRYCH
DDB = ODPIS.ZRYCH2
DISC = DISC
DOLLARDE = DOLLARDE
DOLLARFR = DOLLARFR
DURATION = DURATION
EFFECT = EFFECT
FV = BUDHODNOTA
FVSCHEDULE = FVSCHEDULE
INTRATE = INTRATE
IPMT = PLATBA.ÚROK
IRR = MÍRA.VÝNOSNOSTI
ISPMT = ISPMT
MDURATION = MDURATION
MIRR = MOD.MÍRA.VÝNOSNOSTI
NOMINAL = NOMINAL
NPER = POČET.OBDOBÍ
NPV = ČISTÁ.SOUČHODNOTA
ODDFPRICE = ODDFPRICE
ODDFYIELD = ODDFYIELD
ODDLPRICE = ODDLPRICE
ODDLYIELD = ODDLYIELD
PDURATION = PDURATION
PMT = PLATBA
PPMT = PLATBA.ZÁKLAD
PRICE = PRICE
PRICEDISC = PRICEDISC
PRICEMAT = PRICEMAT
PV = SOUČHODNOTA
RATE = ÚROKOVÁ.MÍRA
RECEIVED = RECEIVED
RRI = RRI
SLN = ODPIS.LIN
SYD = ODPIS.NELIN
TBILLEQ = TBILLEQ
TBILLPRICE = TBILLPRICE
TBILLYIELD = TBILLYIELD
VDB = ODPIS.ZA.INT
XIRR = XIRR
XNPV = XNPV
YIELD = YIELD
YIELDDISC = YIELDDISC
YIELDMAT = YIELDMAT
##
## Informační funkce (Information Functions)
##
CELL = POLÍČKO
ERROR.TYPE = CHYBA.TYP
INFO = O.PROSTŘEDÍ
ISBLANK = JE.PRÁZDNÉ
ISERR = JE.CHYBA
ISERROR = JE.CHYBHODN
ISEVEN = ISEVEN
ISFORMULA = ISFORMULA
ISLOGICAL = JE.LOGHODN
ISNA = JE.NEDEF
ISNONTEXT = JE.NETEXT
ISNUMBER = JE.ČISLO
ISODD = ISODD
ISREF = JE.ODKAZ
ISTEXT = JE.TEXT
N = N
NA = NEDEF
SHEET = SHEET
SHEETS = SHEETS
TYPE = TYP
##
## Logické funkce (Logical Functions)
##
AND = A
FALSE = NEPRAVDA
IF = KDYŽ
IFERROR = IFERROR
IFNA = IFNA
IFS = IFS
NOT = NE
OR = NEBO
SWITCH = SWITCH
TRUE = PRAVDA
XOR = XOR
##
## Vyhledávací funkce a funkce pro odkazy (Lookup & Reference Functions)
##
ADDRESS = ODKAZ
AREAS = POČET.BLOKŮ
CHOOSE = ZVOLIT
COLUMN = SLOUPEC
COLUMNS = SLOUPCE
FORMULATEXT = FORMULATEXT
GETPIVOTDATA = ZÍSKATKONTDATA
HLOOKUP = VVYHLEDAT
HYPERLINK = HYPERTEXTOVÝ.ODKAZ
INDEX = INDEX
INDIRECT = NEPŘÍMÝ.ODKAZ
LOOKUP = VYHLEDAT
MATCH = POZVYHLEDAT
OFFSET = POSUN
ROW = ŘÁDEK
ROWS = ŘÁDKY
RTD = RTD
TRANSPOSE = TRANSPOZICE
VLOOKUP = SVYHLEDAT
##
## Matematické a trigonometrické funkce (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGGREGATE
ARABIC = ARABIC
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTG
ATAN2 = ARCTG2
ATANH = ARCTGH
BASE = BASE
CEILING.MATH = CEILING.MATH
COMBIN = KOMBINACE
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = DEGREES
EVEN = ZAOKROUHLIT.NA.SUDÉ
EXP = EXP
FACT = FAKTORIÁL
FACTDOUBLE = FACTDOUBLE
FLOOR.MATH = FLOOR.MATH
GCD = GCD
INT = CELÁ.ČÁST
LCM = LCM
LN = LN
LOG = LOGZ
LOG10 = LOG
MDETERM = DETERMINANT
MINVERSE = INVERZE
MMULT = SOUČIN.MATIC
MOD = MOD
MROUND = MROUND
MULTINOMIAL = MULTINOMIAL
MUNIT = MUNIT
ODD = ZAOKROUHLIT.NA.LICHÉ
PI = PI
POWER = POWER
PRODUCT = SOUČIN
QUOTIENT = QUOTIENT
RADIANS = RADIANS
RAND = NÁHČÍSLO
RANDBETWEEN = RANDBETWEEN
ROMAN = ROMAN
ROUND = ZAOKROUHLIT
ROUNDDOWN = ROUNDDOWN
ROUNDUP = ROUNDUP
SEC = SEC
SECH = SECH
SERIESSUM = SERIESSUM
SIGN = SIGN
SIN = SIN
SINH = SINH
SQRT = ODMOCNINA
SQRTPI = SQRTPI
SUBTOTAL = SUBTOTAL
SUM = SUMA
SUMIF = SUMIF
SUMIFS = SUMIFS
SUMPRODUCT = SOUČIN.SKALÁRNÍ
SUMSQ = SUMA.ČTVERCŮ
SUMX2MY2 = SUMX2MY2
SUMX2PY2 = SUMX2PY2
SUMXMY2 = SUMXMY2
TAN = TG
TANH = TGH
TRUNC = USEKNOUT
##
## Statistické funkce (Statistical Functions)
##
AVEDEV = PRŮMODCHYLKA
AVERAGE = PRŮMĚR
AVERAGEA = AVERAGEA
AVERAGEIF = AVERAGEIF
AVERAGEIFS = AVERAGEIFS
BETA.DIST = BETA.DIST
BETA.INV = BETA.INV
BINOM.DIST = BINOM.DIST
BINOM.DIST.RANGE = BINOM.DIST.RANGE
BINOM.INV = BINOM.INV
CHISQ.DIST = CHISQ.DIST
CHISQ.DIST.RT = CHISQ.DIST.RT
CHISQ.INV = CHISQ.INV
CHISQ.INV.RT = CHISQ.INV.RT
CHISQ.TEST = CHISQ.TEST
CONFIDENCE.NORM = CONFIDENCE.NORM
CONFIDENCE.T = CONFIDENCE.T
CORREL = CORREL
COUNT = POČET
COUNTA = POČET2
COUNTBLANK = COUNTBLANK
COUNTIF = COUNTIF
COUNTIFS = COUNTIFS
COVARIANCE.P = COVARIANCE.P
COVARIANCE.S = COVARIANCE.S
DEVSQ = DEVSQ
EXPON.DIST = EXPON.DIST
F.DIST = F.DIST
F.DIST.RT = F.DIST.RT
F.INV = F.INV
F.INV.RT = F.INV.RT
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = FORECAST.ETS
FORECAST.ETS.CONFINT = FORECAST.ETS.CONFINT
FORECAST.ETS.SEASONALITY = FORECAST.ETS.SEASONALITY
FORECAST.ETS.STAT = FORECAST.ETS.STAT
FORECAST.LINEAR = FORECAST.LINEAR
FREQUENCY = ČETNOSTI
GAMMA = GAMMA
GAMMA.DIST = GAMMA.DIST
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PRECISE
GAUSS = GAUSS
GEOMEAN = GEOMEAN
GROWTH = LOGLINTREND
HARMEAN = HARMEAN
HYPGEOM.DIST = HYPGEOM.DIST
INTERCEPT = INTERCEPT
KURT = KURT
LARGE = LARGE
LINEST = LINREGRESE
LOGEST = LOGLINREGRESE
LOGNORM.DIST = LOGNORM.DIST
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAXIFS
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MINIFS
MODE.MULT = MODE.MULT
MODE.SNGL = MODE.SNGL
NEGBINOM.DIST = NEGBINOM.DIST
NORM.DIST = NORM.DIST
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.DIST
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = PERCENTRANK.EXC
PERCENTRANK.INC = PERCENTRANK.INC
PERMUT = PERMUTACE
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = POISSON.DIST
PROB = PROB
QUARTILE.EXC = QUARTIL.EXC
QUARTILE.INC = QUARTIL.INC
RANK.AVG = RANK.AVG
RANK.EQ = RANK.EQ
RSQ = RKQ
SKEW = SKEW
SKEW.P = SKEW.P
SLOPE = SLOPE
SMALL = SMALL
STANDARDIZE = STANDARDIZE
STDEV.P = SMODCH.P
STDEV.S = SMODCH.VÝBĚR.S
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = STEYX
T.DIST = T.DIST
T.DIST.2T = T.DIST.2T
T.DIST.RT = T.DIST.RT
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = LINTREND
TRIMMEAN = TRIMMEAN
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.DIST
Z.TEST = Z.TEST
##
## Textové funkce (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = ZNAK
CLEAN = VYČISTIT
CODE = KÓD
CONCAT = CONCAT
DOLLAR = KČ
EXACT = STEJNÉ
FIND = NAJÍT
FIXED = ZAOKROUHLIT.NA.TEXT
LEFT = ZLEVA
LEN = DÉLKA
LOWER = MALÁ
MID = ČÁST
NUMBERVALUE = NUMBERVALUE
PHONETIC = ZVUKOVÉ
PROPER = VELKÁ2
REPLACE = NAHRADIT
REPT = OPAKOVAT
RIGHT = ZPRAVA
SEARCH = HLEDAT
SUBSTITUTE = DOSADIT
T = T
TEXT = HODNOTA.NA.TEXT
TEXTJOIN = TEXTJOIN
TRIM = PROČISTIT
UNICHAR = UNICHAR
UNICODE = UNICODE
UPPER = VELKÁ
VALUE = HODNOTA
##
## Webové funkce (Web Functions)
##
ENCODEURL = ENCODEURL
FILTERXML = FILTERXML
WEBSERVICE = WEBSERVICE
##
## Funkce pro kompatibilitu (Compatibility Functions)
##
BETADIST = BETADIST
BETAINV = BETAINV
BINOMDIST = BINOMDIST
CEILING = ZAOKR.NAHORU
CHIDIST = CHIDIST
CHIINV = CHIINV
CHITEST = CHITEST
CONCATENATE = CONCATENATE
CONFIDENCE = CONFIDENCE
COVAR = COVAR
CRITBINOM = CRITBINOM
EXPONDIST = EXPONDIST
FDIST = FDIST
FINV = FINV
FLOOR = ZAOKR.DOLŮ
FORECAST = FORECAST
FTEST = FTEST
GAMMADIST = GAMMADIST
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOMDIST
LOGINV = LOGINV
LOGNORMDIST = LOGNORMDIST
MODE = MODE
NEGBINOMDIST = NEGBINOMDIST
NORMDIST = NORMDIST
NORMINV = NORMINV
NORMSDIST = NORMSDIST
NORMSINV = NORMSINV
PERCENTILE = PERCENTIL
PERCENTRANK = PERCENTRANK
POISSON = POISSON
QUARTILE = QUARTIL
RANK = RANK
STDEV = SMODCH.VÝBĚR
STDEVP = SMODCH
TDIST = TDIST
TINV = TINV
TTEST = TTEST
VAR = VAR.VÝBĚR
VARP = VAR
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config 0000644 00000000467 15243417764 0020566 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Dansk (Danish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NUL!
DIV0
VALUE = #VÆRDI!
REF = #REFERENCE!
NAME = #NAVN?
NUM
NA = #I/T
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions 0000644 00000024441 15243417764 0021327 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Dansk (Danish)
##
############################################################
##
## Kubefunktioner (Cube Functions)
##
CUBEKPIMEMBER = KUBE.KPI.MEDLEM
CUBEMEMBER = KUBEMEDLEM
CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB
CUBERANKEDMEMBER = KUBERANGERET.MEDLEM
CUBESET = KUBESÆT
CUBESETCOUNT = KUBESÆT.ANTAL
CUBEVALUE = KUBEVÆRDI
##
## Databasefunktioner (Database Functions)
##
DAVERAGE = DMIDDEL
DCOUNT = DTÆL
DCOUNTA = DTÆLV
DGET = DHENT
DMAX = DMAKS
DMIN = DMIN
DPRODUCT = DPRODUKT
DSTDEV = DSTDAFV
DSTDEVP = DSTDAFVP
DSUM = DSUM
DVAR = DVARIANS
DVARP = DVARIANSP
##
## Dato- og klokkeslætfunktioner (Date & Time Functions)
##
DATE = DATO
DATEDIF = DATO.FORSKEL
DATESTRING = DATOSTRENG
DATEVALUE = DATOVÆRDI
DAY = DAG
DAYS = DAGE
DAYS360 = DAGE360
EDATE = EDATO
EOMONTH = SLUT.PÅ.MÅNED
HOUR = TIME
ISOWEEKNUM = ISOUGE.NR
MINUTE = MINUT
MONTH = MÅNED
NETWORKDAYS = ANTAL.ARBEJDSDAGE
NETWORKDAYS.INTL = ANTAL.ARBEJDSDAGE.INTL
NOW = NU
SECOND = SEKUND
THAIDAYOFWEEK = THAILANDSKUGEDAG
THAIMONTHOFYEAR = THAILANDSKMÅNED
THAIYEAR = THAILANDSKÅR
TIME = TID
TIMEVALUE = TIDSVÆRDI
TODAY = IDAG
WEEKDAY = UGEDAG
WEEKNUM = UGE.NR
WORKDAY = ARBEJDSDAG
WORKDAY.INTL = ARBEJDSDAG.INTL
YEAR = ÅR
YEARFRAC = ÅR.BRØK
##
## Tekniske funktioner (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.TIL.DEC
BIN2HEX = BIN.TIL.HEX
BIN2OCT = BIN.TIL.OKT
BITAND = BITOG
BITLSHIFT = BITLSKIFT
BITOR = BITELLER
BITRSHIFT = BITRSKIFT
BITXOR = BITXELLER
COMPLEX = KOMPLEKS
CONVERT = KONVERTER
DEC2BIN = DEC.TIL.BIN
DEC2HEX = DEC.TIL.HEX
DEC2OCT = DEC.TIL.OKT
DELTA = DELTA
ERF = FEJLFUNK
ERF.PRECISE = ERF.PRECISE
ERFC = FEJLFUNK.KOMP
ERFC.PRECISE = ERFC.PRECISE
GESTEP = GETRIN
HEX2BIN = HEX.TIL.BIN
HEX2DEC = HEX.TIL.DEC
HEX2OCT = HEX.TIL.OKT
IMABS = IMAGABS
IMAGINARY = IMAGINÆR
IMARGUMENT = IMAGARGUMENT
IMCONJUGATE = IMAGKONJUGERE
IMCOS = IMAGCOS
IMCOSH = IMAGCOSH
IMCOT = IMAGCOT
IMCSC = IMAGCSC
IMCSCH = IMAGCSCH
IMDIV = IMAGDIV
IMEXP = IMAGEKSP
IMLN = IMAGLN
IMLOG10 = IMAGLOG10
IMLOG2 = IMAGLOG2
IMPOWER = IMAGPOTENS
IMPRODUCT = IMAGPRODUKT
IMREAL = IMAGREELT
IMSEC = IMAGSEC
IMSECH = IMAGSECH
IMSIN = IMAGSIN
IMSINH = IMAGSINH
IMSQRT = IMAGKVROD
IMSUB = IMAGSUB
IMSUM = IMAGSUM
IMTAN = IMAGTAN
OCT2BIN = OKT.TIL.BIN
OCT2DEC = OKT.TIL.DEC
OCT2HEX = OKT.TIL.HEX
##
## Finansielle funktioner (Financial Functions)
##
ACCRINT = PÅLØBRENTE
ACCRINTM = PÅLØBRENTE.UDLØB
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = KUPONDAGE.SA
COUPDAYS = KUPONDAGE.A
COUPDAYSNC = KUPONDAGE.ANK
COUPNCD = KUPONDAG.NÆSTE
COUPNUM = KUPONBETALINGER
COUPPCD = KUPONDAG.FORRIGE
CUMIPMT = AKKUM.RENTE
CUMPRINC = AKKUM.HOVEDSTOL
DB = DB
DDB = DSA
DISC = DISKONTO
DOLLARDE = KR.DECIMAL
DOLLARFR = KR.BRØK
DURATION = VARIGHED
EFFECT = EFFEKTIV.RENTE
FV = FV
FVSCHEDULE = FVTABEL
INTRATE = RENTEFOD
IPMT = R.YDELSE
IRR = IA
ISPMT = ISPMT
MDURATION = MVARIGHED
MIRR = MIA
NOMINAL = NOMINEL
NPER = NPER
NPV = NUTIDSVÆRDI
ODDFPRICE = ULIGE.KURS.PÅLYDENDE
ODDFYIELD = ULIGE.FØRSTE.AFKAST
ODDLPRICE = ULIGE.SIDSTE.KURS
ODDLYIELD = ULIGE.SIDSTE.AFKAST
PDURATION = PVARIGHED
PMT = YDELSE
PPMT = H.YDELSE
PRICE = KURS
PRICEDISC = KURS.DISKONTO
PRICEMAT = KURS.UDLØB
PV = NV
RATE = RENTE
RECEIVED = MODTAGET.VED.UDLØB
RRI = RRI
SLN = LA
SYD = ÅRSAFSKRIVNING
TBILLEQ = STATSOBLIGATION
TBILLPRICE = STATSOBLIGATION.KURS
TBILLYIELD = STATSOBLIGATION.AFKAST
VDB = VSA
XIRR = INTERN.RENTE
XNPV = NETTO.NUTIDSVÆRDI
YIELD = AFKAST
YIELDDISC = AFKAST.DISKONTO
YIELDMAT = AFKAST.UDLØBSDATO
##
## Informationsfunktioner (Information Functions)
##
CELL = CELLE
ERROR.TYPE = FEJLTYPE
INFO = INFO
ISBLANK = ER.TOM
ISERR = ER.FJL
ISERROR = ER.FEJL
ISEVEN = ER.LIGE
ISFORMULA = ER.FORMEL
ISLOGICAL = ER.LOGISK
ISNA = ER.IKKE.TILGÆNGELIG
ISNONTEXT = ER.IKKE.TEKST
ISNUMBER = ER.TAL
ISODD = ER.ULIGE
ISREF = ER.REFERENCE
ISTEXT = ER.TEKST
N = TAL
NA = IKKE.TILGÆNGELIG
SHEET = ARK
SHEETS = ARK.FLERE
TYPE = VÆRDITYPE
##
## Logiske funktioner (Logical Functions)
##
AND = OG
FALSE = FALSK
IF = HVIS
IFERROR = HVIS.FEJL
IFNA = HVISIT
IFS = HVISER
NOT = IKKE
OR = ELLER
SWITCH = SKIFT
TRUE = SAND
XOR = XELLER
##
## Opslags- og referencefunktioner (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = OMRÅDER
CHOOSE = VÆLG
COLUMN = KOLONNE
COLUMNS = KOLONNER
FORMULATEXT = FORMELTEKST
GETPIVOTDATA = GETPIVOTDATA
HLOOKUP = VOPSLAG
HYPERLINK = HYPERLINK
INDEX = INDEKS
INDIRECT = INDIREKTE
LOOKUP = SLÅ.OP
MATCH = SAMMENLIGN
OFFSET = FORSKYDNING
ROW = RÆKKE
ROWS = RÆKKER
RTD = RTD
TRANSPOSE = TRANSPONER
VLOOKUP = LOPSLAG
*RC = RK
##
## Matematiske og trigonometriske funktioner (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = SAMLING
ARABIC = ARABISK
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANH
BASE = BASIS
CEILING.MATH = LOFT.MAT
CEILING.PRECISE = LOFT.PRECISE
COMBIN = KOMBIN
COMBINA = KOMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = GRADER
ECMA.CEILING = ECMA.LOFT
EVEN = LIGE
EXP = EKSP
FACT = FAKULTET
FACTDOUBLE = DOBBELT.FAKULTET
FLOOR.MATH = AFRUND.BUND.MAT
FLOOR.PRECISE = AFRUND.GULV.PRECISE
GCD = STØRSTE.FÆLLES.DIVISOR
INT = HELTAL
ISO.CEILING = ISO.LOFT
LCM = MINDSTE.FÆLLES.MULTIPLUM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERT
MMULT = MPRODUKT
MOD = REST
MROUND = MAFRUND
MULTINOMIAL = MULTINOMIAL
MUNIT = MENHED
ODD = ULIGE
PI = PI
POWER = POTENS
PRODUCT = PRODUKT
QUOTIENT = KVOTIENT
RADIANS = RADIANER
RAND = SLUMP
RANDBETWEEN = SLUMPMELLEM
ROMAN = ROMERTAL
ROUND = AFRUND
ROUNDBAHTDOWN = RUNDBAHTNED
ROUNDBAHTUP = RUNDBAHTOP
ROUNDDOWN = RUND.NED
ROUNDUP = RUND.OP
SEC = SEC
SECH = SECH
SERIESSUM = SERIESUM
SIGN = FORTEGN
SIN = SIN
SINH = SINH
SQRT = KVROD
SQRTPI = KVRODPI
SUBTOTAL = SUBTOTAL
SUM = SUM
SUMIF = SUM.HVIS
SUMIFS = SUM.HVISER
SUMPRODUCT = SUMPRODUKT
SUMSQ = SUMKV
SUMX2MY2 = SUMX2MY2
SUMX2PY2 = SUMX2PY2
SUMXMY2 = SUMXMY2
TAN = TAN
TANH = TANH
TRUNC = AFKORT
##
## Statistiske funktioner (Statistical Functions)
##
AVEDEV = MAD
AVERAGE = MIDDEL
AVERAGEA = MIDDELV
AVERAGEIF = MIDDEL.HVIS
AVERAGEIFS = MIDDEL.HVISER
BETA.DIST = BETA.FORDELING
BETA.INV = BETA.INV
BINOM.DIST = BINOMIAL.FORDELING
BINOM.DIST.RANGE = BINOMIAL.DIST.INTERVAL
BINOM.INV = BINOMIAL.INV
CHISQ.DIST = CHI2.FORDELING
CHISQ.DIST.RT = CHI2.FORD.RT
CHISQ.INV = CHI2.INV
CHISQ.INV.RT = CHI2.INV.RT
CHISQ.TEST = CHI2.TEST
CONFIDENCE.NORM = KONFIDENS.NORM
CONFIDENCE.T = KONFIDENST
CORREL = KORRELATION
COUNT = TÆL
COUNTA = TÆLV
COUNTBLANK = ANTAL.BLANKE
COUNTIF = TÆL.HVIS
COUNTIFS = TÆL.HVISER
COVARIANCE.P = KOVARIANS.P
COVARIANCE.S = KOVARIANS.S
DEVSQ = SAK
EXPON.DIST = EKSP.FORDELING
F.DIST = F.FORDELING
F.DIST.RT = F.FORDELING.RT
F.INV = F.INV
F.INV.RT = F.INV.RT
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOSE.ETS
FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SÆSONUDSVING
FORECAST.ETS.STAT = PROGNOSE.ETS.STAT
FORECAST.LINEAR = PROGNOSE.LINEÆR
FREQUENCY = FREKVENS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.FORDELING
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PRECISE
GAUSS = GAUSS
GEOMEAN = GEOMIDDELVÆRDI
GROWTH = FORØGELSE
HARMEAN = HARMIDDELVÆRDI
HYPGEOM.DIST = HYPGEO.FORDELING
INTERCEPT = SKÆRING
KURT = TOPSTEJL
LARGE = STØRSTE
LINEST = LINREGR
LOGEST = LOGREGR
LOGNORM.DIST = LOGNORM.FORDELING
LOGNORM.INV = LOGNORM.INV
MAX = MAKS
MAXA = MAKSV
MAXIFS = MAKSHVISER
MEDIAN = MEDIAN
MIN = MIN
MINA = MINV
MINIFS = MINHVISER
MODE.MULT = HYPPIGST.FLERE
MODE.SNGL = HYPPIGST.ENKELT
NEGBINOM.DIST = NEGBINOM.FORDELING
NORM.DIST = NORMAL.FORDELING
NORM.INV = NORM.INV
NORM.S.DIST = STANDARD.NORM.FORDELING
NORM.S.INV = STANDARD.NORM.INV
PEARSON = PEARSON
PERCENTILE.EXC = FRAKTIL.UDELAD
PERCENTILE.INC = FRAKTIL.MEDTAG
PERCENTRANK.EXC = PROCENTPLADS.UDELAD
PERCENTRANK.INC = PROCENTPLADS.MEDTAG
PERMUT = PERMUT
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = POISSON.FORDELING
PROB = SANDSYNLIGHED
QUARTILE.EXC = KVARTIL.UDELAD
QUARTILE.INC = KVARTIL.MEDTAG
RANK.AVG = PLADS.GNSN
RANK.EQ = PLADS.LIGE
RSQ = FORKLARINGSGRAD
SKEW = SKÆVHED
SKEW.P = SKÆVHED.P
SLOPE = STIGNING
SMALL = MINDSTE
STANDARDIZE = STANDARDISER
STDEV.P = STDAFV.P
STDEV.S = STDAFV.S
STDEVA = STDAFVV
STDEVPA = STDAFVPV
STEYX = STFYX
T.DIST = T.FORDELING
T.DIST.2T = T.FORDELING.2T
T.DIST.RT = T.FORDELING.RT
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TENDENS
TRIMMEAN = TRIMMIDDELVÆRDI
VAR.P = VARIANS.P
VAR.S = VARIANS.S
VARA = VARIANSV
VARPA = VARIANSPV
WEIBULL.DIST = WEIBULL.FORDELING
Z.TEST = Z.TEST
##
## Tekstfunktioner (Text Functions)
##
BAHTTEXT = BAHTTEKST
CHAR = TEGN
CLEAN = RENS
CODE = KODE
CONCAT = CONCAT
DOLLAR = KR
EXACT = EKSAKT
FIND = FIND
FIXED = FAST
ISTHAIDIGIT = ERTHAILANDSKCIFFER
LEFT = VENSTRE
LEN = LÆNGDE
LOWER = SMÅ.BOGSTAVER
MID = MIDT
NUMBERSTRING = TALSTRENG
NUMBERVALUE = TALVÆRDI
PHONETIC = FONETISK
PROPER = STORT.FORBOGSTAV
REPLACE = ERSTAT
REPT = GENTAG
RIGHT = HØJRE
SEARCH = SØG
SUBSTITUTE = UDSKIFT
T = T
TEXT = TEKST
TEXTJOIN = TEKST.KOMBINER
THAIDIGIT = THAILANDSKCIFFER
THAINUMSOUND = THAILANDSKNUMLYD
THAINUMSTRING = THAILANDSKNUMSTRENG
THAISTRINGLENGTH = THAILANDSKSTRENGLÆNGDE
TRIM = FJERN.OVERFLØDIGE.BLANKE
UNICHAR = UNICHAR
UNICODE = UNICODE
UPPER = STORE.BOGSTAVER
VALUE = VÆRDI
##
## Webfunktioner (Web Functions)
##
ENCODEURL = KODNINGSURL
FILTERXML = FILTRERXML
WEBSERVICE = WEBTJENESTE
##
## Kompatibilitetsfunktioner (Compatibility Functions)
##
BETADIST = BETAFORDELING
BETAINV = BETAINV
BINOMDIST = BINOMIALFORDELING
CEILING = AFRUND.LOFT
CHIDIST = CHIFORDELING
CHIINV = CHIINV
CHITEST = CHITEST
CONCATENATE = SAMMENKÆDNING
CONFIDENCE = KONFIDENSINTERVAL
COVAR = KOVARIANS
CRITBINOM = KRITBINOM
EXPONDIST = EKSPFORDELING
FDIST = FFORDELING
FINV = FINV
FLOOR = AFRUND.GULV
FORECAST = PROGNOSE
FTEST = FTEST
GAMMADIST = GAMMAFORDELING
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOFORDELING
LOGINV = LOGINV
LOGNORMDIST = LOGNORMFORDELING
MODE = HYPPIGST
NEGBINOMDIST = NEGBINOMFORDELING
NORMDIST = NORMFORDELING
NORMINV = NORMINV
NORMSDIST = STANDARDNORMFORDELING
NORMSINV = STANDARDNORMINV
PERCENTILE = FRAKTIL
PERCENTRANK = PROCENTPLADS
POISSON = POISSON
QUARTILE = KVARTIL
RANK = PLADS
STDEV = STDAFV
STDEVP = STDAFVP
TDIST = TFORDELING
TINV = TINV
TTEST = TTEST
VAR = VARIANS
VARP = VARIANSP
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config 0000644 00000000455 15243417764 0020613 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Italiano (Italian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0
VALUE = #VALORE!
REF = #RIF!
NAME = #NOME?
NUM
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions 0000644 00000025234 15243417764 0021360 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Italiano (Italian)
##
############################################################
##
## Funzioni cubo (Cube Functions)
##
CUBEKPIMEMBER = MEMBRO.KPI.CUBO
CUBEMEMBER = MEMBRO.CUBO
CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO
CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO
CUBESET = SET.CUBO
CUBESETCOUNT = CONTA.SET.CUBO
CUBEVALUE = VALORE.CUBO
##
## Funzioni di database (Database Functions)
##
DAVERAGE = DB.MEDIA
DCOUNT = DB.CONTA.NUMERI
DCOUNTA = DB.CONTA.VALORI
DGET = DB.VALORI
DMAX = DB.MAX
DMIN = DB.MIN
DPRODUCT = DB.PRODOTTO
DSTDEV = DB.DEV.ST
DSTDEVP = DB.DEV.ST.POP
DSUM = DB.SOMMA
DVAR = DB.VAR
DVARP = DB.VAR.POP
##
## Funzioni data e ora (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATA.DIFF
DATESTRING = DATA.STRINGA
DATEVALUE = DATA.VALORE
DAY = GIORNO
DAYS = GIORNI
DAYS360 = GIORNO360
EDATE = DATA.MESE
EOMONTH = FINE.MESE
HOUR = ORA
ISOWEEKNUM = NUM.SETTIMANA.ISO
MINUTE = MINUTO
MONTH = MESE
NETWORKDAYS = GIORNI.LAVORATIVI.TOT
NETWORKDAYS.INTL = GIORNI.LAVORATIVI.TOT.INTL
NOW = ADESSO
SECOND = SECONDO
THAIDAYOFWEEK = THAIGIORNODELLASETTIMANA
THAIMONTHOFYEAR = THAIMESEDELLANNO
THAIYEAR = THAIANNO
TIME = ORARIO
TIMEVALUE = ORARIO.VALORE
TODAY = OGGI
WEEKDAY = GIORNO.SETTIMANA
WEEKNUM = NUM.SETTIMANA
WORKDAY = GIORNO.LAVORATIVO
WORKDAY.INTL = GIORNO.LAVORATIVO.INTL
YEAR = ANNO
YEARFRAC = FRAZIONE.ANNO
##
## Funzioni ingegneristiche (Engineering Functions)
##
BESSELI = BESSEL.I
BESSELJ = BESSEL.J
BESSELK = BESSEL.K
BESSELY = BESSEL.Y
BIN2DEC = BINARIO.DECIMALE
BIN2HEX = BINARIO.HEX
BIN2OCT = BINARIO.OCT
BITAND = BITAND
BITLSHIFT = BIT.SPOSTA.SX
BITOR = BITOR
BITRSHIFT = BIT.SPOSTA.DX
BITXOR = BITXOR
COMPLEX = COMPLESSO
CONVERT = CONVERTI
DEC2BIN = DECIMALE.BINARIO
DEC2HEX = DECIMALE.HEX
DEC2OCT = DECIMALE.OCT
DELTA = DELTA
ERF = FUNZ.ERRORE
ERF.PRECISE = FUNZ.ERRORE.PRECISA
ERFC = FUNZ.ERRORE.COMP
ERFC.PRECISE = FUNZ.ERRORE.COMP.PRECISA
GESTEP = SOGLIA
HEX2BIN = HEX.BINARIO
HEX2DEC = HEX.DECIMALE
HEX2OCT = HEX.OCT
IMABS = COMP.MODULO
IMAGINARY = COMP.IMMAGINARIO
IMARGUMENT = COMP.ARGOMENTO
IMCONJUGATE = COMP.CONIUGATO
IMCOS = COMP.COS
IMCOSH = COMP.COSH
IMCOT = COMP.COT
IMCSC = COMP.CSC
IMCSCH = COMP.CSCH
IMDIV = COMP.DIV
IMEXP = COMP.EXP
IMLN = COMP.LN
IMLOG10 = COMP.LOG10
IMLOG2 = COMP.LOG2
IMPOWER = COMP.POTENZA
IMPRODUCT = COMP.PRODOTTO
IMREAL = COMP.PARTE.REALE
IMSEC = COMP.SEC
IMSECH = COMP.SECH
IMSIN = COMP.SEN
IMSINH = COMP.SENH
IMSQRT = COMP.RADQ
IMSUB = COMP.DIFF
IMSUM = COMP.SOMMA
IMTAN = COMP.TAN
OCT2BIN = OCT.BINARIO
OCT2DEC = OCT.DECIMALE
OCT2HEX = OCT.HEX
##
## Funzioni finanziarie (Financial Functions)
##
ACCRINT = INT.MATURATO.PER
ACCRINTM = INT.MATURATO.SCAD
AMORDEGRC = AMMORT.DEGR
AMORLINC = AMMORT.PER
COUPDAYBS = GIORNI.CED.INIZ.LIQ
COUPDAYS = GIORNI.CED
COUPDAYSNC = GIORNI.CED.NUOVA
COUPNCD = DATA.CED.SUCC
COUPNUM = NUM.CED
COUPPCD = DATA.CED.PREC
CUMIPMT = INT.CUMUL
CUMPRINC = CAP.CUM
DB = AMMORT.FISSO
DDB = AMMORT
DISC = TASSO.SCONTO
DOLLARDE = VALUTA.DEC
DOLLARFR = VALUTA.FRAZ
DURATION = DURATA
EFFECT = EFFETTIVO
FV = VAL.FUT
FVSCHEDULE = VAL.FUT.CAPITALE
INTRATE = TASSO.INT
IPMT = INTERESSI
IRR = TIR.COST
ISPMT = INTERESSE.RATA
MDURATION = DURATA.M
MIRR = TIR.VAR
NOMINAL = NOMINALE
NPER = NUM.RATE
NPV = VAN
ODDFPRICE = PREZZO.PRIMO.IRR
ODDFYIELD = REND.PRIMO.IRR
ODDLPRICE = PREZZO.ULTIMO.IRR
ODDLYIELD = REND.ULTIMO.IRR
PDURATION = DURATA.P
PMT = RATA
PPMT = P.RATA
PRICE = PREZZO
PRICEDISC = PREZZO.SCONT
PRICEMAT = PREZZO.SCAD
PV = VA
RATE = TASSO
RECEIVED = RICEV.SCAD
RRI = RIT.INVEST.EFFETT
SLN = AMMORT.COST
SYD = AMMORT.ANNUO
TBILLEQ = BOT.EQUIV
TBILLPRICE = BOT.PREZZO
TBILLYIELD = BOT.REND
VDB = AMMORT.VAR
XIRR = TIR.X
XNPV = VAN.X
YIELD = REND
YIELDDISC = REND.TITOLI.SCONT
YIELDMAT = REND.SCAD
##
## Funzioni relative alle informazioni (Information Functions)
##
CELL = CELLA
ERROR.TYPE = ERRORE.TIPO
INFO = AMBIENTE.INFO
ISBLANK = VAL.VUOTO
ISERR = VAL.ERR
ISERROR = VAL.ERRORE
ISEVEN = VAL.PARI
ISFORMULA = VAL.FORMULA
ISLOGICAL = VAL.LOGICO
ISNA = VAL.NON.DISP
ISNONTEXT = VAL.NON.TESTO
ISNUMBER = VAL.NUMERO
ISODD = VAL.DISPARI
ISREF = VAL.RIF
ISTEXT = VAL.TESTO
N = NUM
NA = NON.DISP
SHEET = FOGLIO
SHEETS = FOGLI
TYPE = TIPO
##
## Funzioni logiche (Logical Functions)
##
AND = E
FALSE = FALSO
IF = SE
IFERROR = SE.ERRORE
IFNA = SE.NON.DISP.
IFS = PIÙ.SE
NOT = NON
OR = O
SWITCH = SWITCH
TRUE = VERO
XOR = XOR
##
## Funzioni di ricerca e di riferimento (Lookup & Reference Functions)
##
ADDRESS = INDIRIZZO
AREAS = AREE
CHOOSE = SCEGLI
COLUMN = RIF.COLONNA
COLUMNS = COLONNE
FORMULATEXT = TESTO.FORMULA
GETPIVOTDATA = INFO.DATI.TAB.PIVOT
HLOOKUP = CERCA.ORIZZ
HYPERLINK = COLLEG.IPERTESTUALE
INDEX = INDICE
INDIRECT = INDIRETTO
LOOKUP = CERCA
MATCH = CONFRONTA
OFFSET = SCARTO
ROW = RIF.RIGA
ROWS = RIGHE
RTD = DATITEMPOREALE
TRANSPOSE = MATR.TRASPOSTA
VLOOKUP = CERCA.VERT
##
## Funzioni matematiche e trigonometriche (Math & Trig Functions)
##
ABS = ASS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = AGGREGA
ARABIC = ARABO
ASIN = ARCSEN
ASINH = ARCSENH
ATAN = ARCTAN
ATAN2 = ARCTAN.2
ATANH = ARCTANH
BASE = BASE
CEILING.MATH = ARROTONDA.ECCESSO.MAT
CEILING.PRECISE = ARROTONDA.ECCESSO.PRECISA
COMBIN = COMBINAZIONE
COMBINA = COMBINAZIONE.VALORI
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMALE
DEGREES = GRADI
ECMA.CEILING = ECMA.ARROTONDA.ECCESSO
EVEN = PARI
EXP = EXP
FACT = FATTORIALE
FACTDOUBLE = FATT.DOPPIO
FLOOR.MATH = ARROTONDA.DIFETTO.MAT
FLOOR.PRECISE = ARROTONDA.DIFETTO.PRECISA
GCD = MCD
INT = INT
ISO.CEILING = ISO.ARROTONDA.ECCESSO
LCM = MCM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MATR.DETERM
MINVERSE = MATR.INVERSA
MMULT = MATR.PRODOTTO
MOD = RESTO
MROUND = ARROTONDA.MULTIPLO
MULTINOMIAL = MULTINOMIALE
MUNIT = MATR.UNIT
ODD = DISPARI
PI = PI.GRECO
POWER = POTENZA
PRODUCT = PRODOTTO
QUOTIENT = QUOZIENTE
RADIANS = RADIANTI
RAND = CASUALE
RANDBETWEEN = CASUALE.TRA
ROMAN = ROMANO
ROUND = ARROTONDA
ROUNDBAHTDOWN = ARROTBAHTGIU
ROUNDBAHTUP = ARROTBAHTSU
ROUNDDOWN = ARROTONDA.PER.DIF
ROUNDUP = ARROTONDA.PER.ECC
SEC = SEC
SECH = SECH
SERIESSUM = SOMMA.SERIE
SIGN = SEGNO
SIN = SEN
SINH = SENH
SQRT = RADQ
SQRTPI = RADQ.PI.GRECO
SUBTOTAL = SUBTOTALE
SUM = SOMMA
SUMIF = SOMMA.SE
SUMIFS = SOMMA.PIÙ.SE
SUMPRODUCT = MATR.SOMMA.PRODOTTO
SUMSQ = SOMMA.Q
SUMX2MY2 = SOMMA.DIFF.Q
SUMX2PY2 = SOMMA.SOMMA.Q
SUMXMY2 = SOMMA.Q.DIFF
TAN = TAN
TANH = TANH
TRUNC = TRONCA
##
## Funzioni statistiche (Statistical Functions)
##
AVEDEV = MEDIA.DEV
AVERAGE = MEDIA
AVERAGEA = MEDIA.VALORI
AVERAGEIF = MEDIA.SE
AVERAGEIFS = MEDIA.PIÙ.SE
BETA.DIST = DISTRIB.BETA.N
BETA.INV = INV.BETA.N
BINOM.DIST = DISTRIB.BINOM.N
BINOM.DIST.RANGE = INTERVALLO.DISTRIB.BINOM.N.
BINOM.INV = INV.BINOM
CHISQ.DIST = DISTRIB.CHI.QUAD
CHISQ.DIST.RT = DISTRIB.CHI.QUAD.DS
CHISQ.INV = INV.CHI.QUAD
CHISQ.INV.RT = INV.CHI.QUAD.DS
CHISQ.TEST = TEST.CHI.QUAD
CONFIDENCE.NORM = CONFIDENZA.NORM
CONFIDENCE.T = CONFIDENZA.T
CORREL = CORRELAZIONE
COUNT = CONTA.NUMERI
COUNTA = CONTA.VALORI
COUNTBLANK = CONTA.VUOTE
COUNTIF = CONTA.SE
COUNTIFS = CONTA.PIÙ.SE
COVARIANCE.P = COVARIANZA.P
COVARIANCE.S = COVARIANZA.C
DEVSQ = DEV.Q
EXPON.DIST = DISTRIB.EXP.N
F.DIST = DISTRIBF
F.DIST.RT = DISTRIB.F.DS
F.INV = INVF
F.INV.RT = INV.F.DS
F.TEST = TESTF
FISHER = FISHER
FISHERINV = INV.FISHER
FORECAST.ETS = PREVISIONE.ETS
FORECAST.ETS.CONFINT = PREVISIONE.ETS.INTCONF
FORECAST.ETS.SEASONALITY = PREVISIONE.ETS.STAGIONALITÀ
FORECAST.ETS.STAT = PREVISIONE.ETS.STAT
FORECAST.LINEAR = PREVISIONE.LINEARE
FREQUENCY = FREQUENZA
GAMMA = GAMMA
GAMMA.DIST = DISTRIB.GAMMA.N
GAMMA.INV = INV.GAMMA.N
GAMMALN = LN.GAMMA
GAMMALN.PRECISE = LN.GAMMA.PRECISA
GAUSS = GAUSS
GEOMEAN = MEDIA.GEOMETRICA
GROWTH = CRESCITA
HARMEAN = MEDIA.ARMONICA
HYPGEOM.DIST = DISTRIB.IPERGEOM.N
INTERCEPT = INTERCETTA
KURT = CURTOSI
LARGE = GRANDE
LINEST = REGR.LIN
LOGEST = REGR.LOG
LOGNORM.DIST = DISTRIB.LOGNORM.N
LOGNORM.INV = INV.LOGNORM.N
MAX = MAX
MAXA = MAX.VALORI
MAXIFS = MAX.PIÙ.SE
MEDIAN = MEDIANA
MIN = MIN
MINA = MIN.VALORI
MINIFS = MIN.PIÙ.SE
MODE.MULT = MODA.MULT
MODE.SNGL = MODA.SNGL
NEGBINOM.DIST = DISTRIB.BINOM.NEG.N
NORM.DIST = DISTRIB.NORM.N
NORM.INV = INV.NORM.N
NORM.S.DIST = DISTRIB.NORM.ST.N
NORM.S.INV = INV.NORM.S
PEARSON = PEARSON
PERCENTILE.EXC = ESC.PERCENTILE
PERCENTILE.INC = INC.PERCENTILE
PERCENTRANK.EXC = ESC.PERCENT.RANGO
PERCENTRANK.INC = INC.PERCENT.RANGO
PERMUT = PERMUTAZIONE
PERMUTATIONA = PERMUTAZIONE.VALORI
PHI = PHI
POISSON.DIST = DISTRIB.POISSON
PROB = PROBABILITÀ
QUARTILE.EXC = ESC.QUARTILE
QUARTILE.INC = INC.QUARTILE
RANK.AVG = RANGO.MEDIA
RANK.EQ = RANGO.UG
RSQ = RQ
SKEW = ASIMMETRIA
SKEW.P = ASIMMETRIA.P
SLOPE = PENDENZA
SMALL = PICCOLO
STANDARDIZE = NORMALIZZA
STDEV.P = DEV.ST.P
STDEV.S = DEV.ST.C
STDEVA = DEV.ST.VALORI
STDEVPA = DEV.ST.POP.VALORI
STEYX = ERR.STD.YX
T.DIST = DISTRIB.T.N
T.DIST.2T = DISTRIB.T.2T
T.DIST.RT = DISTRIB.T.DS
T.INV = INVT
T.INV.2T = INV.T.2T
T.TEST = TESTT
TREND = TENDENZA
TRIMMEAN = MEDIA.TRONCATA
VAR.P = VAR.P
VAR.S = VAR.C
VARA = VAR.VALORI
VARPA = VAR.POP.VALORI
WEIBULL.DIST = DISTRIB.WEIBULL
Z.TEST = TESTZ
##
## Funzioni di testo (Text Functions)
##
BAHTTEXT = BAHTTESTO
CHAR = CODICE.CARATT
CLEAN = LIBERA
CODE = CODICE
CONCAT = CONCAT
DOLLAR = VALUTA
EXACT = IDENTICO
FIND = TROVA
FIXED = FISSO
ISTHAIDIGIT = ÈTHAICIFRA
LEFT = SINISTRA
LEN = LUNGHEZZA
LOWER = MINUSC
MID = STRINGA.ESTRAI
NUMBERSTRING = NUMERO.STRINGA
NUMBERVALUE = NUMERO.VALORE
PHONETIC = FURIGANA
PROPER = MAIUSC.INIZ
REPLACE = RIMPIAZZA
REPT = RIPETI
RIGHT = DESTRA
SEARCH = RICERCA
SUBSTITUTE = SOSTITUISCI
T = T
TEXT = TESTO
TEXTJOIN = TESTO.UNISCI
THAIDIGIT = THAICIFRA
THAINUMSOUND = THAINUMSUONO
THAINUMSTRING = THAISZÁMKAR
THAISTRINGLENGTH = THAILUNGSTRINGA
TRIM = ANNULLA.SPAZI
UNICHAR = CARATT.UNI
UNICODE = UNICODE
UPPER = MAIUSC
VALUE = VALORE
##
## Funzioni Web (Web Functions)
##
ENCODEURL = CODIFICA.URL
FILTERXML = FILTRO.XML
WEBSERVICE = SERVIZIO.WEB
##
## Funzioni di compatibilità (Compatibility Functions)
##
BETADIST = DISTRIB.BETA
BETAINV = INV.BETA
BINOMDIST = DISTRIB.BINOM
CEILING = ARROTONDA.ECCESSO
CHIDIST = DISTRIB.CHI
CHIINV = INV.CHI
CHITEST = TEST.CHI
CONCATENATE = CONCATENA
CONFIDENCE = CONFIDENZA
COVAR = COVARIANZA
CRITBINOM = CRIT.BINOM
EXPONDIST = DISTRIB.EXP
FDIST = DISTRIB.F
FINV = INV.F
FLOOR = ARROTONDA.DIFETTO
FORECAST = PREVISIONE
FTEST = TEST.F
GAMMADIST = DISTRIB.GAMMA
GAMMAINV = INV.GAMMA
HYPGEOMDIST = DISTRIB.IPERGEOM
LOGINV = INV.LOGNORM
LOGNORMDIST = DISTRIB.LOGNORM
MODE = MODA
NEGBINOMDIST = DISTRIB.BINOM.NEG
NORMDIST = DISTRIB.NORM
NORMINV = INV.NORM
NORMSDIST = DISTRIB.NORM.ST
NORMSINV = INV.NORM.ST
PERCENTILE = PERCENTILE
PERCENTRANK = PERCENT.RANGO
POISSON = POISSON
QUARTILE = QUARTILE
RANK = RANGO
STDEV = DEV.ST
STDEVP = DEV.ST.POP
TDIST = DISTRIB.T
TINV = INV.T
TTEST = TEST.T
VAR = VAR
VARP = VAR.POP
WEIBULL = WEIBULL
ZTEST = TEST.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config 0000644 00000000517 15243417764 0020611 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Jezyk polski (Polish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #ZERO!
DIV0 = #DZIEL/0!
VALUE = #ARG!
REF = #ADR!
NAME = #NAZWA?
NUM = #LICZBA!
NA = #N/D!
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions 0000644 00000026520 15243417764 0021356 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Jezyk polski (Polish)
##
############################################################
##
## Funkcje baz danych (Cube Functions)
##
CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU
CUBEMEMBER = ELEMENT.MODUŁU
CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU
CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU
CUBESET = ZESTAW.MODUŁÓW
CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU
CUBEVALUE = WARTOŚĆ.MODUŁU
##
## Funkcje baz danych (Database Functions)
##
DAVERAGE = BD.ŚREDNIA
DCOUNT = BD.ILE.REKORDÓW
DCOUNTA = BD.ILE.REKORDÓW.A
DGET = BD.POLE
DMAX = BD.MAX
DMIN = BD.MIN
DPRODUCT = BD.ILOCZYN
DSTDEV = BD.ODCH.STANDARD
DSTDEVP = BD.ODCH.STANDARD.POPUL
DSUM = BD.SUMA
DVAR = BD.WARIANCJA
DVARP = BD.WARIANCJA.POPUL
##
## Funkcje daty i godziny (Date & Time Functions)
##
DATE = DATA
DATEDIF = DATA.RÓŻNICA
DATESTRING = DATA.CIĄG.ZNAK
DATEVALUE = DATA.WARTOŚĆ
DAY = DZIEŃ
DAYS = DNI
DAYS360 = DNI.360
EDATE = NR.SER.DATY
EOMONTH = NR.SER.OST.DN.MIES
HOUR = GODZINA
ISOWEEKNUM = ISO.NUM.TYG
MINUTE = MINUTA
MONTH = MIESIĄC
NETWORKDAYS = DNI.ROBOCZE
NETWORKDAYS.INTL = DNI.ROBOCZE.NIESTAND
NOW = TERAZ
SECOND = SEKUNDA
THAIDAYOFWEEK = TAJ.DZIEŃ.TYGODNIA
THAIMONTHOFYEAR = TAJ.MIESIĄC.ROKU
THAIYEAR = TAJ.ROK
TIME = CZAS
TIMEVALUE = CZAS.WARTOŚĆ
TODAY = DZIŚ
WEEKDAY = DZIEŃ.TYG
WEEKNUM = NUM.TYG
WORKDAY = DZIEŃ.ROBOCZY
WORKDAY.INTL = DZIEŃ.ROBOCZY.NIESTAND
YEAR = ROK
YEARFRAC = CZĘŚĆ.ROKU
##
## Funkcje inżynierskie (Engineering Functions)
##
BESSELI = BESSEL.I
BESSELJ = BESSEL.J
BESSELK = BESSEL.K
BESSELY = BESSEL.Y
BIN2DEC = DWÓJK.NA.DZIES
BIN2HEX = DWÓJK.NA.SZESN
BIN2OCT = DWÓJK.NA.ÓSM
BITAND = BITAND
BITLSHIFT = BIT.PRZESUNIĘCIE.W.LEWO
BITOR = BITOR
BITRSHIFT = BIT.PRZESUNIĘCIE.W.PRAWO
BITXOR = BITXOR
COMPLEX = LICZBA.ZESP
CONVERT = KONWERTUJ
DEC2BIN = DZIES.NA.DWÓJK
DEC2HEX = DZIES.NA.SZESN
DEC2OCT = DZIES.NA.ÓSM
DELTA = CZY.RÓWNE
ERF = FUNKCJA.BŁ
ERF.PRECISE = FUNKCJA.BŁ.DOKŁ
ERFC = KOMP.FUNKCJA.BŁ
ERFC.PRECISE = KOMP.FUNKCJA.BŁ.DOKŁ
GESTEP = SPRAWDŹ.PRÓG
HEX2BIN = SZESN.NA.DWÓJK
HEX2DEC = SZESN.NA.DZIES
HEX2OCT = SZESN.NA.ÓSM
IMABS = MODUŁ.LICZBY.ZESP
IMAGINARY = CZ.UROJ.LICZBY.ZESP
IMARGUMENT = ARG.LICZBY.ZESP
IMCONJUGATE = SPRZĘŻ.LICZBY.ZESP
IMCOS = COS.LICZBY.ZESP
IMCOSH = COSH.LICZBY.ZESP
IMCOT = COT.LICZBY.ZESP
IMCSC = CSC.LICZBY.ZESP
IMCSCH = CSCH.LICZBY.ZESP
IMDIV = ILORAZ.LICZB.ZESP
IMEXP = EXP.LICZBY.ZESP
IMLN = LN.LICZBY.ZESP
IMLOG10 = LOG10.LICZBY.ZESP
IMLOG2 = LOG2.LICZBY.ZESP
IMPOWER = POTĘGA.LICZBY.ZESP
IMPRODUCT = ILOCZYN.LICZB.ZESP
IMREAL = CZ.RZECZ.LICZBY.ZESP
IMSEC = SEC.LICZBY.ZESP
IMSECH = SECH.LICZBY.ZESP
IMSIN = SIN.LICZBY.ZESP
IMSINH = SINH.LICZBY.ZESP
IMSQRT = PIERWIASTEK.LICZBY.ZESP
IMSUB = RÓŻN.LICZB.ZESP
IMSUM = SUMA.LICZB.ZESP
IMTAN = TAN.LICZBY.ZESP
OCT2BIN = ÓSM.NA.DWÓJK
OCT2DEC = ÓSM.NA.DZIES
OCT2HEX = ÓSM.NA.SZESN
##
## Funkcje finansowe (Financial Functions)
##
ACCRINT = NAL.ODS
ACCRINTM = NAL.ODS.WYKUP
AMORDEGRC = AMORT.NIELIN
AMORLINC = AMORT.LIN
COUPDAYBS = WYPŁ.DNI.OD.POCZ
COUPDAYS = WYPŁ.DNI
COUPDAYSNC = WYPŁ.DNI.NAST
COUPNCD = WYPŁ.DATA.NAST
COUPNUM = WYPŁ.LICZBA
COUPPCD = WYPŁ.DATA.POPRZ
CUMIPMT = SPŁAC.ODS
CUMPRINC = SPŁAC.KAPIT
DB = DB
DDB = DDB
DISC = STOPA.DYSK
DOLLARDE = CENA.DZIES
DOLLARFR = CENA.UŁAM
DURATION = ROCZ.PRZYCH
EFFECT = EFEKTYWNA
FV = FV
FVSCHEDULE = WART.PRZYSZŁ.KAP
INTRATE = STOPA.PROC
IPMT = IPMT
IRR = IRR
ISPMT = ISPMT
MDURATION = ROCZ.PRZYCH.M
MIRR = MIRR
NOMINAL = NOMINALNA
NPER = NPER
NPV = NPV
ODDFPRICE = CENA.PIERW.OKR
ODDFYIELD = RENT.PIERW.OKR
ODDLPRICE = CENA.OST.OKR
ODDLYIELD = RENT.OST.OKR
PDURATION = O.CZAS.TRWANIA
PMT = PMT
PPMT = PPMT
PRICE = CENA
PRICEDISC = CENA.DYSK
PRICEMAT = CENA.WYKUP
PV = PV
RATE = RATE
RECEIVED = KWOTA.WYKUP
RRI = RÓWNOW.STOPA.PROC
SLN = SLN
SYD = SYD
TBILLEQ = RENT.EKW.BS
TBILLPRICE = CENA.BS
TBILLYIELD = RENT.BS
VDB = VDB
XIRR = XIRR
XNPV = XNPV
YIELD = RENTOWNOŚĆ
YIELDDISC = RENT.DYSK
YIELDMAT = RENT.WYKUP
##
## Funkcje informacyjne (Information Functions)
##
CELL = KOMÓRKA
ERROR.TYPE = NR.BŁĘDU
INFO = INFO
ISBLANK = CZY.PUSTA
ISERR = CZY.BŁ
ISERROR = CZY.BŁĄD
ISEVEN = CZY.PARZYSTE
ISFORMULA = CZY.FORMUŁA
ISLOGICAL = CZY.LOGICZNA
ISNA = CZY.BRAK
ISNONTEXT = CZY.NIE.TEKST
ISNUMBER = CZY.LICZBA
ISODD = CZY.NIEPARZYSTE
ISREF = CZY.ADR
ISTEXT = CZY.TEKST
N = N
NA = BRAK
SHEET = ARKUSZ
SHEETS = ARKUSZE
TYPE = TYP
##
## Funkcje logiczne (Logical Functions)
##
AND = ORAZ
FALSE = FAŁSZ
IF = JEŻELI
IFERROR = JEŻELI.BŁĄD
IFNA = JEŻELI.ND
IFS = WARUNKI
NOT = NIE
OR = LUB
SWITCH = PRZEŁĄCZ
TRUE = PRAWDA
XOR = XOR
##
## Funkcje wyszukiwania i odwołań (Lookup & Reference Functions)
##
ADDRESS = ADRES
AREAS = OBSZARY
CHOOSE = WYBIERZ
COLUMN = NR.KOLUMNY
COLUMNS = LICZBA.KOLUMN
FORMULATEXT = FORMUŁA.TEKST
GETPIVOTDATA = WEŹDANETABELI
HLOOKUP = WYSZUKAJ.POZIOMO
HYPERLINK = HIPERŁĄCZE
INDEX = INDEKS
INDIRECT = ADR.POŚR
LOOKUP = WYSZUKAJ
MATCH = PODAJ.POZYCJĘ
OFFSET = PRZESUNIĘCIE
ROW = WIERSZ
ROWS = ILE.WIERSZY
RTD = DANE.CZASU.RZECZ
TRANSPOSE = TRANSPONUJ
VLOOKUP = WYSZUKAJ.PIONOWO
##
## Funkcje matematyczne i trygonometryczne (Math & Trig Functions)
##
ABS = MODUŁ.LICZBY
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGUJ
ARABIC = ARABSKIE
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = PODSTAWA
CEILING.MATH = ZAOKR.W.GÓRĘ.MATEMATYCZNE
CEILING.PRECISE = ZAOKR.W.GÓRĘ.DOKŁ
COMBIN = KOMBINACJE
COMBINA = KOMBINACJE.A
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DZIESIĘTNA
DEGREES = STOPNIE
ECMA.CEILING = ECMA.ZAOKR.W.GÓRĘ
EVEN = ZAOKR.DO.PARZ
EXP = EXP
FACT = SILNIA
FACTDOUBLE = SILNIA.DWUKR
FLOOR.MATH = ZAOKR.W.DÓŁ.MATEMATYCZNE
FLOOR.PRECISE = ZAOKR.W.DÓŁ.DOKŁ
GCD = NAJW.WSP.DZIEL
INT = ZAOKR.DO.CAŁK
ISO.CEILING = ISO.ZAOKR.W.GÓRĘ
LCM = NAJMN.WSP.WIEL
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = WYZNACZNIK.MACIERZY
MINVERSE = MACIERZ.ODW
MMULT = MACIERZ.ILOCZYN
MOD = MOD
MROUND = ZAOKR.DO.WIELOKR
MULTINOMIAL = WIELOMIAN
MUNIT = MACIERZ.JEDNOSTKOWA
ODD = ZAOKR.DO.NPARZ
PI = PI
POWER = POTĘGA
PRODUCT = ILOCZYN
QUOTIENT = CZ.CAŁK.DZIELENIA
RADIANS = RADIANY
RAND = LOS
RANDBETWEEN = LOS.ZAKR
ROMAN = RZYMSKIE
ROUND = ZAOKR
ROUNDBAHTDOWN = ZAOKR.DÓŁ.BAT
ROUNDBAHTUP = ZAOKR.GÓRA.BAT
ROUNDDOWN = ZAOKR.DÓŁ
ROUNDUP = ZAOKR.GÓRA
SEC = SEC
SECH = SECH
SERIESSUM = SUMA.SZER.POT
SIGN = ZNAK.LICZBY
SIN = SIN
SINH = SINH
SQRT = PIERWIASTEK
SQRTPI = PIERW.PI
SUBTOTAL = SUMY.CZĘŚCIOWE
SUM = SUMA
SUMIF = SUMA.JEŻELI
SUMIFS = SUMA.WARUNKÓW
SUMPRODUCT = SUMA.ILOCZYNÓW
SUMSQ = SUMA.KWADRATÓW
SUMX2MY2 = SUMA.X2.M.Y2
SUMX2PY2 = SUMA.X2.P.Y2
SUMXMY2 = SUMA.XMY.2
TAN = TAN
TANH = TANH
TRUNC = LICZBA.CAŁK
##
## Funkcje statystyczne (Statistical Functions)
##
AVEDEV = ODCH.ŚREDNIE
AVERAGE = ŚREDNIA
AVERAGEA = ŚREDNIA.A
AVERAGEIF = ŚREDNIA.JEŻELI
AVERAGEIFS = ŚREDNIA.WARUNKÓW
BETA.DIST = ROZKŁ.BETA
BETA.INV = ROZKŁ.BETA.ODWR
BINOM.DIST = ROZKŁ.DWUM
BINOM.DIST.RANGE = ROZKŁ.DWUM.ZAKRES
BINOM.INV = ROZKŁ.DWUM.ODWR
CHISQ.DIST = ROZKŁ.CHI
CHISQ.DIST.RT = ROZKŁ.CHI.PS
CHISQ.INV = ROZKŁ.CHI.ODWR
CHISQ.INV.RT = ROZKŁ.CHI.ODWR.PS
CHISQ.TEST = CHI.TEST
CONFIDENCE.NORM = UFNOŚĆ.NORM
CONFIDENCE.T = UFNOŚĆ.T
CORREL = WSP.KORELACJI
COUNT = ILE.LICZB
COUNTA = ILE.NIEPUSTYCH
COUNTBLANK = LICZ.PUSTE
COUNTIF = LICZ.JEŻELI
COUNTIFS = LICZ.WARUNKI
COVARIANCE.P = KOWARIANCJA.POPUL
COVARIANCE.S = KOWARIANCJA.PRÓBKI
DEVSQ = ODCH.KWADRATOWE
EXPON.DIST = ROZKŁ.EXP
F.DIST = ROZKŁ.F
F.DIST.RT = ROZKŁ.F.PS
F.INV = ROZKŁ.F.ODWR
F.INV.RT = ROZKŁ.F.ODWR.PS
F.TEST = F.TEST
FISHER = ROZKŁAD.FISHER
FISHERINV = ROZKŁAD.FISHER.ODW
FORECAST.ETS = REGLINX.ETS
FORECAST.ETS.CONFINT = REGLINX.ETS.CONFINT
FORECAST.ETS.SEASONALITY = REGLINX.ETS.SEZONOWOŚĆ
FORECAST.ETS.STAT = REGLINX.ETS.STATYSTYKA
FORECAST.LINEAR = REGLINX.LINIOWA
FREQUENCY = CZĘSTOŚĆ
GAMMA = GAMMA
GAMMA.DIST = ROZKŁ.GAMMA
GAMMA.INV = ROZKŁ.GAMMA.ODWR
GAMMALN = ROZKŁAD.LIN.GAMMA
GAMMALN.PRECISE = ROZKŁAD.LIN.GAMMA.DOKŁ
GAUSS = GAUSS
GEOMEAN = ŚREDNIA.GEOMETRYCZNA
GROWTH = REGEXPW
HARMEAN = ŚREDNIA.HARMONICZNA
HYPGEOM.DIST = ROZKŁ.HIPERGEOM
INTERCEPT = ODCIĘTA
KURT = KURTOZA
LARGE = MAX.K
LINEST = REGLINP
LOGEST = REGEXPP
LOGNORM.DIST = ROZKŁ.LOG
LOGNORM.INV = ROZKŁ.LOG.ODWR
MAX = MAX
MAXA = MAX.A
MAXIFS = MAKS.WARUNKÓW
MEDIAN = MEDIANA
MIN = MIN
MINA = MIN.A
MINIFS = MIN.WARUNKÓW
MODE.MULT = WYST.NAJCZĘŚCIEJ.TABL
MODE.SNGL = WYST.NAJCZĘŚCIEJ.WART
NEGBINOM.DIST = ROZKŁ.DWUM.PRZEC
NORM.DIST = ROZKŁ.NORMALNY
NORM.INV = ROZKŁ.NORMALNY.ODWR
NORM.S.DIST = ROZKŁ.NORMALNY.S
NORM.S.INV = ROZKŁ.NORMALNY.S.ODWR
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTYL.PRZEDZ.OTW
PERCENTILE.INC = PERCENTYL.PRZEDZ.ZAMK
PERCENTRANK.EXC = PROC.POZ.PRZEDZ.OTW
PERCENTRANK.INC = PROC.POZ.PRZEDZ.ZAMK
PERMUT = PERMUTACJE
PERMUTATIONA = PERMUTACJE.A
PHI = PHI
POISSON.DIST = ROZKŁ.POISSON
PROB = PRAWDPD
QUARTILE.EXC = KWARTYL.PRZEDZ.OTW
QUARTILE.INC = KWARTYL.PRZEDZ.ZAMK
RANK.AVG = POZYCJA.ŚR
RANK.EQ = POZYCJA.NAJW
RSQ = R.KWADRAT
SKEW = SKOŚNOŚĆ
SKEW.P = SKOŚNOŚĆ.P
SLOPE = NACHYLENIE
SMALL = MIN.K
STANDARDIZE = NORMALIZUJ
STDEV.P = ODCH.STAND.POPUL
STDEV.S = ODCH.STANDARD.PRÓBKI
STDEVA = ODCH.STANDARDOWE.A
STDEVPA = ODCH.STANDARD.POPUL.A
STEYX = REGBŁSTD
T.DIST = ROZKŁ.T
T.DIST.2T = ROZKŁ.T.DS
T.DIST.RT = ROZKŁ.T.PS
T.INV = ROZKŁ.T.ODWR
T.INV.2T = ROZKŁ.T.ODWR.DS
T.TEST = T.TEST
TREND = REGLINW
TRIMMEAN = ŚREDNIA.WEWN
VAR.P = WARIANCJA.POP
VAR.S = WARIANCJA.PRÓBKI
VARA = WARIANCJA.A
VARPA = WARIANCJA.POPUL.A
WEIBULL.DIST = ROZKŁ.WEIBULL
Z.TEST = Z.TEST
##
## Funkcje tekstowe (Text Functions)
##
BAHTTEXT = BAT.TEKST
CHAR = ZNAK
CLEAN = OCZYŚĆ
CODE = KOD
CONCAT = ZŁĄCZ.TEKST
DOLLAR = KWOTA
EXACT = PORÓWNAJ
FIND = ZNAJDŹ
FIXED = ZAOKR.DO.TEKST
ISTHAIDIGIT = CZY.CYFRA.TAJ
LEFT = LEWY
LEN = DŁ
LOWER = LITERY.MAŁE
MID = FRAGMENT.TEKSTU
NUMBERSTRING = LICZBA.CIĄG.ZNAK
NUMBERVALUE = WARTOŚĆ.LICZBOWA
PROPER = Z.WIELKIEJ.LITERY
REPLACE = ZASTĄP
REPT = POWT
RIGHT = PRAWY
SEARCH = SZUKAJ.TEKST
SUBSTITUTE = PODSTAW
T = T
TEXT = TEKST
TEXTJOIN = POŁĄCZ.TEKSTY
THAIDIGIT = TAJ.CYFRA
THAINUMSOUND = TAJ.DŹWIĘK.NUM
THAINUMSTRING = TAJ.CIĄG.NUM
THAISTRINGLENGTH = TAJ.DŁUGOŚĆ.CIĄGU
TRIM = USUŃ.ZBĘDNE.ODSTĘPY
UNICHAR = ZNAK.UNICODE
UNICODE = UNICODE
UPPER = LITERY.WIELKIE
VALUE = WARTOŚĆ
##
## Funkcje sieci Web (Web Functions)
##
ENCODEURL = ENCODEURL
FILTERXML = FILTERXML
WEBSERVICE = WEBSERVICE
##
## Funkcje zgodności (Compatibility Functions)
##
BETADIST = ROZKŁAD.BETA
BETAINV = ROZKŁAD.BETA.ODW
BINOMDIST = ROZKŁAD.DWUM
CEILING = ZAOKR.W.GÓRĘ
CHIDIST = ROZKŁAD.CHI
CHIINV = ROZKŁAD.CHI.ODW
CHITEST = TEST.CHI
CONCATENATE = ZŁĄCZ.TEKSTY
CONFIDENCE = UFNOŚĆ
COVAR = KOWARIANCJA
CRITBINOM = PRÓG.ROZKŁAD.DWUM
EXPONDIST = ROZKŁAD.EXP
FDIST = ROZKŁAD.F
FINV = ROZKŁAD.F.ODW
FLOOR = ZAOKR.W.DÓŁ
FORECAST = REGLINX
FTEST = TEST.F
GAMMADIST = ROZKŁAD.GAMMA
GAMMAINV = ROZKŁAD.GAMMA.ODW
HYPGEOMDIST = ROZKŁAD.HIPERGEOM
LOGINV = ROZKŁAD.LOG.ODW
LOGNORMDIST = ROZKŁAD.LOG
MODE = WYST.NAJCZĘŚCIEJ
NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC
NORMDIST = ROZKŁAD.NORMALNY
NORMINV = ROZKŁAD.NORMALNY.ODW
NORMSDIST = ROZKŁAD.NORMALNY.S
NORMSINV = ROZKŁAD.NORMALNY.S.ODW
PERCENTILE = PERCENTYL
PERCENTRANK = PROCENT.POZYCJA
POISSON = ROZKŁAD.POISSON
QUARTILE = KWARTYL
RANK = POZYCJA
STDEV = ODCH.STANDARDOWE
STDEVP = ODCH.STANDARD.POPUL
TDIST = ROZKŁAD.T
TINV = ROZKŁAD.T.ODW
TTEST = TEST.T
VAR = WARIANCJA
VARP = WARIANCJA.POPUL
WEIBULL = ROZKŁAD.WEIBULL
ZTEST = TEST.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config 0000644 00000000463 15243417764 0020575 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Norsk Bokmål (Norwegian Bokmål)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL
DIV0
VALUE = #VERDI!
REF
NAME = #NAVN?
NUM
NA = #N/D
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions 0000644 00000024751 15243417764 0021346 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Norsk Bokmål (Norwegian Bokmål)
##
############################################################
##
## Kubefunksjoner (Cube Functions)
##
CUBEKPIMEMBER = KUBEKPIMEDLEM
CUBEMEMBER = KUBEMEDLEM
CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP
CUBERANKEDMEMBER = KUBERANGERTMEDLEM
CUBESET = KUBESETT
CUBESETCOUNT = KUBESETTANTALL
CUBEVALUE = KUBEVERDI
##
## Databasefunksjoner (Database Functions)
##
DAVERAGE = DGJENNOMSNITT
DCOUNT = DANTALL
DCOUNTA = DANTALLA
DGET = DHENT
DMAX = DMAKS
DMIN = DMIN
DPRODUCT = DPRODUKT
DSTDEV = DSTDAV
DSTDEVP = DSTDAVP
DSUM = DSUMMER
DVAR = DVARIANS
DVARP = DVARIANSP
##
## Dato- og tidsfunksjoner (Date & Time Functions)
##
DATE = DATO
DATEDIF = DATODIFF
DATESTRING = DATOSTRENG
DATEVALUE = DATOVERDI
DAY = DAG
DAYS = DAGER
DAYS360 = DAGER360
EDATE = DAG.ETTER
EOMONTH = MÅNEDSSLUTT
HOUR = TIME
ISOWEEKNUM = ISOUKENR
MINUTE = MINUTT
MONTH = MÅNED
NETWORKDAYS = NETT.ARBEIDSDAGER
NETWORKDAYS.INTL = NETT.ARBEIDSDAGER.INTL
NOW = NÅ
SECOND = SEKUND
THAIDAYOFWEEK = THAIUKEDAG
THAIMONTHOFYEAR = THAIMÅNED
THAIYEAR = THAIÅR
TIME = TID
TIMEVALUE = TIDSVERDI
TODAY = IDAG
WEEKDAY = UKEDAG
WEEKNUM = UKENR
WORKDAY = ARBEIDSDAG
WORKDAY.INTL = ARBEIDSDAG.INTL
YEAR = ÅR
YEARFRAC = ÅRDEL
##
## Tekniske funksjoner (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINTILDES
BIN2HEX = BINTILHEKS
BIN2OCT = BINTILOKT
BITAND = BITOG
BITLSHIFT = BITVFORSKYV
BITOR = BITELLER
BITRSHIFT = BITHFORSKYV
BITXOR = BITEKSKLUSIVELLER
COMPLEX = KOMPLEKS
CONVERT = KONVERTER
DEC2BIN = DESTILBIN
DEC2HEX = DESTILHEKS
DEC2OCT = DESTILOKT
DELTA = DELTA
ERF = FEILF
ERF.PRECISE = FEILF.PRESIS
ERFC = FEILFK
ERFC.PRECISE = FEILFK.PRESIS
GESTEP = GRENSEVERDI
HEX2BIN = HEKSTILBIN
HEX2DEC = HEKSTILDES
HEX2OCT = HEKSTILOKT
IMABS = IMABS
IMAGINARY = IMAGINÆR
IMARGUMENT = IMARGUMENT
IMCONJUGATE = IMKONJUGERT
IMCOS = IMCOS
IMCOSH = IMCOSH
IMCOT = IMCOT
IMCSC = IMCSC
IMCSCH = IMCSCH
IMDIV = IMDIV
IMEXP = IMEKSP
IMLN = IMLN
IMLOG10 = IMLOG10
IMLOG2 = IMLOG2
IMPOWER = IMOPPHØY
IMPRODUCT = IMPRODUKT
IMREAL = IMREELL
IMSEC = IMSEC
IMSECH = IMSECH
IMSIN = IMSIN
IMSINH = IMSINH
IMSQRT = IMROT
IMSUB = IMSUB
IMSUM = IMSUMMER
IMTAN = IMTAN
OCT2BIN = OKTTILBIN
OCT2DEC = OKTTILDES
OCT2HEX = OKTTILHEKS
##
## Økonomiske funksjoner (Financial Functions)
##
ACCRINT = PÅLØPT.PERIODISK.RENTE
ACCRINTM = PÅLØPT.FORFALLSRENTE
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = OBLIG.DAGER.FF
COUPDAYS = OBLIG.DAGER
COUPDAYSNC = OBLIG.DAGER.NF
COUPNCD = OBLIG.DAGER.EF
COUPNUM = OBLIG.ANTALL
COUPPCD = OBLIG.DAG.FORRIGE
CUMIPMT = SAMLET.RENTE
CUMPRINC = SAMLET.HOVEDSTOL
DB = DAVSKR
DDB = DEGRAVS
DISC = DISKONTERT
DOLLARDE = DOLLARDE
DOLLARFR = DOLLARBR
DURATION = VARIGHET
EFFECT = EFFEKTIV.RENTE
FV = SLUTTVERDI
FVSCHEDULE = SVPLAN
INTRATE = RENTESATS
IPMT = RAVDRAG
IRR = IR
ISPMT = ER.AVDRAG
MDURATION = MVARIGHET
MIRR = MODIR
NOMINAL = NOMINELL
NPER = PERIODER
NPV = NNV
ODDFPRICE = AVVIKFP.PRIS
ODDFYIELD = AVVIKFP.AVKASTNING
ODDLPRICE = AVVIKSP.PRIS
ODDLYIELD = AVVIKSP.AVKASTNING
PDURATION = PVARIGHET
PMT = AVDRAG
PPMT = AMORT
PRICE = PRIS
PRICEDISC = PRIS.DISKONTERT
PRICEMAT = PRIS.FORFALL
PV = NÅVERDI
RATE = RENTE
RECEIVED = MOTTATT.AVKAST
RRI = REALISERT.AVKASTNING
SLN = LINAVS
SYD = ÅRSAVS
TBILLEQ = TBILLEKV
TBILLPRICE = TBILLPRIS
TBILLYIELD = TBILLAVKASTNING
VDB = VERDIAVS
XIRR = XIR
XNPV = XNNV
YIELD = AVKAST
YIELDDISC = AVKAST.DISKONTERT
YIELDMAT = AVKAST.FORFALL
##
## Informasjonsfunksjoner (Information Functions)
##
CELL = CELLE
ERROR.TYPE = FEIL.TYPE
INFO = INFO
ISBLANK = ERTOM
ISERR = ERF
ISERROR = ERFEIL
ISEVEN = ERPARTALL
ISFORMULA = ERFORMEL
ISLOGICAL = ERLOGISK
ISNA = ERIT
ISNONTEXT = ERIKKETEKST
ISNUMBER = ERTALL
ISODD = ERODDE
ISREF = ERREF
ISTEXT = ERTEKST
N = N
NA = IT
SHEET = ARK
SHEETS = ANTALL.ARK
TYPE = VERDITYPE
##
## Logiske funksjoner (Logical Functions)
##
AND = OG
FALSE = USANN
IF = HVIS
IFERROR = HVISFEIL
IFNA = HVIS.IT
IFS = HVIS.SETT
NOT = IKKE
OR = ELLER
SWITCH = BRYTER
TRUE = SANN
XOR = EKSKLUSIVELLER
##
## Oppslag- og referansefunksjoner (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = OMRÅDER
CHOOSE = VELG
COLUMN = KOLONNE
COLUMNS = KOLONNER
FORMULATEXT = FORMELTEKST
GETPIVOTDATA = HENTPIVOTDATA
HLOOKUP = FINN.KOLONNE
HYPERLINK = HYPERKOBLING
INDEX = INDEKS
INDIRECT = INDIREKTE
LOOKUP = SLÅ.OPP
MATCH = SAMMENLIGNE
OFFSET = FORSKYVNING
ROW = RAD
ROWS = RADER
RTD = RTD
TRANSPOSE = TRANSPONER
VLOOKUP = FINN.RAD
*RC = RK
##
## Matematikk- og trigonometrifunksjoner (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ARCCOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = MENGDE
ARABIC = ARABISK
ASIN = ARCSIN
ASINH = ARCSINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ARCTANH
BASE = GRUNNTALL
CEILING.MATH = AVRUND.GJELDENDE.MULTIPLUM.OPP.MATEMATISK
CEILING.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.PRESIS
COMBIN = KOMBINASJON
COMBINA = KOMBINASJONA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DESIMAL
DEGREES = GRADER
ECMA.CEILING = ECMA.AVRUND.GJELDENDE.MULTIPLUM
EVEN = AVRUND.TIL.PARTALL
EXP = EKSP
FACT = FAKULTET
FACTDOUBLE = DOBBELFAKT
FLOOR.MATH = AVRUND.GJELDENDE.MULTIPLUM.NED.MATEMATISK
FLOOR.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.NED.PRESIS
GCD = SFF
INT = HELTALL
ISO.CEILING = ISO.AVRUND.GJELDENDE.MULTIPLUM
LCM = MFM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERS
MMULT = MMULT
MOD = REST
MROUND = MRUND
MULTINOMIAL = MULTINOMINELL
MUNIT = MENHET
ODD = AVRUND.TIL.ODDETALL
PI = PI
POWER = OPPHØYD.I
PRODUCT = PRODUKT
QUOTIENT = KVOTIENT
RADIANS = RADIANER
RAND = TILFELDIG
RANDBETWEEN = TILFELDIGMELLOM
ROMAN = ROMERTALL
ROUND = AVRUND
ROUNDBAHTDOWN = RUNDAVBAHTNEDOVER
ROUNDBAHTUP = RUNDAVBAHTOPPOVER
ROUNDDOWN = AVRUND.NED
ROUNDUP = AVRUND.OPP
SEC = SEC
SECH = SECH
SERIESSUM = SUMMER.REKKE
SIGN = FORTEGN
SIN = SIN
SINH = SINH
SQRT = ROT
SQRTPI = ROTPI
SUBTOTAL = DELSUM
SUM = SUMMER
SUMIF = SUMMERHVIS
SUMIFS = SUMMER.HVIS.SETT
SUMPRODUCT = SUMMERPRODUKT
SUMSQ = SUMMERKVADRAT
SUMX2MY2 = SUMMERX2MY2
SUMX2PY2 = SUMMERX2PY2
SUMXMY2 = SUMMERXMY2
TAN = TAN
TANH = TANH
TRUNC = AVKORT
##
## Statistiske funksjoner (Statistical Functions)
##
AVEDEV = GJENNOMSNITTSAVVIK
AVERAGE = GJENNOMSNITT
AVERAGEA = GJENNOMSNITTA
AVERAGEIF = GJENNOMSNITTHVIS
AVERAGEIFS = GJENNOMSNITT.HVIS.SETT
BETA.DIST = BETA.FORDELING.N
BETA.INV = BETA.INV
BINOM.DIST = BINOM.FORDELING.N
BINOM.DIST.RANGE = BINOM.FORDELING.OMRÅDE
BINOM.INV = BINOM.INV
CHISQ.DIST = KJIKVADRAT.FORDELING
CHISQ.DIST.RT = KJIKVADRAT.FORDELING.H
CHISQ.INV = KJIKVADRAT.INV
CHISQ.INV.RT = KJIKVADRAT.INV.H
CHISQ.TEST = KJIKVADRAT.TEST
CONFIDENCE.NORM = KONFIDENS.NORM
CONFIDENCE.T = KONFIDENS.T
CORREL = KORRELASJON
COUNT = ANTALL
COUNTA = ANTALLA
COUNTBLANK = TELLBLANKE
COUNTIF = ANTALL.HVIS
COUNTIFS = ANTALL.HVIS.SETT
COVARIANCE.P = KOVARIANS.P
COVARIANCE.S = KOVARIANS.S
DEVSQ = AVVIK.KVADRERT
EXPON.DIST = EKSP.FORDELING.N
F.DIST = F.FORDELING
F.DIST.RT = F.FORDELING.H
F.INV = F.INV
F.INV.RT = F.INV.H
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERINV
FORECAST.ETS = PROGNOSE.ETS
FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SESONGAVHENGIGHET
FORECAST.ETS.STAT = PROGNOSE.ETS.STAT
FORECAST.LINEAR = PROGNOSE.LINEÆR
FREQUENCY = FREKVENS
GAMMA = GAMMA
GAMMA.DIST = GAMMA.FORDELING
GAMMA.INV = GAMMA.INV
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PRESIS
GAUSS = GAUSS
GEOMEAN = GJENNOMSNITT.GEOMETRISK
GROWTH = VEKST
HARMEAN = GJENNOMSNITT.HARMONISK
HYPGEOM.DIST = HYPGEOM.FORDELING.N
INTERCEPT = SKJÆRINGSPUNKT
KURT = KURT
LARGE = N.STØRST
LINEST = RETTLINJE
LOGEST = KURVE
LOGNORM.DIST = LOGNORM.FORDELING
LOGNORM.INV = LOGNORM.INV
MAX = STØRST
MAXA = MAKSA
MAXIFS = MAKS.HVIS.SETT
MEDIAN = MEDIAN
MIN = MIN
MINA = MINA
MINIFS = MIN.HVIS.SETT
MODE.MULT = MODUS.MULT
MODE.SNGL = MODUS.SNGL
NEGBINOM.DIST = NEGBINOM.FORDELING.N
NORM.DIST = NORM.FORDELING
NORM.INV = NORM.INV
NORM.S.DIST = NORM.S.FORDELING
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERSENTIL.EKS
PERCENTILE.INC = PERSENTIL.INK
PERCENTRANK.EXC = PROSENTDEL.EKS
PERCENTRANK.INC = PROSENTDEL.INK
PERMUT = PERMUTER
PERMUTATIONA = PERMUTASJONA
PHI = PHI
POISSON.DIST = POISSON.FORDELING
PROB = SANNSYNLIG
QUARTILE.EXC = KVARTIL.EKS
QUARTILE.INC = KVARTIL.INK
RANK.AVG = RANG.GJSN
RANK.EQ = RANG.EKV
RSQ = RKVADRAT
SKEW = SKJEVFORDELING
SKEW.P = SKJEVFORDELING.P
SLOPE = STIGNINGSTALL
SMALL = N.MINST
STANDARDIZE = NORMALISER
STDEV.P = STDAV.P
STDEV.S = STDAV.S
STDEVA = STDAVVIKA
STDEVPA = STDAVVIKPA
STEYX = STANDARDFEIL
T.DIST = T.FORDELING
T.DIST.2T = T.FORDELING.2T
T.DIST.RT = T.FORDELING.H
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = TRIMMET.GJENNOMSNITT
VAR.P = VARIANS.P
VAR.S = VARIANS.S
VARA = VARIANSA
VARPA = VARIANSPA
WEIBULL.DIST = WEIBULL.DIST.N
Z.TEST = Z.TEST
##
## Tekstfunksjoner (Text Functions)
##
ASC = STIGENDE
BAHTTEXT = BAHTTEKST
CHAR = TEGNKODE
CLEAN = RENSK
CODE = KODE
CONCAT = KJED.SAMMEN
DOLLAR = VALUTA
EXACT = EKSAKT
FIND = FINN
FIXED = FASTSATT
ISTHAIDIGIT = ERTHAISIFFER
LEFT = VENSTRE
LEN = LENGDE
LOWER = SMÅ
MID = DELTEKST
NUMBERSTRING = TALLSTRENG
NUMBERVALUE = TALLVERDI
PHONETIC = FURIGANA
PROPER = STOR.FORBOKSTAV
REPLACE = ERSTATT
REPT = GJENTA
RIGHT = HØYRE
SEARCH = SØK
SUBSTITUTE = BYTT.UT
T = T
TEXT = TEKST
TEXTJOIN = TEKST.KOMBINER
THAIDIGIT = THAISIFFER
THAINUMSOUND = THAINUMLYD
THAINUMSTRING = THAINUMSTRENG
THAISTRINGLENGTH = THAISTRENGLENGDE
TRIM = TRIMME
UNICHAR = UNICODETEGN
UNICODE = UNICODE
UPPER = STORE
VALUE = VERDI
##
## Nettfunksjoner (Web Functions)
##
ENCODEURL = URL.KODE
FILTERXML = FILTRERXML
WEBSERVICE = NETTJENESTE
##
## Kompatibilitetsfunksjoner (Compatibility Functions)
##
BETADIST = BETA.FORDELING
BETAINV = INVERS.BETA.FORDELING
BINOMDIST = BINOM.FORDELING
CEILING = AVRUND.GJELDENDE.MULTIPLUM
CHIDIST = KJI.FORDELING
CHIINV = INVERS.KJI.FORDELING
CHITEST = KJI.TEST
CONCATENATE = KJEDE.SAMMEN
CONFIDENCE = KONFIDENS
COVAR = KOVARIANS
CRITBINOM = GRENSE.BINOM
EXPONDIST = EKSP.FORDELING
FDIST = FFORDELING
FINV = FFORDELING.INVERS
FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED
FORECAST = PROGNOSE
FTEST = FTEST
GAMMADIST = GAMMAFORDELING
GAMMAINV = GAMMAINV
HYPGEOMDIST = HYPGEOM.FORDELING
LOGINV = LOGINV
LOGNORMDIST = LOGNORMFORD
MODE = MODUS
NEGBINOMDIST = NEGBINOM.FORDELING
NORMDIST = NORMALFORDELING
NORMINV = NORMINV
NORMSDIST = NORMSFORDELING
NORMSINV = NORMSINV
PERCENTILE = PERSENTIL
PERCENTRANK = PROSENTDEL
POISSON = POISSON
QUARTILE = KVARTIL
RANK = RANG
STDEV = STDAV
STDEVP = STDAVP
TDIST = TFORDELING
TINV = TINV
TTEST = TTEST
VAR = VARIANS
VARP = VARIANSP
WEIBULL = WEIBULL.FORDELING
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config 0000644 00000000531 15243417764 0020606 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Magyar (Hungarian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NULLA!
DIV0 = #ZÉRÓOSZTÓ!
VALUE = #ÉRTÉK!
REF = #HIV!
NAME = #NÉV?
NUM = #SZÁM!
NA = #HIÁNYZIK
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions 0000644 00000025073 15243417764 0021361 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Magyar (Hungarian)
##
############################################################
##
## Kockafüggvények (Cube Functions)
##
CUBEKPIMEMBER = KOCKA.FŐTELJMUT
CUBEMEMBER = KOCKA.TAG
CUBEMEMBERPROPERTY = KOCKA.TAG.TUL
CUBERANKEDMEMBER = KOCKA.HALM.ELEM
CUBESET = KOCKA.HALM
CUBESETCOUNT = KOCKA.HALM.DB
CUBEVALUE = KOCKA.ÉRTÉK
##
## Adatbázis-kezelő függvények (Database Functions)
##
DAVERAGE = AB.ÁTLAG
DCOUNT = AB.DARAB
DCOUNTA = AB.DARAB2
DGET = AB.MEZŐ
DMAX = AB.MAX
DMIN = AB.MIN
DPRODUCT = AB.SZORZAT
DSTDEV = AB.SZÓRÁS
DSTDEVP = AB.SZÓRÁS2
DSUM = AB.SZUM
DVAR = AB.VAR
DVARP = AB.VAR2
##
## Dátumfüggvények (Date & Time Functions)
##
DATE = DÁTUM
DATEDIF = DÁTUMTÓLIG
DATESTRING = DÁTUMSZÖVEG
DATEVALUE = DÁTUMÉRTÉK
DAY = NAP
DAYS = NAPOK
DAYS360 = NAP360
EDATE = KALK.DÁTUM
EOMONTH = HÓNAP.UTOLSÓ.NAP
HOUR = ÓRA
ISOWEEKNUM = ISO.HÉT.SZÁMA
MINUTE = PERCEK
MONTH = HÓNAP
NETWORKDAYS = ÖSSZ.MUNKANAP
NETWORKDAYS.INTL = ÖSSZ.MUNKANAP.INTL
NOW = MOST
SECOND = MPERC
THAIDAYOFWEEK = THAIHÉTNAPJA
THAIMONTHOFYEAR = THAIHÓNAP
THAIYEAR = THAIÉV
TIME = IDŐ
TIMEVALUE = IDŐÉRTÉK
TODAY = MA
WEEKDAY = HÉT.NAPJA
WEEKNUM = HÉT.SZÁMA
WORKDAY = KALK.MUNKANAP
WORKDAY.INTL = KALK.MUNKANAP.INTL
YEAR = ÉV
YEARFRAC = TÖRTÉV
##
## Mérnöki függvények (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.DEC
BIN2HEX = BIN.HEX
BIN2OCT = BIN.OKT
BITAND = BIT.ÉS
BITLSHIFT = BIT.BAL.ELTOL
BITOR = BIT.VAGY
BITRSHIFT = BIT.JOBB.ELTOL
BITXOR = BIT.XVAGY
COMPLEX = KOMPLEX
CONVERT = KONVERTÁLÁS
DEC2BIN = DEC.BIN
DEC2HEX = DEC.HEX
DEC2OCT = DEC.OKT
DELTA = DELTA
ERF = HIBAF
ERF.PRECISE = HIBAF.PONTOS
ERFC = HIBAF.KOMPLEMENTER
ERFC.PRECISE = HIBAFKOMPLEMENTER.PONTOS
GESTEP = KÜSZÖBNÉL.NAGYOBB
HEX2BIN = HEX.BIN
HEX2DEC = HEX.DEC
HEX2OCT = HEX.OKT
IMABS = KÉPZ.ABSZ
IMAGINARY = KÉPZETES
IMARGUMENT = KÉPZ.ARGUMENT
IMCONJUGATE = KÉPZ.KONJUGÁLT
IMCOS = KÉPZ.COS
IMCOSH = KÉPZ.COSH
IMCOT = KÉPZ.COT
IMCSC = KÉPZ.CSC
IMCSCH = KÉPZ.CSCH
IMDIV = KÉPZ.HÁNYAD
IMEXP = KÉPZ.EXP
IMLN = KÉPZ.LN
IMLOG10 = KÉPZ.LOG10
IMLOG2 = KÉPZ.LOG2
IMPOWER = KÉPZ.HATV
IMPRODUCT = KÉPZ.SZORZAT
IMREAL = KÉPZ.VALÓS
IMSEC = KÉPZ.SEC
IMSECH = KÉPZ.SECH
IMSIN = KÉPZ.SIN
IMSINH = KÉPZ.SINH
IMSQRT = KÉPZ.GYÖK
IMSUB = KÉPZ.KÜL
IMSUM = KÉPZ.ÖSSZEG
IMTAN = KÉPZ.TAN
OCT2BIN = OKT.BIN
OCT2DEC = OKT.DEC
OCT2HEX = OKT.HEX
##
## Pénzügyi függvények (Financial Functions)
##
ACCRINT = IDŐSZAKI.KAMAT
ACCRINTM = LEJÁRATI.KAMAT
AMORDEGRC = ÉRTÉKCSÖKK.TÉNYEZŐVEL
AMORLINC = ÉRTÉKCSÖKK
COUPDAYBS = SZELVÉNYIDŐ.KEZDETTŐL
COUPDAYS = SZELVÉNYIDŐ
COUPDAYSNC = SZELVÉNYIDŐ.KIFIZETÉSTŐL
COUPNCD = ELSŐ.SZELVÉNYDÁTUM
COUPNUM = SZELVÉNYSZÁM
COUPPCD = UTOLSÓ.SZELVÉNYDÁTUM
CUMIPMT = ÖSSZES.KAMAT
CUMPRINC = ÖSSZES.TŐKERÉSZ
DB = KCS2
DDB = KCSA
DISC = LESZÁM
DOLLARDE = FORINT.DEC
DOLLARFR = FORINT.TÖRT
DURATION = KAMATÉRZ
EFFECT = TÉNYLEGES
FV = JBÉ
FVSCHEDULE = KJÉ
INTRATE = KAMATRÁTA
IPMT = RRÉSZLET
IRR = BMR
ISPMT = LRÉSZLETKAMAT
MDURATION = MKAMATÉRZ
MIRR = MEGTÉRÜLÉS
NOMINAL = NÉVLEGES
NPER = PER.SZÁM
NPV = NMÉ
ODDFPRICE = ELTÉRŐ.EÁR
ODDFYIELD = ELTÉRŐ.EHOZAM
ODDLPRICE = ELTÉRŐ.UÁR
ODDLYIELD = ELTÉRŐ.UHOZAM
PDURATION = KAMATÉRZ.PER
PMT = RÉSZLET
PPMT = PRÉSZLET
PRICE = ÁR
PRICEDISC = ÁR.LESZÁM
PRICEMAT = ÁR.LEJÁRAT
PV = MÉ
RATE = RÁTA
RECEIVED = KAPOTT
RRI = MR
SLN = LCSA
SYD = ÉSZÖ
TBILLEQ = KJEGY.EGYENÉRT
TBILLPRICE = KJEGY.ÁR
TBILLYIELD = KJEGY.HOZAM
VDB = ÉCSRI
XIRR = XBMR
XNPV = XNJÉ
YIELD = HOZAM
YIELDDISC = HOZAM.LESZÁM
YIELDMAT = HOZAM.LEJÁRAT
##
## Információs függvények (Information Functions)
##
CELL = CELLA
ERROR.TYPE = HIBA.TÍPUS
INFO = INFÓ
ISBLANK = ÜRES
ISERR = HIBA.E
ISERROR = HIBÁS
ISEVEN = PÁROSE
ISFORMULA = KÉPLET
ISLOGICAL = LOGIKAI
ISNA = NINCS
ISNONTEXT = NEM.SZÖVEG
ISNUMBER = SZÁM
ISODD = PÁRATLANE
ISREF = HIVATKOZÁS
ISTEXT = SZÖVEG.E
N = S
NA = HIÁNYZIK
SHEET = LAP
SHEETS = LAPOK
TYPE = TÍPUS
##
## Logikai függvények (Logical Functions)
##
AND = ÉS
FALSE = HAMIS
IF = HA
IFERROR = HAHIBA
IFNA = HAHIÁNYZIK
IFS = HAELSŐIGAZ
NOT = NEM
OR = VAGY
SWITCH = ÁTVÁLT
TRUE = IGAZ
XOR = XVAGY
##
## Keresési és hivatkozási függvények (Lookup & Reference Functions)
##
ADDRESS = CÍM
AREAS = TERÜLET
CHOOSE = VÁLASZT
COLUMN = OSZLOP
COLUMNS = OSZLOPOK
FORMULATEXT = KÉPLETSZÖVEG
GETPIVOTDATA = KIMUTATÁSADATOT.VESZ
HLOOKUP = VKERES
HYPERLINK = HIPERHIVATKOZÁS
INDEX = INDEX
INDIRECT = INDIREKT
LOOKUP = KERES
MATCH = HOL.VAN
OFFSET = ELTOLÁS
ROW = SOR
ROWS = SOROK
RTD = VIA
TRANSPOSE = TRANSZPONÁLÁS
VLOOKUP = FKERES
*RC = SO
##
## Matematikai és trigonometrikus függvények (Math & Trig Functions)
##
ABS = ABS
ACOS = ARCCOS
ACOSH = ACOSH
ACOT = ARCCOT
ACOTH = ARCCOTH
AGGREGATE = ÖSSZESÍT
ARABIC = ARAB
ASIN = ARCSIN
ASINH = ASINH
ATAN = ARCTAN
ATAN2 = ARCTAN2
ATANH = ATANH
BASE = ALAP
CEILING.MATH = PLAFON.MAT
CEILING.PRECISE = PLAFON.PONTOS
COMBIN = KOMBINÁCIÓK
COMBINA = KOMBINÁCIÓK.ISM
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = TIZEDES
DEGREES = FOK
ECMA.CEILING = ECMA.PLAFON
EVEN = PÁROS
EXP = KITEVŐ
FACT = FAKT
FACTDOUBLE = FAKTDUPLA
FLOOR.MATH = PADLÓ.MAT
FLOOR.PRECISE = PADLÓ.PONTOS
GCD = LKO
INT = INT
ISO.CEILING = ISO.PLAFON
LCM = LKT
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = INVERZ.MÁTRIX
MMULT = MSZORZAT
MOD = MARADÉK
MROUND = TÖBBSZ.KEREKÍT
MULTINOMIAL = SZORHÁNYFAKT
MUNIT = MMÁTRIX
ODD = PÁRATLAN
PI = PI
POWER = HATVÁNY
PRODUCT = SZORZAT
QUOTIENT = KVÓCIENS
RADIANS = RADIÁN
RAND = VÉL
RANDBETWEEN = VÉLETLEN.KÖZÖTT
ROMAN = RÓMAI
ROUND = KEREKÍTÉS
ROUNDBAHTDOWN = BAHTKEREK.LE
ROUNDBAHTUP = BAHTKEREK.FEL
ROUNDDOWN = KEREK.LE
ROUNDUP = KEREK.FEL
SEC = SEC
SECH = SECH
SERIESSUM = SORÖSSZEG
SIGN = ELŐJEL
SIN = SIN
SINH = SINH
SQRT = GYÖK
SQRTPI = GYÖKPI
SUBTOTAL = RÉSZÖSSZEG
SUM = SZUM
SUMIF = SZUMHA
SUMIFS = SZUMHATÖBB
SUMPRODUCT = SZORZATÖSSZEG
SUMSQ = NÉGYZETÖSSZEG
SUMX2MY2 = SZUMX2BŐLY2
SUMX2PY2 = SZUMX2MEGY2
SUMXMY2 = SZUMXBŐLY2
TAN = TAN
TANH = TANH
TRUNC = CSONK
##
## Statisztikai függvények (Statistical Functions)
##
AVEDEV = ÁTL.ELTÉRÉS
AVERAGE = ÁTLAG
AVERAGEA = ÁTLAGA
AVERAGEIF = ÁTLAGHA
AVERAGEIFS = ÁTLAGHATÖBB
BETA.DIST = BÉTA.ELOSZL
BETA.INV = BÉTA.INVERZ
BINOM.DIST = BINOM.ELOSZL
BINOM.DIST.RANGE = BINOM.ELOSZL.TART
BINOM.INV = BINOM.INVERZ
CHISQ.DIST = KHINÉGYZET.ELOSZLÁS
CHISQ.DIST.RT = KHINÉGYZET.ELOSZLÁS.JOBB
CHISQ.INV = KHINÉGYZET.INVERZ
CHISQ.INV.RT = KHINÉGYZET.INVERZ.JOBB
CHISQ.TEST = KHINÉGYZET.PRÓBA
CONFIDENCE.NORM = MEGBÍZHATÓSÁG.NORM
CONFIDENCE.T = MEGBÍZHATÓSÁG.T
CORREL = KORREL
COUNT = DARAB
COUNTA = DARAB2
COUNTBLANK = DARABÜRES
COUNTIF = DARABTELI
COUNTIFS = DARABHATÖBB
COVARIANCE.P = KOVARIANCIA.S
COVARIANCE.S = KOVARIANCIA.M
DEVSQ = SQ
EXPON.DIST = EXP.ELOSZL
F.DIST = F.ELOSZL
F.DIST.RT = F.ELOSZLÁS.JOBB
F.INV = F.INVERZ
F.INV.RT = F.INVERZ.JOBB
F.TEST = F.PRÓB
FISHER = FISHER
FISHERINV = INVERZ.FISHER
FORECAST.ETS = ELŐREJELZÉS.ESIM
FORECAST.ETS.CONFINT = ELŐREJELZÉS.ESIM.KONFINT
FORECAST.ETS.SEASONALITY = ELŐREJELZÉS.ESIM.SZEZONALITÁS
FORECAST.ETS.STAT = ELŐREJELZÉS.ESIM.STAT
FORECAST.LINEAR = ELŐREJELZÉS.LINEÁRIS
FREQUENCY = GYAKORISÁG
GAMMA = GAMMA
GAMMA.DIST = GAMMA.ELOSZL
GAMMA.INV = GAMMA.INVERZ
GAMMALN = GAMMALN
GAMMALN.PRECISE = GAMMALN.PONTOS
GAUSS = GAUSS
GEOMEAN = MÉRTANI.KÖZÉP
GROWTH = NÖV
HARMEAN = HARM.KÖZÉP
HYPGEOM.DIST = HIPGEOM.ELOSZLÁS
INTERCEPT = METSZ
KURT = CSÚCSOSSÁG
LARGE = NAGY
LINEST = LIN.ILL
LOGEST = LOG.ILL
LOGNORM.DIST = LOGNORM.ELOSZLÁS
LOGNORM.INV = LOGNORM.INVERZ
MAX = MAX
MAXA = MAXA
MAXIFS = MAXHA
MEDIAN = MEDIÁN
MIN = MIN
MINA = MIN2
MINIFS = MINHA
MODE.MULT = MÓDUSZ.TÖBB
MODE.SNGL = MÓDUSZ.EGY
NEGBINOM.DIST = NEGBINOM.ELOSZLÁS
NORM.DIST = NORM.ELOSZLÁS
NORM.INV = NORM.INVERZ
NORM.S.DIST = NORM.S.ELOSZLÁS
NORM.S.INV = NORM.S.INVERZ
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTILIS.KIZÁR
PERCENTILE.INC = PERCENTILIS.TARTALMAZ
PERCENTRANK.EXC = SZÁZALÉKRANG.KIZÁR
PERCENTRANK.INC = SZÁZALÉKRANG.TARTALMAZ
PERMUT = VARIÁCIÓK
PERMUTATIONA = VARIÁCIÓK.ISM
PHI = FI
POISSON.DIST = POISSON.ELOSZLÁS
PROB = VALÓSZÍNŰSÉG
QUARTILE.EXC = KVARTILIS.KIZÁR
QUARTILE.INC = KVARTILIS.TARTALMAZ
RANK.AVG = RANG.ÁTL
RANK.EQ = RANG.EGY
RSQ = RNÉGYZET
SKEW = FERDESÉG
SKEW.P = FERDESÉG.P
SLOPE = MEREDEKSÉG
SMALL = KICSI
STANDARDIZE = NORMALIZÁLÁS
STDEV.P = SZÓR.S
STDEV.S = SZÓR.M
STDEVA = SZÓRÁSA
STDEVPA = SZÓRÁSPA
STEYX = STHIBAYX
T.DIST = T.ELOSZL
T.DIST.2T = T.ELOSZLÁS.2SZ
T.DIST.RT = T.ELOSZLÁS.JOBB
T.INV = T.INVERZ
T.INV.2T = T.INVERZ.2SZ
T.TEST = T.PRÓB
TREND = TREND
TRIMMEAN = RÉSZÁTLAG
VAR.P = VAR.S
VAR.S = VAR.M
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.ELOSZLÁS
Z.TEST = Z.PRÓB
##
## Szövegműveletekhez használható függvények (Text Functions)
##
BAHTTEXT = BAHTSZÖVEG
CHAR = KARAKTER
CLEAN = TISZTÍT
CODE = KÓD
CONCAT = FŰZ
DOLLAR = FORINT
EXACT = AZONOS
FIND = SZÖVEG.TALÁL
FIXED = FIX
ISTHAIDIGIT = ON.THAI.NUMERO
LEFT = BAL
LEN = HOSSZ
LOWER = KISBETŰ
MID = KÖZÉP
NUMBERSTRING = SZÁM.BETŰVEL
NUMBERVALUE = SZÁMÉRTÉK
PHONETIC = FONETIKUS
PROPER = TNÉV
REPLACE = CSERE
REPT = SOKSZOR
RIGHT = JOBB
SEARCH = SZÖVEG.KERES
SUBSTITUTE = HELYETTE
T = T
TEXT = SZÖVEG
TEXTJOIN = SZÖVEGÖSSZEFŰZÉS
THAIDIGIT = THAISZÁM
THAINUMSOUND = THAISZÁMHANG
THAINUMSTRING = THAISZÁMKAR
THAISTRINGLENGTH = THAIKARHOSSZ
TRIM = KIMETSZ
UNICHAR = UNIKARAKTER
UNICODE = UNICODE
UPPER = NAGYBETŰS
VALUE = ÉRTÉK
##
## Webes függvények (Web Functions)
##
ENCODEURL = URL.KÓDOL
FILTERXML = XMLSZŰRÉS
WEBSERVICE = WEBSZOLGÁLTATÁS
##
## Kompatibilitási függvények (Compatibility Functions)
##
BETADIST = BÉTA.ELOSZLÁS
BETAINV = INVERZ.BÉTA
BINOMDIST = BINOM.ELOSZLÁS
CEILING = PLAFON
CHIDIST = KHI.ELOSZLÁS
CHIINV = INVERZ.KHI
CHITEST = KHI.PRÓBA
CONCATENATE = ÖSSZEFŰZ
CONFIDENCE = MEGBÍZHATÓSÁG
COVAR = KOVAR
CRITBINOM = KRITBINOM
EXPONDIST = EXP.ELOSZLÁS
FDIST = F.ELOSZLÁS
FINV = INVERZ.F
FLOOR = PADLÓ
FORECAST = ELŐREJELZÉS
FTEST = F.PRÓBA
GAMMADIST = GAMMA.ELOSZLÁS
GAMMAINV = INVERZ.GAMMA
HYPGEOMDIST = HIPERGEOM.ELOSZLÁS
LOGINV = INVERZ.LOG.ELOSZLÁS
LOGNORMDIST = LOG.ELOSZLÁS
MODE = MÓDUSZ
NEGBINOMDIST = NEGBINOM.ELOSZL
NORMDIST = NORM.ELOSZL
NORMINV = INVERZ.NORM
NORMSDIST = STNORMELOSZL
NORMSINV = INVERZ.STNORM
PERCENTILE = PERCENTILIS
PERCENTRANK = SZÁZALÉKRANG
POISSON = POISSON
QUARTILE = KVARTILIS
RANK = SORSZÁM
STDEV = SZÓRÁS
STDEVP = SZÓRÁSP
TDIST = T.ELOSZLÁS
TINV = INVERZ.T
TTEST = T.PRÓBA
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = Z.PRÓBA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config 0000644 00000000516 15243417764 0020604 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Español (Spanish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #¡NULO!
DIV0 = #¡DIV/0!
VALUE = #¡VALOR!
REF = #¡REF!
NAME = #¿NOMBRE?
NUM = #¡NUM!
NA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions 0000644 00000024714 15243417764 0021355 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Español (Spanish)
##
############################################################
##
## Funciones de cubo (Cube Functions)
##
CUBEKPIMEMBER = MIEMBROKPICUBO
CUBEMEMBER = MIEMBROCUBO
CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO
CUBERANKEDMEMBER = MIEMBRORANGOCUBO
CUBESET = CONJUNTOCUBO
CUBESETCOUNT = RECUENTOCONJUNTOCUBO
CUBEVALUE = VALORCUBO
##
## Funciones de base de datos (Database Functions)
##
DAVERAGE = BDPROMEDIO
DCOUNT = BDCONTAR
DCOUNTA = BDCONTARA
DGET = BDEXTRAER
DMAX = BDMAX
DMIN = BDMIN
DPRODUCT = BDPRODUCTO
DSTDEV = BDDESVEST
DSTDEVP = BDDESVESTP
DSUM = BDSUMA
DVAR = BDVAR
DVARP = BDVARP
##
## Funciones de fecha y hora (Date & Time Functions)
##
DATE = FECHA
DATEDIF = SIFECHA
DATESTRING = CADENA.FECHA
DATEVALUE = FECHANUMERO
DAY = DIA
DAYS = DIAS
DAYS360 = DIAS360
EDATE = FECHA.MES
EOMONTH = FIN.MES
HOUR = HORA
ISOWEEKNUM = ISO.NUM.DE.SEMANA
MINUTE = MINUTO
MONTH = MES
NETWORKDAYS = DIAS.LAB
NETWORKDAYS.INTL = DIAS.LAB.INTL
NOW = AHORA
SECOND = SEGUNDO
THAIDAYOFWEEK = DIASEMTAI
THAIMONTHOFYEAR = MESAÑOTAI
THAIYEAR = AÑOTAI
TIME = NSHORA
TIMEVALUE = HORANUMERO
TODAY = HOY
WEEKDAY = DIASEM
WEEKNUM = NUM.DE.SEMANA
WORKDAY = DIA.LAB
WORKDAY.INTL = DIA.LAB.INTL
YEAR = AÑO
YEARFRAC = FRAC.AÑO
##
## Funciones de ingeniería (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN.A.DEC
BIN2HEX = BIN.A.HEX
BIN2OCT = BIN.A.OCT
BITAND = BIT.Y
BITLSHIFT = BIT.DESPLIZQDA
BITOR = BIT.O
BITRSHIFT = BIT.DESPLDCHA
BITXOR = BIT.XO
COMPLEX = COMPLEJO
CONVERT = CONVERTIR
DEC2BIN = DEC.A.BIN
DEC2HEX = DEC.A.HEX
DEC2OCT = DEC.A.OCT
DELTA = DELTA
ERF = FUN.ERROR
ERF.PRECISE = FUN.ERROR.EXACTO
ERFC = FUN.ERROR.COMPL
ERFC.PRECISE = FUN.ERROR.COMPL.EXACTO
GESTEP = MAYOR.O.IGUAL
HEX2BIN = HEX.A.BIN
HEX2DEC = HEX.A.DEC
HEX2OCT = HEX.A.OCT
IMABS = IM.ABS
IMAGINARY = IMAGINARIO
IMARGUMENT = IM.ANGULO
IMCONJUGATE = IM.CONJUGADA
IMCOS = IM.COS
IMCOSH = IM.COSH
IMCOT = IM.COT
IMCSC = IM.CSC
IMCSCH = IM.CSCH
IMDIV = IM.DIV
IMEXP = IM.EXP
IMLN = IM.LN
IMLOG10 = IM.LOG10
IMLOG2 = IM.LOG2
IMPOWER = IM.POT
IMPRODUCT = IM.PRODUCT
IMREAL = IM.REAL
IMSEC = IM.SEC
IMSECH = IM.SECH
IMSIN = IM.SENO
IMSINH = IM.SENOH
IMSQRT = IM.RAIZ2
IMSUB = IM.SUSTR
IMSUM = IM.SUM
IMTAN = IM.TAN
OCT2BIN = OCT.A.BIN
OCT2DEC = OCT.A.DEC
OCT2HEX = OCT.A.HEX
##
## Funciones financieras (Financial Functions)
##
ACCRINT = INT.ACUM
ACCRINTM = INT.ACUM.V
AMORDEGRC = AMORTIZ.PROGRE
AMORLINC = AMORTIZ.LIN
COUPDAYBS = CUPON.DIAS.L1
COUPDAYS = CUPON.DIAS
COUPDAYSNC = CUPON.DIAS.L2
COUPNCD = CUPON.FECHA.L2
COUPNUM = CUPON.NUM
COUPPCD = CUPON.FECHA.L1
CUMIPMT = PAGO.INT.ENTRE
CUMPRINC = PAGO.PRINC.ENTRE
DB = DB
DDB = DDB
DISC = TASA.DESC
DOLLARDE = MONEDA.DEC
DOLLARFR = MONEDA.FRAC
DURATION = DURACION
EFFECT = INT.EFECTIVO
FV = VF
FVSCHEDULE = VF.PLAN
INTRATE = TASA.INT
IPMT = PAGOINT
IRR = TIR
ISPMT = INT.PAGO.DIR
MDURATION = DURACION.MODIF
MIRR = TIRM
NOMINAL = TASA.NOMINAL
NPER = NPER
NPV = VNA
ODDFPRICE = PRECIO.PER.IRREGULAR.1
ODDFYIELD = RENDTO.PER.IRREGULAR.1
ODDLPRICE = PRECIO.PER.IRREGULAR.2
ODDLYIELD = RENDTO.PER.IRREGULAR.2
PDURATION = P.DURACION
PMT = PAGO
PPMT = PAGOPRIN
PRICE = PRECIO
PRICEDISC = PRECIO.DESCUENTO
PRICEMAT = PRECIO.VENCIMIENTO
PV = VA
RATE = TASA
RECEIVED = CANTIDAD.RECIBIDA
RRI = RRI
SLN = SLN
SYD = SYD
TBILLEQ = LETRA.DE.TEST.EQV.A.BONO
TBILLPRICE = LETRA.DE.TES.PRECIO
TBILLYIELD = LETRA.DE.TES.RENDTO
VDB = DVS
XIRR = TIR.NO.PER
XNPV = VNA.NO.PER
YIELD = RENDTO
YIELDDISC = RENDTO.DESC
YIELDMAT = RENDTO.VENCTO
##
## Funciones de información (Information Functions)
##
CELL = CELDA
ERROR.TYPE = TIPO.DE.ERROR
INFO = INFO
ISBLANK = ESBLANCO
ISERR = ESERR
ISERROR = ESERROR
ISEVEN = ES.PAR
ISFORMULA = ESFORMULA
ISLOGICAL = ESLOGICO
ISNA = ESNOD
ISNONTEXT = ESNOTEXTO
ISNUMBER = ESNUMERO
ISODD = ES.IMPAR
ISREF = ESREF
ISTEXT = ESTEXTO
N = N
NA = NOD
SHEET = HOJA
SHEETS = HOJAS
TYPE = TIPO
##
## Funciones lógicas (Logical Functions)
##
AND = Y
FALSE = FALSO
IF = SI
IFERROR = SI.ERROR
IFNA = SI.ND
IFS = SI.CONJUNTO
NOT = NO
OR = O
SWITCH = CAMBIAR
TRUE = VERDADERO
XOR = XO
##
## Funciones de búsqueda y referencia (Lookup & Reference Functions)
##
ADDRESS = DIRECCION
AREAS = AREAS
CHOOSE = ELEGIR
COLUMN = COLUMNA
COLUMNS = COLUMNAS
FORMULATEXT = FORMULATEXTO
GETPIVOTDATA = IMPORTARDATOSDINAMICOS
HLOOKUP = BUSCARH
HYPERLINK = HIPERVINCULO
INDEX = INDICE
INDIRECT = INDIRECTO
LOOKUP = BUSCAR
MATCH = COINCIDIR
OFFSET = DESREF
ROW = FILA
ROWS = FILAS
RTD = RDTR
TRANSPOSE = TRANSPONER
VLOOKUP = BUSCARV
*RC = FC
##
## Funciones matemáticas y trigonométricas (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAR
ARABIC = NUMERO.ARABE
ASIN = ASENO
ASINH = ASENOH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = MULTIPLO.SUPERIOR.MAT
CEILING.PRECISE = MULTIPLO.SUPERIOR.EXACTO
COMBIN = COMBINAT
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = CONV.DECIMAL
DEGREES = GRADOS
ECMA.CEILING = MULTIPLO.SUPERIOR.ECMA
EVEN = REDONDEA.PAR
EXP = EXP
FACT = FACT
FACTDOUBLE = FACT.DOBLE
FLOOR.MATH = MULTIPLO.INFERIOR.MAT
FLOOR.PRECISE = MULTIPLO.INFERIOR.EXACTO
GCD = M.C.D
INT = ENTERO
ISO.CEILING = MULTIPLO.SUPERIOR.ISO
LCM = M.C.M
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = MDETERM
MINVERSE = MINVERSA
MMULT = MMULT
MOD = RESIDUO
MROUND = REDOND.MULT
MULTINOMIAL = MULTINOMIAL
MUNIT = M.UNIDAD
ODD = REDONDEA.IMPAR
PI = PI
POWER = POTENCIA
PRODUCT = PRODUCTO
QUOTIENT = COCIENTE
RADIANS = RADIANES
RAND = ALEATORIO
RANDBETWEEN = ALEATORIO.ENTRE
ROMAN = NUMERO.ROMANO
ROUND = REDONDEAR
ROUNDBAHTDOWN = REDONDEAR.BAHT.MAS
ROUNDBAHTUP = REDONDEAR.BAHT.MENOS
ROUNDDOWN = REDONDEAR.MENOS
ROUNDUP = REDONDEAR.MAS
SEC = SEC
SECH = SECH
SERIESSUM = SUMA.SERIES
SIGN = SIGNO
SIN = SENO
SINH = SENOH
SQRT = RAIZ
SQRTPI = RAIZ2PI
SUBTOTAL = SUBTOTALES
SUM = SUMA
SUMIF = SUMAR.SI
SUMIFS = SUMAR.SI.CONJUNTO
SUMPRODUCT = SUMAPRODUCTO
SUMSQ = SUMA.CUADRADOS
SUMX2MY2 = SUMAX2MENOSY2
SUMX2PY2 = SUMAX2MASY2
SUMXMY2 = SUMAXMENOSY2
TAN = TAN
TANH = TANH
TRUNC = TRUNCAR
##
## Funciones estadísticas (Statistical Functions)
##
AVEDEV = DESVPROM
AVERAGE = PROMEDIO
AVERAGEA = PROMEDIOA
AVERAGEIF = PROMEDIO.SI
AVERAGEIFS = PROMEDIO.SI.CONJUNTO
BETA.DIST = DISTR.BETA.N
BETA.INV = INV.BETA.N
BINOM.DIST = DISTR.BINOM.N
BINOM.DIST.RANGE = DISTR.BINOM.SERIE
BINOM.INV = INV.BINOM
CHISQ.DIST = DISTR.CHICUAD
CHISQ.DIST.RT = DISTR.CHICUAD.CD
CHISQ.INV = INV.CHICUAD
CHISQ.INV.RT = INV.CHICUAD.CD
CHISQ.TEST = PRUEBA.CHICUAD
CONFIDENCE.NORM = INTERVALO.CONFIANZA.NORM
CONFIDENCE.T = INTERVALO.CONFIANZA.T
CORREL = COEF.DE.CORREL
COUNT = CONTAR
COUNTA = CONTARA
COUNTBLANK = CONTAR.BLANCO
COUNTIF = CONTAR.SI
COUNTIFS = CONTAR.SI.CONJUNTO
COVARIANCE.P = COVARIANCE.P
COVARIANCE.S = COVARIANZA.M
DEVSQ = DESVIA2
EXPON.DIST = DISTR.EXP.N
F.DIST = DISTR.F.N
F.DIST.RT = DISTR.F.CD
F.INV = INV.F
F.INV.RT = INV.F.CD
F.TEST = PRUEBA.F.N
FISHER = FISHER
FISHERINV = PRUEBA.FISHER.INV
FORECAST.ETS = PRONOSTICO.ETS
FORECAST.ETS.CONFINT = PRONOSTICO.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PRONOSTICO.ETS.ESTACIONALIDAD
FORECAST.ETS.STAT = PRONOSTICO.ETS.STAT
FORECAST.LINEAR = PRONOSTICO.LINEAL
FREQUENCY = FRECUENCIA
GAMMA = GAMMA
GAMMA.DIST = DISTR.GAMMA.N
GAMMA.INV = INV.GAMMA
GAMMALN = GAMMA.LN
GAMMALN.PRECISE = GAMMA.LN.EXACTO
GAUSS = GAUSS
GEOMEAN = MEDIA.GEOM
GROWTH = CRECIMIENTO
HARMEAN = MEDIA.ARMO
HYPGEOM.DIST = DISTR.HIPERGEOM.N
INTERCEPT = INTERSECCION.EJE
KURT = CURTOSIS
LARGE = K.ESIMO.MAYOR
LINEST = ESTIMACION.LINEAL
LOGEST = ESTIMACION.LOGARITMICA
LOGNORM.DIST = DISTR.LOGNORM
LOGNORM.INV = INV.LOGNORM
MAX = MAX
MAXA = MAXA
MAXIFS = MAX.SI.CONJUNTO
MEDIAN = MEDIANA
MIN = MIN
MINA = MINA
MINIFS = MIN.SI.CONJUNTO
MODE.MULT = MODA.VARIOS
MODE.SNGL = MODA.UNO
NEGBINOM.DIST = NEGBINOM.DIST
NORM.DIST = DISTR.NORM.N
NORM.INV = INV.NORM
NORM.S.DIST = DISTR.NORM.ESTAND.N
NORM.S.INV = INV.NORM.ESTAND
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIL.EXC
PERCENTILE.INC = PERCENTIL.INC
PERCENTRANK.EXC = RANGO.PERCENTIL.EXC
PERCENTRANK.INC = RANGO.PERCENTIL.INC
PERMUT = PERMUTACIONES
PERMUTATIONA = PERMUTACIONES.A
PHI = FI
POISSON.DIST = POISSON.DIST
PROB = PROBABILIDAD
QUARTILE.EXC = CUARTIL.EXC
QUARTILE.INC = CUARTIL.INC
RANK.AVG = JERARQUIA.MEDIA
RANK.EQ = JERARQUIA.EQV
RSQ = COEFICIENTE.R2
SKEW = COEFICIENTE.ASIMETRIA
SKEW.P = COEFICIENTE.ASIMETRIA.P
SLOPE = PENDIENTE
SMALL = K.ESIMO.MENOR
STANDARDIZE = NORMALIZACION
STDEV.P = DESVEST.P
STDEV.S = DESVEST.M
STDEVA = DESVESTA
STDEVPA = DESVESTPA
STEYX = ERROR.TIPICO.XY
T.DIST = DISTR.T.N
T.DIST.2T = DISTR.T.2C
T.DIST.RT = DISTR.T.CD
T.INV = INV.T
T.INV.2T = INV.T.2C
T.TEST = PRUEBA.T.N
TREND = TENDENCIA
TRIMMEAN = MEDIA.ACOTADA
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = DISTR.WEIBULL
Z.TEST = PRUEBA.Z.N
##
## Funciones de texto (Text Functions)
##
BAHTTEXT = TEXTOBAHT
CHAR = CARACTER
CLEAN = LIMPIAR
CODE = CODIGO
CONCAT = CONCAT
DOLLAR = MONEDA
EXACT = IGUAL
FIND = ENCONTRAR
FIXED = DECIMAL
ISTHAIDIGIT = ESDIGITOTAI
LEFT = IZQUIERDA
LEN = LARGO
LOWER = MINUSC
MID = EXTRAE
NUMBERSTRING = CADENA.NUMERO
NUMBERVALUE = VALOR.NUMERO
PHONETIC = FONETICO
PROPER = NOMPROPIO
REPLACE = REEMPLAZAR
REPT = REPETIR
RIGHT = DERECHA
SEARCH = HALLAR
SUBSTITUTE = SUSTITUIR
T = T
TEXT = TEXTO
TEXTJOIN = UNIRCADENAS
THAIDIGIT = DIGITOTAI
THAINUMSOUND = SONNUMTAI
THAINUMSTRING = CADENANUMTAI
THAISTRINGLENGTH = LONGCADENATAI
TRIM = ESPACIOS
UNICHAR = UNICAR
UNICODE = UNICODE
UPPER = MAYUSC
VALUE = VALOR
##
## Funciones web (Web Functions)
##
ENCODEURL = URLCODIF
FILTERXML = XMLFILTRO
WEBSERVICE = SERVICIOWEB
##
## Funciones de compatibilidad (Compatibility Functions)
##
BETADIST = DISTR.BETA
BETAINV = DISTR.BETA.INV
BINOMDIST = DISTR.BINOM
CEILING = MULTIPLO.SUPERIOR
CHIDIST = DISTR.CHI
CHIINV = PRUEBA.CHI.INV
CHITEST = PRUEBA.CHI
CONCATENATE = CONCATENAR
CONFIDENCE = INTERVALO.CONFIANZA
COVAR = COVAR
CRITBINOM = BINOM.CRIT
EXPONDIST = DISTR.EXP
FDIST = DISTR.F
FINV = DISTR.F.INV
FLOOR = MULTIPLO.INFERIOR
FORECAST = PRONOSTICO
FTEST = PRUEBA.F
GAMMADIST = DISTR.GAMMA
GAMMAINV = DISTR.GAMMA.INV
HYPGEOMDIST = DISTR.HIPERGEOM
LOGINV = DISTR.LOG.INV
LOGNORMDIST = DISTR.LOG.NORM
MODE = MODA
NEGBINOMDIST = NEGBINOMDIST
NORMDIST = DISTR.NORM
NORMINV = DISTR.NORM.INV
NORMSDIST = DISTR.NORM.ESTAND
NORMSINV = DISTR.NORM.ESTAND.INV
PERCENTILE = PERCENTIL
PERCENTRANK = RANGO.PERCENTIL
POISSON = POISSON
QUARTILE = CUARTIL
RANK = JERARQUIA
STDEV = DESVEST
STDEVP = DESVESTP
TDIST = DISTR.T
TINV = DISTR.T.INV
TTEST = PRUEBA.T
VAR = VAR
VARP = VARP
WEIBULL = DIST.WEIBULL
ZTEST = PRUEBA.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config 0000644 00000000512 15243417764 0020616 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Türkçe (Turkish)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #BOŞ!
DIV0 = #SAYI/0!
VALUE = #DEĞER!
REF = #BAŞV!
NAME = #AD?
NUM = #SAYI!
NA = #YOK
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions 0000644 00000024411 15243417764 0021365 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Türkçe (Turkish)
##
############################################################
##
## Küp işlevleri (Cube Functions)
##
CUBEKPIMEMBER = KÜPKPIÜYESİ
CUBEMEMBER = KÜPÜYESİ
CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ
CUBERANKEDMEMBER = DERECELİKÜPÜYESİ
CUBESET = KÜPKÜMESİ
CUBESETCOUNT = KÜPKÜMESAYISI
CUBEVALUE = KÜPDEĞERİ
##
## Veritabanı işlevleri (Database Functions)
##
DAVERAGE = VSEÇORT
DCOUNT = VSEÇSAY
DCOUNTA = VSEÇSAYDOLU
DGET = VAL
DMAX = VSEÇMAK
DMIN = VSEÇMİN
DPRODUCT = VSEÇÇARP
DSTDEV = VSEÇSTDSAPMA
DSTDEVP = VSEÇSTDSAPMAS
DSUM = VSEÇTOPLA
DVAR = VSEÇVAR
DVARP = VSEÇVARS
##
## Tarih ve saat işlevleri (Date & Time Functions)
##
DATE = TARİH
DATEDIF = ETARİHLİ
DATESTRING = TARİHDİZİ
DATEVALUE = TARİHSAYISI
DAY = GÜN
DAYS = GÜNSAY
DAYS360 = GÜN360
EDATE = SERİTARİH
EOMONTH = SERİAY
HOUR = SAAT
ISOWEEKNUM = ISOHAFTASAY
MINUTE = DAKİKA
MONTH = AY
NETWORKDAYS = TAMİŞGÜNÜ
NETWORKDAYS.INTL = TAMİŞGÜNÜ.ULUSL
NOW = ŞİMDİ
SECOND = SANİYE
THAIDAYOFWEEK = TAYHAFTANINGÜNÜ
THAIMONTHOFYEAR = TAYYILINAYI
THAIYEAR = TAYYILI
TIME = ZAMAN
TIMEVALUE = ZAMANSAYISI
TODAY = BUGÜN
WEEKDAY = HAFTANINGÜNÜ
WEEKNUM = HAFTASAY
WORKDAY = İŞGÜNÜ
WORKDAY.INTL = İŞGÜNÜ.ULUSL
YEAR = YIL
YEARFRAC = YILORAN
##
## Mühendislik işlevleri (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BIN2DEC
BIN2HEX = BIN2HEX
BIN2OCT = BIN2OCT
BITAND = BİTVE
BITLSHIFT = BİTSOLAKAYDIR
BITOR = BİTVEYA
BITRSHIFT = BİTSAĞAKAYDIR
BITXOR = BİTÖZELVEYA
COMPLEX = KARMAŞIK
CONVERT = ÇEVİR
DEC2BIN = DEC2BIN
DEC2HEX = DEC2HEX
DEC2OCT = DEC2OCT
DELTA = DELTA
ERF = HATAİŞLEV
ERF.PRECISE = HATAİŞLEV.DUYARLI
ERFC = TÜMHATAİŞLEV
ERFC.PRECISE = TÜMHATAİŞLEV.DUYARLI
GESTEP = BESINIR
HEX2BIN = HEX2BIN
HEX2DEC = HEX2DEC
HEX2OCT = HEX2OCT
IMABS = SANMUTLAK
IMAGINARY = SANAL
IMARGUMENT = SANBAĞ_DEĞİŞKEN
IMCONJUGATE = SANEŞLENEK
IMCOS = SANCOS
IMCOSH = SANCOSH
IMCOT = SANCOT
IMCSC = SANCSC
IMCSCH = SANCSCH
IMDIV = SANBÖL
IMEXP = SANÜS
IMLN = SANLN
IMLOG10 = SANLOG10
IMLOG2 = SANLOG2
IMPOWER = SANKUVVET
IMPRODUCT = SANÇARP
IMREAL = SANGERÇEK
IMSEC = SANSEC
IMSECH = SANSECH
IMSIN = SANSIN
IMSINH = SANSINH
IMSQRT = SANKAREKÖK
IMSUB = SANTOPLA
IMSUM = SANÇIKAR
IMTAN = SANTAN
OCT2BIN = OCT2BIN
OCT2DEC = OCT2DEC
OCT2HEX = OCT2HEX
##
## Finansal işlevler (Financial Functions)
##
ACCRINT = GERÇEKFAİZ
ACCRINTM = GERÇEKFAİZV
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = KUPONGÜNBD
COUPDAYS = KUPONGÜN
COUPDAYSNC = KUPONGÜNDSK
COUPNCD = KUPONGÜNSKT
COUPNUM = KUPONSAYI
COUPPCD = KUPONGÜNÖKT
CUMIPMT = TOPÖDENENFAİZ
CUMPRINC = TOPANAPARA
DB = AZALANBAKİYE
DDB = ÇİFTAZALANBAKİYE
DISC = İNDİRİM
DOLLARDE = LİRAON
DOLLARFR = LİRAKES
DURATION = SÜRE
EFFECT = ETKİN
FV = GD
FVSCHEDULE = GDPROGRAM
INTRATE = FAİZORANI
IPMT = FAİZTUTARI
IRR = İÇ_VERİM_ORANI
ISPMT = ISPMT
MDURATION = MSÜRE
MIRR = D_İÇ_VERİM_ORANI
NOMINAL = NOMİNAL
NPER = TAKSİT_SAYISI
NPV = NBD
ODDFPRICE = TEKYDEĞER
ODDFYIELD = TEKYÖDEME
ODDLPRICE = TEKSDEĞER
ODDLYIELD = TEKSÖDEME
PDURATION = PSÜRE
PMT = DEVRESEL_ÖDEME
PPMT = ANA_PARA_ÖDEMESİ
PRICE = DEĞER
PRICEDISC = DEĞERİND
PRICEMAT = DEĞERVADE
PV = BD
RATE = FAİZ_ORANI
RECEIVED = GETİRİ
RRI = GERÇEKLEŞENYATIRIMGETİRİSİ
SLN = DA
SYD = YAT
TBILLEQ = HTAHEŞ
TBILLPRICE = HTAHDEĞER
TBILLYIELD = HTAHÖDEME
VDB = DAB
XIRR = AİÇVERİMORANI
XNPV = ANBD
YIELD = ÖDEME
YIELDDISC = ÖDEMEİND
YIELDMAT = ÖDEMEVADE
##
## Bilgi işlevleri (Information Functions)
##
CELL = HÜCRE
ERROR.TYPE = HATA.TİPİ
INFO = BİLGİ
ISBLANK = EBOŞSA
ISERR = EHATA
ISERROR = EHATALIYSA
ISEVEN = ÇİFTMİ
ISFORMULA = EFORMÜLSE
ISLOGICAL = EMANTIKSALSA
ISNA = EYOKSA
ISNONTEXT = EMETİNDEĞİLSE
ISNUMBER = ESAYIYSA
ISODD = TEKMİ
ISREF = EREFSE
ISTEXT = EMETİNSE
N = S
NA = YOKSAY
SHEET = SAYFA
SHEETS = SAYFALAR
TYPE = TÜR
##
## Mantıksal işlevler (Logical Functions)
##
AND = VE
FALSE = YANLIŞ
IF = EĞER
IFERROR = EĞERHATA
IFNA = EĞERYOKSA
IFS = ÇOKEĞER
NOT = DEĞİL
OR = YADA
SWITCH = İLKEŞLEŞEN
TRUE = DOĞRU
XOR = ÖZELVEYA
##
## Arama ve başvuru işlevleri (Lookup & Reference Functions)
##
ADDRESS = ADRES
AREAS = ALANSAY
CHOOSE = ELEMAN
COLUMN = SÜTUN
COLUMNS = SÜTUNSAY
FORMULATEXT = FORMÜLMETNİ
GETPIVOTDATA = ÖZETVERİAL
HLOOKUP = YATAYARA
HYPERLINK = KÖPRÜ
INDEX = İNDİS
INDIRECT = DOLAYLI
LOOKUP = ARA
MATCH = KAÇINCI
OFFSET = KAYDIR
ROW = SATIR
ROWS = SATIRSAY
RTD = GZV
TRANSPOSE = DEVRİK_DÖNÜŞÜM
VLOOKUP = DÜŞEYARA
##
## Matematik ve trigonometri işlevleri (Math & Trig Functions)
##
ABS = MUTLAK
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = TOPLAMA
ARABIC = ARAP
ASIN = ASİN
ASINH = ASİNH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = TABAN
CEILING.MATH = TAVANAYUVARLA.MATEMATİK
CEILING.PRECISE = TAVANAYUVARLA.DUYARLI
COMBIN = KOMBİNASYON
COMBINA = KOMBİNASYONA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = ONDALIK
DEGREES = DERECE
ECMA.CEILING = ECMA.TAVAN
EVEN = ÇİFT
EXP = ÜS
FACT = ÇARPINIM
FACTDOUBLE = ÇİFTFAKTÖR
FLOOR.MATH = TABANAYUVARLA.MATEMATİK
FLOOR.PRECISE = TABANAYUVARLA.DUYARLI
GCD = OBEB
INT = TAMSAYI
ISO.CEILING = ISO.TAVAN
LCM = OKEK
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = DETERMİNANT
MINVERSE = DİZEY_TERS
MMULT = DÇARP
MOD = MOD
MROUND = KYUVARLA
MULTINOMIAL = ÇOKTERİMLİ
MUNIT = BİRİMMATRİS
ODD = TEK
PI = Pİ
POWER = KUVVET
PRODUCT = ÇARPIM
QUOTIENT = BÖLÜM
RADIANS = RADYAN
RAND = S_SAYI_ÜRET
RANDBETWEEN = RASTGELEARADA
ROMAN = ROMEN
ROUND = YUVARLA
ROUNDBAHTDOWN = BAHTAŞAĞIYUVARLA
ROUNDBAHTUP = BAHTYUKARIYUVARLA
ROUNDDOWN = AŞAĞIYUVARLA
ROUNDUP = YUKARIYUVARLA
SEC = SEC
SECH = SECH
SERIESSUM = SERİTOPLA
SIGN = İŞARET
SIN = SİN
SINH = SİNH
SQRT = KAREKÖK
SQRTPI = KAREKÖKPİ
SUBTOTAL = ALTTOPLAM
SUM = TOPLA
SUMIF = ETOPLA
SUMIFS = ÇOKETOPLA
SUMPRODUCT = TOPLA.ÇARPIM
SUMSQ = TOPKARE
SUMX2MY2 = TOPX2EY2
SUMX2PY2 = TOPX2AY2
SUMXMY2 = TOPXEY2
TAN = TAN
TANH = TANH
TRUNC = NSAT
##
## İstatistik işlevleri (Statistical Functions)
##
AVEDEV = ORTSAP
AVERAGE = ORTALAMA
AVERAGEA = ORTALAMAA
AVERAGEIF = EĞERORTALAMA
AVERAGEIFS = ÇOKEĞERORTALAMA
BETA.DIST = BETA.DAĞ
BETA.INV = BETA.TERS
BINOM.DIST = BİNOM.DAĞ
BINOM.DIST.RANGE = BİNOM.DAĞ.ARALIK
BINOM.INV = BİNOM.TERS
CHISQ.DIST = KİKARE.DAĞ
CHISQ.DIST.RT = KİKARE.DAĞ.SAĞK
CHISQ.INV = KİKARE.TERS
CHISQ.INV.RT = KİKARE.TERS.SAĞK
CHISQ.TEST = KİKARE.TEST
CONFIDENCE.NORM = GÜVENİLİRLİK.NORM
CONFIDENCE.T = GÜVENİLİRLİK.T
CORREL = KORELASYON
COUNT = BAĞ_DEĞ_SAY
COUNTA = BAĞ_DEĞ_DOLU_SAY
COUNTBLANK = BOŞLUKSAY
COUNTIF = EĞERSAY
COUNTIFS = ÇOKEĞERSAY
COVARIANCE.P = KOVARYANS.P
COVARIANCE.S = KOVARYANS.S
DEVSQ = SAPKARE
EXPON.DIST = ÜSTEL.DAĞ
F.DIST = F.DAĞ
F.DIST.RT = F.DAĞ.SAĞK
F.INV = F.TERS
F.INV.RT = F.TERS.SAĞK
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHERTERS
FORECAST.ETS = TAHMİN.ETS
FORECAST.ETS.CONFINT = TAHMİN.ETS.GVNARAL
FORECAST.ETS.SEASONALITY = TAHMİN.ETS.MEVSİMSELLİK
FORECAST.ETS.STAT = TAHMİN.ETS.İSTAT
FORECAST.LINEAR = TAHMİN.DOĞRUSAL
FREQUENCY = SIKLIK
GAMMA = GAMA
GAMMA.DIST = GAMA.DAĞ
GAMMA.INV = GAMA.TERS
GAMMALN = GAMALN
GAMMALN.PRECISE = GAMALN.DUYARLI
GAUSS = GAUSS
GEOMEAN = GEOORT
GROWTH = BÜYÜME
HARMEAN = HARORT
HYPGEOM.DIST = HİPERGEOM.DAĞ
INTERCEPT = KESMENOKTASI
KURT = BASIKLIK
LARGE = BÜYÜK
LINEST = DOT
LOGEST = LOT
LOGNORM.DIST = LOGNORM.DAĞ
LOGNORM.INV = LOGNORM.TERS
MAX = MAK
MAXA = MAKA
MAXIFS = ÇOKEĞERMAK
MEDIAN = ORTANCA
MIN = MİN
MINA = MİNA
MINIFS = ÇOKEĞERMİN
MODE.MULT = ENÇOK_OLAN.ÇOK
MODE.SNGL = ENÇOK_OLAN.TEK
NEGBINOM.DIST = NEGBİNOM.DAĞ
NORM.DIST = NORM.DAĞ
NORM.INV = NORM.TERS
NORM.S.DIST = NORM.S.DAĞ
NORM.S.INV = NORM.S.TERS
PEARSON = PEARSON
PERCENTILE.EXC = YÜZDEBİRLİK.HRC
PERCENTILE.INC = YÜZDEBİRLİK.DHL
PERCENTRANK.EXC = YÜZDERANK.HRC
PERCENTRANK.INC = YÜZDERANK.DHL
PERMUT = PERMÜTASYON
PERMUTATIONA = PERMÜTASYONA
PHI = PHI
POISSON.DIST = POISSON.DAĞ
PROB = OLASILIK
QUARTILE.EXC = DÖRTTEBİRLİK.HRC
QUARTILE.INC = DÖRTTEBİRLİK.DHL
RANK.AVG = RANK.ORT
RANK.EQ = RANK.EŞİT
RSQ = RKARE
SKEW = ÇARPIKLIK
SKEW.P = ÇARPIKLIK.P
SLOPE = EĞİM
SMALL = KÜÇÜK
STANDARDIZE = STANDARTLAŞTIRMA
STDEV.P = STDSAPMA.P
STDEV.S = STDSAPMA.S
STDEVA = STDSAPMAA
STDEVPA = STDSAPMASA
STEYX = STHYX
T.DIST = T.DAĞ
T.DIST.2T = T.DAĞ.2K
T.DIST.RT = T.DAĞ.SAĞK
T.INV = T.TERS
T.INV.2T = T.TERS.2K
T.TEST = T.TEST
TREND = EĞİLİM
TRIMMEAN = KIRPORTALAMA
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARSA
WEIBULL.DIST = WEIBULL.DAĞ
Z.TEST = Z.TEST
##
## Metin işlevleri (Text Functions)
##
BAHTTEXT = BAHTMETİN
CHAR = DAMGA
CLEAN = TEMİZ
CODE = KOD
CONCAT = ARALIKBİRLEŞTİR
DOLLAR = LİRA
EXACT = ÖZDEŞ
FIND = BUL
FIXED = SAYIDÜZENLE
ISTHAIDIGIT = TAYRAKAMIYSA
LEFT = SOLDAN
LEN = UZUNLUK
LOWER = KÜÇÜKHARF
MID = PARÇAAL
NUMBERSTRING = SAYIDİZİ
NUMBERVALUE = SAYIDEĞERİ
PHONETIC = SES
PROPER = YAZIM.DÜZENİ
REPLACE = DEĞİŞTİR
REPT = YİNELE
RIGHT = SAĞDAN
SEARCH = MBUL
SUBSTITUTE = YERİNEKOY
T = M
TEXT = METNEÇEVİR
TEXTJOIN = METİNBİRLEŞTİR
THAIDIGIT = TAYRAKAM
THAINUMSOUND = TAYSAYISES
THAINUMSTRING = TAYSAYIDİZE
THAISTRINGLENGTH = TAYDİZEUZUNLUĞU
TRIM = KIRP
UNICHAR = UNICODEKARAKTERİ
UNICODE = UNICODE
UPPER = BÜYÜKHARF
VALUE = SAYIYAÇEVİR
##
## Metin işlevleri (Web Functions)
##
ENCODEURL = URLKODLA
FILTERXML = XMLFİLTRELE
WEBSERVICE = WEBHİZMETİ
##
## Uyumluluk işlevleri (Compatibility Functions)
##
BETADIST = BETADAĞ
BETAINV = BETATERS
BINOMDIST = BİNOMDAĞ
CEILING = TAVANAYUVARLA
CHIDIST = KİKAREDAĞ
CHIINV = KİKARETERS
CHITEST = KİKARETEST
CONCATENATE = BİRLEŞTİR
CONFIDENCE = GÜVENİRLİK
COVAR = KOVARYANS
CRITBINOM = KRİTİKBİNOM
EXPONDIST = ÜSTELDAĞ
FDIST = FDAĞ
FINV = FTERS
FLOOR = TABANAYUVARLA
FORECAST = TAHMİN
FTEST = FTEST
GAMMADIST = GAMADAĞ
GAMMAINV = GAMATERS
HYPGEOMDIST = HİPERGEOMDAĞ
LOGINV = LOGTERS
LOGNORMDIST = LOGNORMDAĞ
MODE = ENÇOK_OLAN
NEGBINOMDIST = NEGBİNOMDAĞ
NORMDIST = NORMDAĞ
NORMINV = NORMTERS
NORMSDIST = NORMSDAĞ
NORMSINV = NORMSTERS
PERCENTILE = YÜZDEBİRLİK
PERCENTRANK = YÜZDERANK
POISSON = POISSON
QUARTILE = DÖRTTEBİRLİK
RANK = RANK
STDEV = STDSAPMA
STDEVP = STDSAPMAS
TDIST = TDAĞ
TINV = TTERS
TTEST = TTEST
VAR = VAR
VARP = VARS
WEIBULL = WEIBULL
ZTEST = ZTEST
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config 0000644 00000000514 15243417764 0020604 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Nederlands (Dutch)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #LEEG!
DIV0 = #DEEL/0!
VALUE = #WAARDE!
REF = #VERW!
NAME = #NAAM?
NUM = #GETAL!
NA = #N/B
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions 0000644 00000024377 15243417764 0021364 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Nederlands (Dutch)
##
############################################################
##
## Kubusfuncties (Cube Functions)
##
CUBEKPIMEMBER = KUBUSKPILID
CUBEMEMBER = KUBUSLID
CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP
CUBERANKEDMEMBER = KUBUSGERANGSCHIKTLID
CUBESET = KUBUSSET
CUBESETCOUNT = KUBUSSETAANTAL
CUBEVALUE = KUBUSWAARDE
##
## Databasefuncties (Database Functions)
##
DAVERAGE = DBGEMIDDELDE
DCOUNT = DBAANTAL
DCOUNTA = DBAANTALC
DGET = DBLEZEN
DMAX = DBMAX
DMIN = DBMIN
DPRODUCT = DBPRODUCT
DSTDEV = DBSTDEV
DSTDEVP = DBSTDEVP
DSUM = DBSOM
DVAR = DBVAR
DVARP = DBVARP
##
## Datum- en tijdfuncties (Date & Time Functions)
##
DATE = DATUM
DATESTRING = DATUMNOTATIE
DATEVALUE = DATUMWAARDE
DAY = DAG
DAYS = DAGEN
DAYS360 = DAGEN360
EDATE = ZELFDE.DAG
EOMONTH = LAATSTE.DAG
HOUR = UUR
ISOWEEKNUM = ISO.WEEKNUMMER
MINUTE = MINUUT
MONTH = MAAND
NETWORKDAYS = NETTO.WERKDAGEN
NETWORKDAYS.INTL = NETWERKDAGEN.INTL
NOW = NU
SECOND = SECONDE
THAIDAYOFWEEK = THAIS.WEEKDAG
THAIMONTHOFYEAR = THAIS.MAAND.VAN.JAAR
THAIYEAR = THAIS.JAAR
TIME = TIJD
TIMEVALUE = TIJDWAARDE
TODAY = VANDAAG
WEEKDAY = WEEKDAG
WEEKNUM = WEEKNUMMER
WORKDAY = WERKDAG
WORKDAY.INTL = WERKDAG.INTL
YEAR = JAAR
YEARFRAC = JAAR.DEEL
##
## Technische functies (Engineering Functions)
##
BESSELI = BESSEL.I
BESSELJ = BESSEL.J
BESSELK = BESSEL.K
BESSELY = BESSEL.Y
BIN2DEC = BIN.N.DEC
BIN2HEX = BIN.N.HEX
BIN2OCT = BIN.N.OCT
BITAND = BIT.EN
BITLSHIFT = BIT.VERSCHUIF.LINKS
BITOR = BIT.OF
BITRSHIFT = BIT.VERSCHUIF.RECHTS
BITXOR = BIT.EX.OF
COMPLEX = COMPLEX
CONVERT = CONVERTEREN
DEC2BIN = DEC.N.BIN
DEC2HEX = DEC.N.HEX
DEC2OCT = DEC.N.OCT
DELTA = DELTA
ERF = FOUTFUNCTIE
ERF.PRECISE = FOUTFUNCTIE.NAUWKEURIG
ERFC = FOUT.COMPLEMENT
ERFC.PRECISE = FOUT.COMPLEMENT.NAUWKEURIG
GESTEP = GROTER.DAN
HEX2BIN = HEX.N.BIN
HEX2DEC = HEX.N.DEC
HEX2OCT = HEX.N.OCT
IMABS = C.ABS
IMAGINARY = C.IM.DEEL
IMARGUMENT = C.ARGUMENT
IMCONJUGATE = C.TOEGEVOEGD
IMCOS = C.COS
IMCOSH = C.COSH
IMCOT = C.COT
IMCSC = C.COSEC
IMCSCH = C.COSECH
IMDIV = C.QUOTIENT
IMEXP = C.EXP
IMLN = C.LN
IMLOG10 = C.LOG10
IMLOG2 = C.LOG2
IMPOWER = C.MACHT
IMPRODUCT = C.PRODUCT
IMREAL = C.REEEL.DEEL
IMSEC = C.SEC
IMSECH = C.SECH
IMSIN = C.SIN
IMSINH = C.SINH
IMSQRT = C.WORTEL
IMSUB = C.VERSCHIL
IMSUM = C.SOM
IMTAN = C.TAN
OCT2BIN = OCT.N.BIN
OCT2DEC = OCT.N.DEC
OCT2HEX = OCT.N.HEX
##
## Financiële functies (Financial Functions)
##
ACCRINT = SAMENG.RENTE
ACCRINTM = SAMENG.RENTE.V
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = COUP.DAGEN.BB
COUPDAYS = COUP.DAGEN
COUPDAYSNC = COUP.DAGEN.VV
COUPNCD = COUP.DATUM.NB
COUPNUM = COUP.AANTAL
COUPPCD = COUP.DATUM.VB
CUMIPMT = CUM.RENTE
CUMPRINC = CUM.HOOFDSOM
DB = DB
DDB = DDB
DISC = DISCONTO
DOLLARDE = EURO.DE
DOLLARFR = EURO.BR
DURATION = DUUR
EFFECT = EFFECT.RENTE
FV = TW
FVSCHEDULE = TOEK.WAARDE2
INTRATE = RENTEPERCENTAGE
IPMT = IBET
IRR = IR
ISPMT = ISBET
MDURATION = AANG.DUUR
MIRR = GIR
NOMINAL = NOMINALE.RENTE
NPER = NPER
NPV = NHW
ODDFPRICE = AFW.ET.PRIJS
ODDFYIELD = AFW.ET.REND
ODDLPRICE = AFW.LT.PRIJS
ODDLYIELD = AFW.LT.REND
PDURATION = PDUUR
PMT = BET
PPMT = PBET
PRICE = PRIJS.NOM
PRICEDISC = PRIJS.DISCONTO
PRICEMAT = PRIJS.VERVALDAG
PV = HW
RATE = RENTE
RECEIVED = OPBRENGST
RRI = RRI
SLN = LIN.AFSCHR
SYD = SYD
TBILLEQ = SCHATK.OBL
TBILLPRICE = SCHATK.PRIJS
TBILLYIELD = SCHATK.REND
VDB = VDB
XIRR = IR.SCHEMA
XNPV = NHW2
YIELD = RENDEMENT
YIELDDISC = REND.DISCONTO
YIELDMAT = REND.VERVAL
##
## Informatiefuncties (Information Functions)
##
CELL = CEL
ERROR.TYPE = TYPE.FOUT
INFO = INFO
ISBLANK = ISLEEG
ISERR = ISFOUT2
ISERROR = ISFOUT
ISEVEN = IS.EVEN
ISFORMULA = ISFORMULE
ISLOGICAL = ISLOGISCH
ISNA = ISNB
ISNONTEXT = ISGEENTEKST
ISNUMBER = ISGETAL
ISODD = IS.ONEVEN
ISREF = ISVERWIJZING
ISTEXT = ISTEKST
N = N
NA = NB
SHEET = BLAD
SHEETS = BLADEN
TYPE = TYPE
##
## Logische functies (Logical Functions)
##
AND = EN
FALSE = ONWAAR
IF = ALS
IFERROR = ALS.FOUT
IFNA = ALS.NB
IFS = ALS.VOORWAARDEN
NOT = NIET
OR = OF
SWITCH = SCHAKELEN
TRUE = WAAR
XOR = EX.OF
##
## Zoek- en verwijzingsfuncties (Lookup & Reference Functions)
##
ADDRESS = ADRES
AREAS = BEREIKEN
CHOOSE = KIEZEN
COLUMN = KOLOM
COLUMNS = KOLOMMEN
FORMULATEXT = FORMULETEKST
GETPIVOTDATA = DRAAITABEL.OPHALEN
HLOOKUP = HORIZ.ZOEKEN
HYPERLINK = HYPERLINK
INDEX = INDEX
INDIRECT = INDIRECT
LOOKUP = ZOEKEN
MATCH = VERGELIJKEN
OFFSET = VERSCHUIVING
ROW = RIJ
ROWS = RIJEN
RTD = RTG
TRANSPOSE = TRANSPONEREN
VLOOKUP = VERT.ZOEKEN
*RC = RK
##
## Wiskundige en trigonometrische functies (Math & Trig Functions)
##
ABS = ABS
ACOS = BOOGCOS
ACOSH = BOOGCOSH
ACOT = BOOGCOT
ACOTH = BOOGCOTH
AGGREGATE = AGGREGAAT
ARABIC = ARABISCH
ASIN = BOOGSIN
ASINH = BOOGSINH
ATAN = BOOGTAN
ATAN2 = BOOGTAN2
ATANH = BOOGTANH
BASE = BASIS
CEILING.MATH = AFRONDEN.BOVEN.WISK
CEILING.PRECISE = AFRONDEN.BOVEN.NAUWKEURIG
COMBIN = COMBINATIES
COMBINA = COMBIN.A
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = COSEC
CSCH = COSECH
DECIMAL = DECIMAAL
DEGREES = GRADEN
ECMA.CEILING = ECMA.AFRONDEN.BOVEN
EVEN = EVEN
EXP = EXP
FACT = FACULTEIT
FACTDOUBLE = DUBBELE.FACULTEIT
FLOOR.MATH = AFRONDEN.BENEDEN.WISK
FLOOR.PRECISE = AFRONDEN.BENEDEN.NAUWKEURIG
GCD = GGD
INT = INTEGER
ISO.CEILING = ISO.AFRONDEN.BOVEN
LCM = KGV
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = DETERMINANTMAT
MINVERSE = INVERSEMAT
MMULT = PRODUCTMAT
MOD = REST
MROUND = AFRONDEN.N.VEELVOUD
MULTINOMIAL = MULTINOMIAAL
MUNIT = EENHEIDMAT
ODD = ONEVEN
PI = PI
POWER = MACHT
PRODUCT = PRODUCT
QUOTIENT = QUOTIENT
RADIANS = RADIALEN
RAND = ASELECT
RANDBETWEEN = ASELECTTUSSEN
ROMAN = ROMEINS
ROUND = AFRONDEN
ROUNDBAHTDOWN = BAHT.AFR.NAAR.BENEDEN
ROUNDBAHTUP = BAHT.AFR.NAAR.BOVEN
ROUNDDOWN = AFRONDEN.NAAR.BENEDEN
ROUNDUP = AFRONDEN.NAAR.BOVEN
SEC = SEC
SECH = SECH
SERIESSUM = SOM.MACHTREEKS
SIGN = POS.NEG
SIN = SIN
SINH = SINH
SQRT = WORTEL
SQRTPI = WORTEL.PI
SUBTOTAL = SUBTOTAAL
SUM = SOM
SUMIF = SOM.ALS
SUMIFS = SOMMEN.ALS
SUMPRODUCT = SOMPRODUCT
SUMSQ = KWADRATENSOM
SUMX2MY2 = SOM.X2MINY2
SUMX2PY2 = SOM.X2PLUSY2
SUMXMY2 = SOM.XMINY.2
TAN = TAN
TANH = TANH
TRUNC = GEHEEL
##
## Statistische functies (Statistical Functions)
##
AVEDEV = GEM.DEVIATIE
AVERAGE = GEMIDDELDE
AVERAGEA = GEMIDDELDEA
AVERAGEIF = GEMIDDELDE.ALS
AVERAGEIFS = GEMIDDELDEN.ALS
BETA.DIST = BETA.VERD
BETA.INV = BETA.INV
BINOM.DIST = BINOM.VERD
BINOM.DIST.RANGE = BINOM.VERD.BEREIK
BINOM.INV = BINOMIALE.INV
CHISQ.DIST = CHIKW.VERD
CHISQ.DIST.RT = CHIKW.VERD.RECHTS
CHISQ.INV = CHIKW.INV
CHISQ.INV.RT = CHIKW.INV.RECHTS
CHISQ.TEST = CHIKW.TEST
CONFIDENCE.NORM = VERTROUWELIJKHEID.NORM
CONFIDENCE.T = VERTROUWELIJKHEID.T
CORREL = CORRELATIE
COUNT = AANTAL
COUNTA = AANTALARG
COUNTBLANK = AANTAL.LEGE.CELLEN
COUNTIF = AANTAL.ALS
COUNTIFS = AANTALLEN.ALS
COVARIANCE.P = COVARIANTIE.P
COVARIANCE.S = COVARIANTIE.S
DEVSQ = DEV.KWAD
EXPON.DIST = EXPON.VERD.N
F.DIST = F.VERD
F.DIST.RT = F.VERD.RECHTS
F.INV = F.INV
F.INV.RT = F.INV.RECHTS
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHER.INV
FORECAST.ETS = VOORSPELLEN.ETS
FORECAST.ETS.CONFINT = VOORSPELLEN.ETS.CONFINT
FORECAST.ETS.SEASONALITY = VOORSPELLEN.ETS.SEASONALITY
FORECAST.ETS.STAT = FORECAST.ETS.STAT
FORECAST.LINEAR = VOORSPELLEN.LINEAR
FREQUENCY = INTERVAL
GAMMA = GAMMA
GAMMA.DIST = GAMMA.VERD.N
GAMMA.INV = GAMMA.INV.N
GAMMALN = GAMMA.LN
GAMMALN.PRECISE = GAMMA.LN.NAUWKEURIG
GAUSS = GAUSS
GEOMEAN = MEETK.GEM
GROWTH = GROEI
HARMEAN = HARM.GEM
HYPGEOM.DIST = HYPGEOM.VERD
INTERCEPT = SNIJPUNT
KURT = KURTOSIS
LARGE = GROOTSTE
LINEST = LIJNSCH
LOGEST = LOGSCH
LOGNORM.DIST = LOGNORM.VERD
LOGNORM.INV = LOGNORM.INV
MAX = MAX
MAXA = MAXA
MAXIFS = MAX.ALS.VOORWAARDEN
MEDIAN = MEDIAAN
MIN = MIN
MINA = MINA
MINIFS = MIN.ALS.VOORWAARDEN
MODE.MULT = MODUS.MEERV
MODE.SNGL = MODUS.ENKELV
NEGBINOM.DIST = NEGBINOM.VERD
NORM.DIST = NORM.VERD.N
NORM.INV = NORM.INV.N
NORM.S.DIST = NORM.S.VERD
NORM.S.INV = NORM.S.INV
PEARSON = PEARSON
PERCENTILE.EXC = PERCENTIEL.EXC
PERCENTILE.INC = PERCENTIEL.INC
PERCENTRANK.EXC = PROCENTRANG.EXC
PERCENTRANK.INC = PROCENTRANG.INC
PERMUT = PERMUTATIES
PERMUTATIONA = PERMUTATIE.A
PHI = PHI
POISSON.DIST = POISSON.VERD
PROB = KANS
QUARTILE.EXC = KWARTIEL.EXC
QUARTILE.INC = KWARTIEL.INC
RANK.AVG = RANG.GEMIDDELDE
RANK.EQ = RANG.GELIJK
RSQ = R.KWADRAAT
SKEW = SCHEEFHEID
SKEW.P = SCHEEFHEID.P
SLOPE = RICHTING
SMALL = KLEINSTE
STANDARDIZE = NORMALISEREN
STDEV.P = STDEV.P
STDEV.S = STDEV.S
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = STAND.FOUT.YX
T.DIST = T.DIST
T.DIST.2T = T.VERD.2T
T.DIST.RT = T.VERD.RECHTS
T.INV = T.INV
T.INV.2T = T.INV.2T
T.TEST = T.TEST
TREND = TREND
TRIMMEAN = GETRIMD.GEM
VAR.P = VAR.P
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = WEIBULL.VERD
Z.TEST = Z.TEST
##
## Tekstfuncties (Text Functions)
##
BAHTTEXT = BAHT.TEKST
CHAR = TEKEN
CLEAN = WISSEN.CONTROL
CODE = CODE
CONCAT = TEKST.SAMENV
DOLLAR = EURO
EXACT = GELIJK
FIND = VIND.ALLES
FIXED = VAST
ISTHAIDIGIT = IS.THAIS.CIJFER
LEFT = LINKS
LEN = LENGTE
LOWER = KLEINE.LETTERS
MID = DEEL
NUMBERSTRING = GETALNOTATIE
NUMBERVALUE = NUMERIEKE.WAARDE
PHONETIC = FONETISCH
PROPER = BEGINLETTERS
REPLACE = VERVANGEN
REPT = HERHALING
RIGHT = RECHTS
SEARCH = VIND.SPEC
SUBSTITUTE = SUBSTITUEREN
T = T
TEXT = TEKST
TEXTJOIN = TEKST.COMBINEREN
THAIDIGIT = THAIS.CIJFER
THAINUMSOUND = THAIS.GETAL.GELUID
THAINUMSTRING = THAIS.GETAL.REEKS
THAISTRINGLENGTH = THAIS.REEKS.LENGTE
TRIM = SPATIES.WISSEN
UNICHAR = UNITEKEN
UNICODE = UNICODE
UPPER = HOOFDLETTERS
VALUE = WAARDE
##
## Webfuncties (Web Functions)
##
ENCODEURL = URL.CODEREN
FILTERXML = XML.FILTEREN
WEBSERVICE = WEBSERVICE
##
## Compatibiliteitsfuncties (Compatibility Functions)
##
BETADIST = BETAVERD
BETAINV = BETAINV
BINOMDIST = BINOMIALE.VERD
CEILING = AFRONDEN.BOVEN
CHIDIST = CHI.KWADRAAT
CHIINV = CHI.KWADRAAT.INV
CHITEST = CHI.TOETS
CONCATENATE = TEKST.SAMENVOEGEN
CONFIDENCE = BETROUWBAARHEID
COVAR = COVARIANTIE
CRITBINOM = CRIT.BINOM
EXPONDIST = EXPON.VERD
FDIST = F.VERDELING
FINV = F.INVERSE
FLOOR = AFRONDEN.BENEDEN
FORECAST = VOORSPELLEN
FTEST = F.TOETS
GAMMADIST = GAMMA.VERD
GAMMAINV = GAMMA.INV
HYPGEOMDIST = HYPERGEO.VERD
LOGINV = LOG.NORM.INV
LOGNORMDIST = LOG.NORM.VERD
MODE = MODUS
NEGBINOMDIST = NEG.BINOM.VERD
NORMDIST = NORM.VERD
NORMINV = NORM.INV
NORMSDIST = STAND.NORM.VERD
NORMSINV = STAND.NORM.INV
PERCENTILE = PERCENTIEL
PERCENTRANK = PERCENT.RANG
POISSON = POISSON
QUARTILE = KWARTIEL
RANK = RANG
STDEV = STDEV
STDEVP = STDEVP
TDIST = T.VERD
TINV = TINV
TTEST = T.TOETS
VAR = VAR
VARP = VARP
WEIBULL = WEIBULL
ZTEST = Z.TOETS
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config 0000644 00000000107 15243417764 0021212 0 ustar 00 ##
## PhpSpreadsheet
##
##
## (For future use)
##
currencySymbol = £
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config 0000644 00000000460 15243417764 0020602 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## Français (French)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #NUL!
DIV0
VALUE = #VALEUR!
REF
NAME = #NOM?
NUM = #NOMBRE!
NA
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions 0000644 00000024740 15243417764 0021354 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## Français (French)
##
############################################################
##
## Fonctions Cube (Cube Functions)
##
CUBEKPIMEMBER = MEMBREKPICUBE
CUBEMEMBER = MEMBRECUBE
CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE
CUBERANKEDMEMBER = RANGMEMBRECUBE
CUBESET = JEUCUBE
CUBESETCOUNT = NBJEUCUBE
CUBEVALUE = VALEURCUBE
##
## Fonctions de base de données (Database Functions)
##
DAVERAGE = BDMOYENNE
DCOUNT = BDNB
DCOUNTA = BDNBVAL
DGET = BDLIRE
DMAX = BDMAX
DMIN = BDMIN
DPRODUCT = BDPRODUIT
DSTDEV = BDECARTYPE
DSTDEVP = BDECARTYPEP
DSUM = BDSOMME
DVAR = BDVAR
DVARP = BDVARP
##
## Fonctions de date et d’heure (Date & Time Functions)
##
DATE = DATE
DATEVALUE = DATEVAL
DAY = JOUR
DAYS = JOURS
DAYS360 = JOURS360
EDATE = MOIS.DECALER
EOMONTH = FIN.MOIS
HOUR = HEURE
ISOWEEKNUM = NO.SEMAINE.ISO
MINUTE = MINUTE
MONTH = MOIS
NETWORKDAYS = NB.JOURS.OUVRES
NETWORKDAYS.INTL = NB.JOURS.OUVRES.INTL
NOW = MAINTENANT
SECOND = SECONDE
TIME = TEMPS
TIMEVALUE = TEMPSVAL
TODAY = AUJOURDHUI
WEEKDAY = JOURSEM
WEEKNUM = NO.SEMAINE
WORKDAY = SERIE.JOUR.OUVRE
WORKDAY.INTL = SERIE.JOUR.OUVRE.INTL
YEAR = ANNEE
YEARFRAC = FRACTION.ANNEE
##
## Fonctions d’ingénierie (Engineering Functions)
##
BESSELI = BESSELI
BESSELJ = BESSELJ
BESSELK = BESSELK
BESSELY = BESSELY
BIN2DEC = BINDEC
BIN2HEX = BINHEX
BIN2OCT = BINOCT
BITAND = BITET
BITLSHIFT = BITDECALG
BITOR = BITOU
BITRSHIFT = BITDECALD
BITXOR = BITOUEXCLUSIF
COMPLEX = COMPLEXE
CONVERT = CONVERT
DEC2BIN = DECBIN
DEC2HEX = DECHEX
DEC2OCT = DECOCT
DELTA = DELTA
ERF = ERF
ERF.PRECISE = ERF.PRECIS
ERFC = ERFC
ERFC.PRECISE = ERFC.PRECIS
GESTEP = SUP.SEUIL
HEX2BIN = HEXBIN
HEX2DEC = HEXDEC
HEX2OCT = HEXOCT
IMABS = COMPLEXE.MODULE
IMAGINARY = COMPLEXE.IMAGINAIRE
IMARGUMENT = COMPLEXE.ARGUMENT
IMCONJUGATE = COMPLEXE.CONJUGUE
IMCOS = COMPLEXE.COS
IMCOSH = COMPLEXE.COSH
IMCOT = COMPLEXE.COT
IMCSC = COMPLEXE.CSC
IMCSCH = COMPLEXE.CSCH
IMDIV = COMPLEXE.DIV
IMEXP = COMPLEXE.EXP
IMLN = COMPLEXE.LN
IMLOG10 = COMPLEXE.LOG10
IMLOG2 = COMPLEXE.LOG2
IMPOWER = COMPLEXE.PUISSANCE
IMPRODUCT = COMPLEXE.PRODUIT
IMREAL = COMPLEXE.REEL
IMSEC = COMPLEXE.SEC
IMSECH = COMPLEXE.SECH
IMSIN = COMPLEXE.SIN
IMSINH = COMPLEXE.SINH
IMSQRT = COMPLEXE.RACINE
IMSUB = COMPLEXE.DIFFERENCE
IMSUM = COMPLEXE.SOMME
IMTAN = COMPLEXE.TAN
OCT2BIN = OCTBIN
OCT2DEC = OCTDEC
OCT2HEX = OCTHEX
##
## Fonctions financières (Financial Functions)
##
ACCRINT = INTERET.ACC
ACCRINTM = INTERET.ACC.MAT
AMORDEGRC = AMORDEGRC
AMORLINC = AMORLINC
COUPDAYBS = NB.JOURS.COUPON.PREC
COUPDAYS = NB.JOURS.COUPONS
COUPDAYSNC = NB.JOURS.COUPON.SUIV
COUPNCD = DATE.COUPON.SUIV
COUPNUM = NB.COUPONS
COUPPCD = DATE.COUPON.PREC
CUMIPMT = CUMUL.INTER
CUMPRINC = CUMUL.PRINCPER
DB = DB
DDB = DDB
DISC = TAUX.ESCOMPTE
DOLLARDE = PRIX.DEC
DOLLARFR = PRIX.FRAC
DURATION = DUREE
EFFECT = TAUX.EFFECTIF
FV = VC
FVSCHEDULE = VC.PAIEMENTS
INTRATE = TAUX.INTERET
IPMT = INTPER
IRR = TRI
ISPMT = ISPMT
MDURATION = DUREE.MODIFIEE
MIRR = TRIM
NOMINAL = TAUX.NOMINAL
NPER = NPM
NPV = VAN
ODDFPRICE = PRIX.PCOUPON.IRREG
ODDFYIELD = REND.PCOUPON.IRREG
ODDLPRICE = PRIX.DCOUPON.IRREG
ODDLYIELD = REND.DCOUPON.IRREG
PDURATION = PDUREE
PMT = VPM
PPMT = PRINCPER
PRICE = PRIX.TITRE
PRICEDISC = VALEUR.ENCAISSEMENT
PRICEMAT = PRIX.TITRE.ECHEANCE
PV = VA
RATE = TAUX
RECEIVED = VALEUR.NOMINALE
RRI = TAUX.INT.EQUIV
SLN = AMORLIN
SYD = SYD
TBILLEQ = TAUX.ESCOMPTE.R
TBILLPRICE = PRIX.BON.TRESOR
TBILLYIELD = RENDEMENT.BON.TRESOR
VDB = VDB
XIRR = TRI.PAIEMENTS
XNPV = VAN.PAIEMENTS
YIELD = RENDEMENT.TITRE
YIELDDISC = RENDEMENT.SIMPLE
YIELDMAT = RENDEMENT.TITRE.ECHEANCE
##
## Fonctions d’information (Information Functions)
##
CELL = CELLULE
ERROR.TYPE = TYPE.ERREUR
INFO = INFORMATIONS
ISBLANK = ESTVIDE
ISERR = ESTERR
ISERROR = ESTERREUR
ISEVEN = EST.PAIR
ISFORMULA = ESTFORMULE
ISLOGICAL = ESTLOGIQUE
ISNA = ESTNA
ISNONTEXT = ESTNONTEXTE
ISNUMBER = ESTNUM
ISODD = EST.IMPAIR
ISREF = ESTREF
ISTEXT = ESTTEXTE
N = N
NA = NA
SHEET = FEUILLE
SHEETS = FEUILLES
TYPE = TYPE
##
## Fonctions logiques (Logical Functions)
##
AND = ET
FALSE = FAUX
IF = SI
IFERROR = SIERREUR
IFNA = SI.NON.DISP
IFS = SI.CONDITIONS
NOT = NON
OR = OU
SWITCH = SI.MULTIPLE
TRUE = VRAI
XOR = OUX
##
## Fonctions de recherche et de référence (Lookup & Reference Functions)
##
ADDRESS = ADRESSE
AREAS = ZONES
CHOOSE = CHOISIR
COLUMN = COLONNE
COLUMNS = COLONNES
FORMULATEXT = FORMULETEXTE
GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE
HLOOKUP = RECHERCHEH
HYPERLINK = LIEN_HYPERTEXTE
INDEX = INDEX
INDIRECT = INDIRECT
LOOKUP = RECHERCHE
MATCH = EQUIV
OFFSET = DECALER
ROW = LIGNE
ROWS = LIGNES
RTD = RTD
TRANSPOSE = TRANSPOSE
VLOOKUP = RECHERCHEV
*RC = LC
##
## Fonctions mathématiques et trigonométriques (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = AGREGAT
ARABIC = CHIFFRE.ARABE
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = BASE
CEILING.MATH = PLAFOND.MATH
CEILING.PRECISE = PLAFOND.PRECIS
COMBIN = COMBIN
COMBINA = COMBINA
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = DECIMAL
DEGREES = DEGRES
ECMA.CEILING = ECMA.PLAFOND
EVEN = PAIR
EXP = EXP
FACT = FACT
FACTDOUBLE = FACTDOUBLE
FLOOR.MATH = PLANCHER.MATH
FLOOR.PRECISE = PLANCHER.PRECIS
GCD = PGCD
INT = ENT
ISO.CEILING = ISO.PLAFOND
LCM = PPCM
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = DETERMAT
MINVERSE = INVERSEMAT
MMULT = PRODUITMAT
MOD = MOD
MROUND = ARRONDI.AU.MULTIPLE
MULTINOMIAL = MULTINOMIALE
MUNIT = MATRICE.UNITAIRE
ODD = IMPAIR
PI = PI
POWER = PUISSANCE
PRODUCT = PRODUIT
QUOTIENT = QUOTIENT
RADIANS = RADIANS
RAND = ALEA
RANDBETWEEN = ALEA.ENTRE.BORNES
ROMAN = ROMAIN
ROUND = ARRONDI
ROUNDDOWN = ARRONDI.INF
ROUNDUP = ARRONDI.SUP
SEC = SEC
SECH = SECH
SERIESSUM = SOMME.SERIES
SIGN = SIGNE
SIN = SIN
SINH = SINH
SQRT = RACINE
SQRTPI = RACINE.PI
SUBTOTAL = SOUS.TOTAL
SUM = SOMME
SUMIF = SOMME.SI
SUMIFS = SOMME.SI.ENS
SUMPRODUCT = SOMMEPROD
SUMSQ = SOMME.CARRES
SUMX2MY2 = SOMME.X2MY2
SUMX2PY2 = SOMME.X2PY2
SUMXMY2 = SOMME.XMY2
TAN = TAN
TANH = TANH
TRUNC = TRONQUE
##
## Fonctions statistiques (Statistical Functions)
##
AVEDEV = ECART.MOYEN
AVERAGE = MOYENNE
AVERAGEA = AVERAGEA
AVERAGEIF = MOYENNE.SI
AVERAGEIFS = MOYENNE.SI.ENS
BETA.DIST = LOI.BETA.N
BETA.INV = BETA.INVERSE.N
BINOM.DIST = LOI.BINOMIALE.N
BINOM.DIST.RANGE = LOI.BINOMIALE.SERIE
BINOM.INV = LOI.BINOMIALE.INVERSE
CHISQ.DIST = LOI.KHIDEUX.N
CHISQ.DIST.RT = LOI.KHIDEUX.DROITE
CHISQ.INV = LOI.KHIDEUX.INVERSE
CHISQ.INV.RT = LOI.KHIDEUX.INVERSE.DROITE
CHISQ.TEST = CHISQ.TEST
CONFIDENCE.NORM = INTERVALLE.CONFIANCE.NORMAL
CONFIDENCE.T = INTERVALLE.CONFIANCE.STUDENT
CORREL = COEFFICIENT.CORRELATION
COUNT = NB
COUNTA = NBVAL
COUNTBLANK = NB.VIDE
COUNTIF = NB.SI
COUNTIFS = NB.SI.ENS
COVARIANCE.P = COVARIANCE.PEARSON
COVARIANCE.S = COVARIANCE.STANDARD
DEVSQ = SOMME.CARRES.ECARTS
EXPON.DIST = LOI.EXPONENTIELLE.N
F.DIST = LOI.F.N
F.DIST.RT = LOI.F.DROITE
F.INV = INVERSE.LOI.F.N
F.INV.RT = INVERSE.LOI.F.DROITE
F.TEST = F.TEST
FISHER = FISHER
FISHERINV = FISHER.INVERSE
FORECAST.ETS = PREVISION.ETS
FORECAST.ETS.CONFINT = PREVISION.ETS.CONFINT
FORECAST.ETS.SEASONALITY = PREVISION.ETS.CARACTERESAISONNIER
FORECAST.ETS.STAT = PREVISION.ETS.STAT
FORECAST.LINEAR = PREVISION.LINEAIRE
FREQUENCY = FREQUENCE
GAMMA = GAMMA
GAMMA.DIST = LOI.GAMMA.N
GAMMA.INV = LOI.GAMMA.INVERSE.N
GAMMALN = LNGAMMA
GAMMALN.PRECISE = LNGAMMA.PRECIS
GAUSS = GAUSS
GEOMEAN = MOYENNE.GEOMETRIQUE
GROWTH = CROISSANCE
HARMEAN = MOYENNE.HARMONIQUE
HYPGEOM.DIST = LOI.HYPERGEOMETRIQUE.N
INTERCEPT = ORDONNEE.ORIGINE
KURT = KURTOSIS
LARGE = GRANDE.VALEUR
LINEST = DROITEREG
LOGEST = LOGREG
LOGNORM.DIST = LOI.LOGNORMALE.N
LOGNORM.INV = LOI.LOGNORMALE.INVERSE.N
MAX = MAX
MAXA = MAXA
MAXIFS = MAX.SI
MEDIAN = MEDIANE
MIN = MIN
MINA = MINA
MINIFS = MIN.SI
MODE.MULT = MODE.MULTIPLE
MODE.SNGL = MODE.SIMPLE
NEGBINOM.DIST = LOI.BINOMIALE.NEG.N
NORM.DIST = LOI.NORMALE.N
NORM.INV = LOI.NORMALE.INVERSE.N
NORM.S.DIST = LOI.NORMALE.STANDARD.N
NORM.S.INV = LOI.NORMALE.STANDARD.INVERSE.N
PEARSON = PEARSON
PERCENTILE.EXC = CENTILE.EXCLURE
PERCENTILE.INC = CENTILE.INCLURE
PERCENTRANK.EXC = RANG.POURCENTAGE.EXCLURE
PERCENTRANK.INC = RANG.POURCENTAGE.INCLURE
PERMUT = PERMUTATION
PERMUTATIONA = PERMUTATIONA
PHI = PHI
POISSON.DIST = LOI.POISSON.N
PROB = PROBABILITE
QUARTILE.EXC = QUARTILE.EXCLURE
QUARTILE.INC = QUARTILE.INCLURE
RANK.AVG = MOYENNE.RANG
RANK.EQ = EQUATION.RANG
RSQ = COEFFICIENT.DETERMINATION
SKEW = COEFFICIENT.ASYMETRIE
SKEW.P = COEFFICIENT.ASYMETRIE.P
SLOPE = PENTE
SMALL = PETITE.VALEUR
STANDARDIZE = CENTREE.REDUITE
STDEV.P = ECARTYPE.PEARSON
STDEV.S = ECARTYPE.STANDARD
STDEVA = STDEVA
STDEVPA = STDEVPA
STEYX = ERREUR.TYPE.XY
T.DIST = LOI.STUDENT.N
T.DIST.2T = LOI.STUDENT.BILATERALE
T.DIST.RT = LOI.STUDENT.DROITE
T.INV = LOI.STUDENT.INVERSE.N
T.INV.2T = LOI.STUDENT.INVERSE.BILATERALE
T.TEST = T.TEST
TREND = TENDANCE
TRIMMEAN = MOYENNE.REDUITE
VAR.P = VAR.P.N
VAR.S = VAR.S
VARA = VARA
VARPA = VARPA
WEIBULL.DIST = LOI.WEIBULL.N
Z.TEST = Z.TEST
##
## Fonctions de texte (Text Functions)
##
BAHTTEXT = BAHTTEXT
CHAR = CAR
CLEAN = EPURAGE
CODE = CODE
CONCAT = CONCAT
DOLLAR = DEVISE
EXACT = EXACT
FIND = TROUVE
FIXED = CTXT
LEFT = GAUCHE
LEN = NBCAR
LOWER = MINUSCULE
MID = STXT
NUMBERVALUE = VALEURNOMBRE
PHONETIC = PHONETIQUE
PROPER = NOMPROPRE
REPLACE = REMPLACER
REPT = REPT
RIGHT = DROITE
SEARCH = CHERCHE
SUBSTITUTE = SUBSTITUE
T = T
TEXT = TEXTE
TEXTJOIN = JOINDRE.TEXTE
TRIM = SUPPRESPACE
UNICHAR = UNICAR
UNICODE = UNICODE
UPPER = MAJUSCULE
VALUE = CNUM
##
## Fonctions web (Web Functions)
##
ENCODEURL = URLENCODAGE
FILTERXML = FILTRE.XML
WEBSERVICE = SERVICEWEB
##
## Fonctions de compatibilité (Compatibility Functions)
##
BETADIST = LOI.BETA
BETAINV = BETA.INVERSE
BINOMDIST = LOI.BINOMIALE
CEILING = PLAFOND
CHIDIST = LOI.KHIDEUX
CHIINV = KHIDEUX.INVERSE
CHITEST = TEST.KHIDEUX
CONCATENATE = CONCATENER
CONFIDENCE = INTERVALLE.CONFIANCE
COVAR = COVARIANCE
CRITBINOM = CRITERE.LOI.BINOMIALE
EXPONDIST = LOI.EXPONENTIELLE
FDIST = LOI.F
FINV = INVERSE.LOI.F
FLOOR = PLANCHER
FORECAST = PREVISION
FTEST = TEST.F
GAMMADIST = LOI.GAMMA
GAMMAINV = LOI.GAMMA.INVERSE
HYPGEOMDIST = LOI.HYPERGEOMETRIQUE
LOGINV = LOI.LOGNORMALE.INVERSE
LOGNORMDIST = LOI.LOGNORMALE
MODE = MODE
NEGBINOMDIST = LOI.BINOMIALE.NEG
NORMDIST = LOI.NORMALE
NORMINV = LOI.NORMALE.INVERSE
NORMSDIST = LOI.NORMALE.STANDARD
NORMSINV = LOI.NORMALE.STANDARD.INVERSE
PERCENTILE = CENTILE
PERCENTRANK = RANG.POURCENTAGE
POISSON = LOI.POISSON
QUARTILE = QUARTILE
RANK = RANG
STDEV = ECARTYPE
STDEVP = ECARTYPEP
TDIST = LOI.STUDENT
TINV = LOI.STUDENT.INVERSE
TTEST = TEST.STUDENT
VAR = VAR
VARP = VAR.P
WEIBULL = LOI.WEIBULL
ZTEST = TEST.Z
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config 0000644 00000000566 15243417764 0020630 0 ustar 00 ############################################################
##
## PhpSpreadsheet - locale settings
##
## русский язык (Russian)
##
############################################################
ArgumentSeparator = ;
##
## Error Codes
##
NULL = #ПУСТО!
DIV0 = #ДЕЛ/0!
VALUE = #ЗНАЧ!
REF = #ССЫЛКА!
NAME = #ИМЯ?
NUM = #ЧИСЛО!
NA = #Н/Д
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions 0000644 00000033315 15243417764 0021371 0 ustar 00 ############################################################
##
## PhpSpreadsheet - function name translations
##
## русский язык (Russian)
##
############################################################
##
## Функции кубов (Cube Functions)
##
CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП
CUBEMEMBER = КУБЭЛЕМЕНТ
CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА
CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ
CUBESET = КУБМНОЖ
CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ
CUBEVALUE = КУБЗНАЧЕНИЕ
##
## Функции для работы с базами данных (Database Functions)
##
DAVERAGE = ДСРЗНАЧ
DCOUNT = БСЧЁТ
DCOUNTA = БСЧЁТА
DGET = БИЗВЛЕЧЬ
DMAX = ДМАКС
DMIN = ДМИН
DPRODUCT = БДПРОИЗВЕД
DSTDEV = ДСТАНДОТКЛ
DSTDEVP = ДСТАНДОТКЛП
DSUM = БДСУММ
DVAR = БДДИСП
DVARP = БДДИСПП
##
## Функции даты и времени (Date & Time Functions)
##
DATE = ДАТА
DATEDIF = РАЗНДАТ
DATESTRING = СТРОКАДАННЫХ
DATEVALUE = ДАТАЗНАЧ
DAY = ДЕНЬ
DAYS = ДНИ
DAYS360 = ДНЕЙ360
EDATE = ДАТАМЕС
EOMONTH = КОНМЕСЯЦА
HOUR = ЧАС
ISOWEEKNUM = НОМНЕДЕЛИ.ISO
MINUTE = МИНУТЫ
MONTH = МЕСЯЦ
NETWORKDAYS = ЧИСТРАБДНИ
NETWORKDAYS.INTL = ЧИСТРАБДНИ.МЕЖД
NOW = ТДАТА
SECOND = СЕКУНДЫ
THAIDAYOFWEEK = ТАЙДЕНЬНЕД
THAIMONTHOFYEAR = ТАЙМЕСЯЦ
THAIYEAR = ТАЙГОД
TIME = ВРЕМЯ
TIMEVALUE = ВРЕМЗНАЧ
TODAY = СЕГОДНЯ
WEEKDAY = ДЕНЬНЕД
WEEKNUM = НОМНЕДЕЛИ
WORKDAY = РАБДЕНЬ
WORKDAY.INTL = РАБДЕНЬ.МЕЖД
YEAR = ГОД
YEARFRAC = ДОЛЯГОДА
##
## Инженерные функции (Engineering Functions)
##
BESSELI = БЕССЕЛЬ.I
BESSELJ = БЕССЕЛЬ.J
BESSELK = БЕССЕЛЬ.K
BESSELY = БЕССЕЛЬ.Y
BIN2DEC = ДВ.В.ДЕС
BIN2HEX = ДВ.В.ШЕСТН
BIN2OCT = ДВ.В.ВОСЬМ
BITAND = БИТ.И
BITLSHIFT = БИТ.СДВИГЛ
BITOR = БИТ.ИЛИ
BITRSHIFT = БИТ.СДВИГП
BITXOR = БИТ.ИСКЛИЛИ
COMPLEX = КОМПЛЕКСН
CONVERT = ПРЕОБР
DEC2BIN = ДЕС.В.ДВ
DEC2HEX = ДЕС.В.ШЕСТН
DEC2OCT = ДЕС.В.ВОСЬМ
DELTA = ДЕЛЬТА
ERF = ФОШ
ERF.PRECISE = ФОШ.ТОЧН
ERFC = ДФОШ
ERFC.PRECISE = ДФОШ.ТОЧН
GESTEP = ПОРОГ
HEX2BIN = ШЕСТН.В.ДВ
HEX2DEC = ШЕСТН.В.ДЕС
HEX2OCT = ШЕСТН.В.ВОСЬМ
IMABS = МНИМ.ABS
IMAGINARY = МНИМ.ЧАСТЬ
IMARGUMENT = МНИМ.АРГУМЕНТ
IMCONJUGATE = МНИМ.СОПРЯЖ
IMCOS = МНИМ.COS
IMCOSH = МНИМ.COSH
IMCOT = МНИМ.COT
IMCSC = МНИМ.CSC
IMCSCH = МНИМ.CSCH
IMDIV = МНИМ.ДЕЛ
IMEXP = МНИМ.EXP
IMLN = МНИМ.LN
IMLOG10 = МНИМ.LOG10
IMLOG2 = МНИМ.LOG2
IMPOWER = МНИМ.СТЕПЕНЬ
IMPRODUCT = МНИМ.ПРОИЗВЕД
IMREAL = МНИМ.ВЕЩ
IMSEC = МНИМ.SEC
IMSECH = МНИМ.SECH
IMSIN = МНИМ.SIN
IMSINH = МНИМ.SINH
IMSQRT = МНИМ.КОРЕНЬ
IMSUB = МНИМ.РАЗН
IMSUM = МНИМ.СУММ
IMTAN = МНИМ.TAN
OCT2BIN = ВОСЬМ.В.ДВ
OCT2DEC = ВОСЬМ.В.ДЕС
OCT2HEX = ВОСЬМ.В.ШЕСТН
##
## Финансовые функции (Financial Functions)
##
ACCRINT = НАКОПДОХОД
ACCRINTM = НАКОПДОХОДПОГАШ
AMORDEGRC = АМОРУМ
AMORLINC = АМОРУВ
COUPDAYBS = ДНЕЙКУПОНДО
COUPDAYS = ДНЕЙКУПОН
COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ
COUPNCD = ДАТАКУПОНПОСЛЕ
COUPNUM = ЧИСЛКУПОН
COUPPCD = ДАТАКУПОНДО
CUMIPMT = ОБЩПЛАТ
CUMPRINC = ОБЩДОХОД
DB = ФУО
DDB = ДДОБ
DISC = СКИДКА
DOLLARDE = РУБЛЬ.ДЕС
DOLLARFR = РУБЛЬ.ДРОБЬ
DURATION = ДЛИТ
EFFECT = ЭФФЕКТ
FV = БС
FVSCHEDULE = БЗРАСПИС
INTRATE = ИНОРМА
IPMT = ПРПЛТ
IRR = ВСД
ISPMT = ПРОЦПЛАТ
MDURATION = МДЛИТ
MIRR = МВСД
NOMINAL = НОМИНАЛ
NPER = КПЕР
NPV = ЧПС
ODDFPRICE = ЦЕНАПЕРВНЕРЕГ
ODDFYIELD = ДОХОДПЕРВНЕРЕГ
ODDLPRICE = ЦЕНАПОСЛНЕРЕГ
ODDLYIELD = ДОХОДПОСЛНЕРЕГ
PDURATION = ПДЛИТ
PMT = ПЛТ
PPMT = ОСПЛТ
PRICE = ЦЕНА
PRICEDISC = ЦЕНАСКИДКА
PRICEMAT = ЦЕНАПОГАШ
PV = ПС
RATE = СТАВКА
RECEIVED = ПОЛУЧЕНО
RRI = ЭКВ.СТАВКА
SLN = АПЛ
SYD = АСЧ
TBILLEQ = РАВНОКЧЕК
TBILLPRICE = ЦЕНАКЧЕК
TBILLYIELD = ДОХОДКЧЕК
USDOLLAR = ДОЛЛСША
VDB = ПУО
XIRR = ЧИСТВНДОХ
XNPV = ЧИСТНЗ
YIELD = ДОХОД
YIELDDISC = ДОХОДСКИДКА
YIELDMAT = ДОХОДПОГАШ
##
## Информационные функции (Information Functions)
##
CELL = ЯЧЕЙКА
ERROR.TYPE = ТИП.ОШИБКИ
INFO = ИНФОРМ
ISBLANK = ЕПУСТО
ISERR = ЕОШ
ISERROR = ЕОШИБКА
ISEVEN = ЕЧЁТН
ISFORMULA = ЕФОРМУЛА
ISLOGICAL = ЕЛОГИЧ
ISNA = ЕНД
ISNONTEXT = ЕНЕТЕКСТ
ISNUMBER = ЕЧИСЛО
ISODD = ЕНЕЧЁТ
ISREF = ЕССЫЛКА
ISTEXT = ЕТЕКСТ
N = Ч
NA = НД
SHEET = ЛИСТ
SHEETS = ЛИСТЫ
TYPE = ТИП
##
## Логические функции (Logical Functions)
##
AND = И
FALSE = ЛОЖЬ
IF = ЕСЛИ
IFERROR = ЕСЛИОШИБКА
IFNA = ЕСНД
IFS = УСЛОВИЯ
NOT = НЕ
OR = ИЛИ
SWITCH = ПЕРЕКЛЮЧ
TRUE = ИСТИНА
XOR = ИСКЛИЛИ
##
## Функции ссылки и поиска (Lookup & Reference Functions)
##
ADDRESS = АДРЕС
AREAS = ОБЛАСТИ
CHOOSE = ВЫБОР
COLUMN = СТОЛБЕЦ
COLUMNS = ЧИСЛСТОЛБ
FILTER = ФИЛЬТР
FORMULATEXT = Ф.ТЕКСТ
GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ
HLOOKUP = ГПР
HYPERLINK = ГИПЕРССЫЛКА
INDEX = ИНДЕКС
INDIRECT = ДВССЫЛ
LOOKUP = ПРОСМОТР
MATCH = ПОИСКПОЗ
OFFSET = СМЕЩ
ROW = СТРОКА
ROWS = ЧСТРОК
RTD = ДРВ
SORT = СОРТ
SORTBY = СОРТПО
TRANSPOSE = ТРАНСП
UNIQUE = УНИК
VLOOKUP = ВПР
XLOOKUP = ПРОСМОТРX
XMATCH = ПОИСКПОЗX
##
## Математические и тригонометрические функции (Math & Trig Functions)
##
ABS = ABS
ACOS = ACOS
ACOSH = ACOSH
ACOT = ACOT
ACOTH = ACOTH
AGGREGATE = АГРЕГАТ
ARABIC = АРАБСКОЕ
ASIN = ASIN
ASINH = ASINH
ATAN = ATAN
ATAN2 = ATAN2
ATANH = ATANH
BASE = ОСНОВАНИЕ
CEILING.MATH = ОКРВВЕРХ.МАТ
CEILING.PRECISE = ОКРВВЕРХ.ТОЧН
COMBIN = ЧИСЛКОМБ
COMBINA = ЧИСЛКОМБА
COS = COS
COSH = COSH
COT = COT
COTH = COTH
CSC = CSC
CSCH = CSCH
DECIMAL = ДЕС
DEGREES = ГРАДУСЫ
ECMA.CEILING = ECMA.ОКРВВЕРХ
EVEN = ЧЁТН
EXP = EXP
FACT = ФАКТР
FACTDOUBLE = ДВФАКТР
FLOOR.MATH = ОКРВНИЗ.МАТ
FLOOR.PRECISE = ОКРВНИЗ.ТОЧН
GCD = НОД
INT = ЦЕЛОЕ
ISO.CEILING = ISO.ОКРВВЕРХ
LCM = НОК
LN = LN
LOG = LOG
LOG10 = LOG10
MDETERM = МОПРЕД
MINVERSE = МОБР
MMULT = МУМНОЖ
MOD = ОСТАТ
MROUND = ОКРУГЛТ
MULTINOMIAL = МУЛЬТИНОМ
MUNIT = МЕДИН
ODD = НЕЧЁТ
PI = ПИ
POWER = СТЕПЕНЬ
PRODUCT = ПРОИЗВЕД
QUOTIENT = ЧАСТНОЕ
RADIANS = РАДИАНЫ
RAND = СЛЧИС
RANDARRAY = СЛУЧМАССИВ
RANDBETWEEN = СЛУЧМЕЖДУ
ROMAN = РИМСКОЕ
ROUND = ОКРУГЛ
ROUNDBAHTDOWN = ОКРУГЛБАТВНИЗ
ROUNDBAHTUP = ОКРУГЛБАТВВЕРХ
ROUNDDOWN = ОКРУГЛВНИЗ
ROUNDUP = ОКРУГЛВВЕРХ
SEC = SEC
SECH = SECH
SERIESSUM = РЯД.СУММ
SEQUENCE = ПОСЛЕДОВ
SIGN = ЗНАК
SIN = SIN
SINH = SINH
SQRT = КОРЕНЬ
SQRTPI = КОРЕНЬПИ
SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ
SUM = СУММ
SUMIF = СУММЕСЛИ
SUMIFS = СУММЕСЛИМН
SUMPRODUCT = СУММПРОИЗВ
SUMSQ = СУММКВ
SUMX2MY2 = СУММРАЗНКВ
SUMX2PY2 = СУММСУММКВ
SUMXMY2 = СУММКВРАЗН
TAN = TAN
TANH = TANH
TRUNC = ОТБР
##
## Статистические функции (Statistical Functions)
##
AVEDEV = СРОТКЛ
AVERAGE = СРЗНАЧ
AVERAGEA = СРЗНАЧА
AVERAGEIF = СРЗНАЧЕСЛИ
AVERAGEIFS = СРЗНАЧЕСЛИМН
BETA.DIST = БЕТА.РАСП
BETA.INV = БЕТА.ОБР
BINOM.DIST = БИНОМ.РАСП
BINOM.DIST.RANGE = БИНОМ.РАСП.ДИАП
BINOM.INV = БИНОМ.ОБР
CHISQ.DIST = ХИ2.РАСП
CHISQ.DIST.RT = ХИ2.РАСП.ПХ
CHISQ.INV = ХИ2.ОБР
CHISQ.INV.RT = ХИ2.ОБР.ПХ
CHISQ.TEST = ХИ2.ТЕСТ
CONFIDENCE.NORM = ДОВЕРИТ.НОРМ
CONFIDENCE.T = ДОВЕРИТ.СТЬЮДЕНТ
CORREL = КОРРЕЛ
COUNT = СЧЁТ
COUNTA = СЧЁТЗ
COUNTBLANK = СЧИТАТЬПУСТОТЫ
COUNTIF = СЧЁТЕСЛИ
COUNTIFS = СЧЁТЕСЛИМН
COVARIANCE.P = КОВАРИАЦИЯ.Г
COVARIANCE.S = КОВАРИАЦИЯ.В
DEVSQ = КВАДРОТКЛ
EXPON.DIST = ЭКСП.РАСП
F.DIST = F.РАСП
F.DIST.RT = F.РАСП.ПХ
F.INV = F.ОБР
F.INV.RT = F.ОБР.ПХ
F.TEST = F.ТЕСТ
FISHER = ФИШЕР
FISHERINV = ФИШЕРОБР
FORECAST.ETS = ПРЕДСКАЗ.ETS
FORECAST.ETS.CONFINT = ПРЕДСКАЗ.ЕTS.ДОВИНТЕРВАЛ
FORECAST.ETS.SEASONALITY = ПРЕДСКАЗ.ETS.СЕЗОННОСТЬ
FORECAST.ETS.STAT = ПРЕДСКАЗ.ETS.СТАТ
FORECAST.LINEAR = ПРЕДСКАЗ.ЛИНЕЙН
FREQUENCY = ЧАСТОТА
GAMMA = ГАММА
GAMMA.DIST = ГАММА.РАСП
GAMMA.INV = ГАММА.ОБР
GAMMALN = ГАММАНЛОГ
GAMMALN.PRECISE = ГАММАНЛОГ.ТОЧН
GAUSS = ГАУСС
GEOMEAN = СРГЕОМ
GROWTH = РОСТ
HARMEAN = СРГАРМ
HYPGEOM.DIST = ГИПЕРГЕОМ.РАСП
INTERCEPT = ОТРЕЗОК
KURT = ЭКСЦЕСС
LARGE = НАИБОЛЬШИЙ
LINEST = ЛИНЕЙН
LOGEST = ЛГРФПРИБЛ
LOGNORM.DIST = ЛОГНОРМ.РАСП
LOGNORM.INV = ЛОГНОРМ.ОБР
MAX = МАКС
MAXA = МАКСА
MAXIFS = МАКСЕСЛИ
MEDIAN = МЕДИАНА
MIN = МИН
MINA = МИНА
MINIFS = МИНЕСЛИ
MODE.MULT = МОДА.НСК
MODE.SNGL = МОДА.ОДН
NEGBINOM.DIST = ОТРБИНОМ.РАСП
NORM.DIST = НОРМ.РАСП
NORM.INV = НОРМ.ОБР
NORM.S.DIST = НОРМ.СТ.РАСП
NORM.S.INV = НОРМ.СТ.ОБР
PEARSON = PEARSON
PERCENTILE.EXC = ПРОЦЕНТИЛЬ.ИСКЛ
PERCENTILE.INC = ПРОЦЕНТИЛЬ.ВКЛ
PERCENTRANK.EXC = ПРОЦЕНТРАНГ.ИСКЛ
PERCENTRANK.INC = ПРОЦЕНТРАНГ.ВКЛ
PERMUT = ПЕРЕСТ
PERMUTATIONA = ПЕРЕСТА
PHI = ФИ
POISSON.DIST = ПУАССОН.РАСП
PROB = ВЕРОЯТНОСТЬ
QUARTILE.EXC = КВАРТИЛЬ.ИСКЛ
QUARTILE.INC = КВАРТИЛЬ.ВКЛ
RANK.AVG = РАНГ.СР
RANK.EQ = РАНГ.РВ
RSQ = КВПИРСОН
SKEW = СКОС
SKEW.P = СКОС.Г
SLOPE = НАКЛОН
SMALL = НАИМЕНЬШИЙ
STANDARDIZE = НОРМАЛИЗАЦИЯ
STDEV.P = СТАНДОТКЛОН.Г
STDEV.S = СТАНДОТКЛОН.В
STDEVA = СТАНДОТКЛОНА
STDEVPA = СТАНДОТКЛОНПА
STEYX = СТОШYX
T.DIST = СТЬЮДЕНТ.РАСП
T.DIST.2T = СТЬЮДЕНТ.РАСП.2Х
T.DIST.RT = СТЬЮДЕНТ.РАСП.ПХ
T.INV = СТЬЮДЕНТ.ОБР
T.INV.2T = СТЬЮДЕНТ.ОБР.2Х
T.TEST = СТЬЮДЕНТ.ТЕСТ
TREND = ТЕНДЕНЦИЯ
TRIMMEAN = УРЕЗСРЕДНЕЕ
VAR.P = ДИСП.Г
VAR.S = ДИСП.В
VARA = ДИСПА
VARPA = ДИСПРА
WEIBULL.DIST = ВЕЙБУЛЛ.РАСП
Z.TEST = Z.ТЕСТ
##
## Текстовые функции (Text Functions)
##
ARRAYTOTEXT = МАССИВВТЕКСТ
BAHTTEXT = БАТТЕКСТ
CHAR = СИМВОЛ
CLEAN = ПЕЧСИМВ
CODE = КОДСИМВ
CONCAT = СЦЕП
DBCS = БДЦС
DOLLAR = РУБЛЬ
EXACT = СОВПАД
FIND = НАЙТИ
FINDB = НАЙТИБ
FIXED = ФИКСИРОВАННЫЙ
ISTHAIDIGIT = ЕТАЙЦИФРЫ
LEFT = ЛЕВСИМВ
LEFTB = ЛЕВБ
LEN = ДЛСТР
LENB = ДЛИНБ
LOWER = СТРОЧН
MID = ПСТР
MIDB = ПСТРБ
NUMBERSTRING = СТРОКАЧИСЕЛ
NUMBERVALUE = ЧЗНАЧ
PROPER = ПРОПНАЧ
REPLACE = ЗАМЕНИТЬ
REPLACEB = ЗАМЕНИТЬБ
REPT = ПОВТОР
RIGHT = ПРАВСИМВ
RIGHTB = ПРАВБ
SEARCH = ПОИСК
SEARCHB = ПОИСКБ
SUBSTITUTE = ПОДСТАВИТЬ
T = Т
TEXT = ТЕКСТ
TEXTJOIN = ОБЪЕДИНИТЬ
THAIDIGIT = ТАЙЦИФРА
THAINUMSOUND = ТАЙЧИСЛОВЗВУК
THAINUMSTRING = ТАЙЧИСЛОВСТРОКУ
THAISTRINGLENGTH = ТАЙДЛИНАСТРОКИ
TRIM = СЖПРОБЕЛЫ
UNICHAR = ЮНИСИМВ
UNICODE = UNICODE
UPPER = ПРОПИСН
VALUE = ЗНАЧЕН
VALUETOTEXT = ЗНАЧЕНИЕВТЕКСТ
##
## Веб-функции (Web Functions)
##
ENCODEURL = КОДИР.URL
FILTERXML = ФИЛЬТР.XML
WEBSERVICE = ВЕБСЛУЖБА
##
## Функции совместимости (Compatibility Functions)
##
BETADIST = БЕТАРАСП
BETAINV = БЕТАОБР
BINOMDIST = БИНОМРАСП
CEILING = ОКРВВЕРХ
CHIDIST = ХИ2РАСП
CHIINV = ХИ2ОБР
CHITEST = ХИ2ТЕСТ
CONCATENATE = СЦЕПИТЬ
CONFIDENCE = ДОВЕРИТ
COVAR = КОВАР
CRITBINOM = КРИТБИНОМ
EXPONDIST = ЭКСПРАСП
FDIST = FРАСП
FINV = FРАСПОБР
FLOOR = ОКРВНИЗ
FORECAST = ПРЕДСКАЗ
FTEST = ФТЕСТ
GAMMADIST = ГАММАРАСП
GAMMAINV = ГАММАОБР
HYPGEOMDIST = ГИПЕРГЕОМЕТ
LOGINV = ЛОГНОРМОБР
LOGNORMDIST = ЛОГНОРМРАСП
MODE = МОДА
NEGBINOMDIST = ОТРБИНОМРАСП
NORMDIST = НОРМРАСП
NORMINV = НОРМОБР
NORMSDIST = НОРМСТРАСП
NORMSINV = НОРМСТОБР
PERCENTILE = ПЕРСЕНТИЛЬ
PERCENTRANK = ПРОЦЕНТРАНГ
POISSON = ПУАССОН
QUARTILE = КВАРТИЛЬ
RANK = РАНГ
STDEV = СТАНДОТКЛОН
STDEVP = СТАНДОТКЛОНП
TDIST = СТЬЮДРАСП
TINV = СТЬЮДРАСПОБР
TTEST = ТТЕСТ
VAR = ДИСП
VARP = ДИСПР
WEIBULL = ВЕЙБУЛЛ
ZTEST = ZТЕСТ
phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx 0000644 00000324313 15243417764 0022412 0 ustar 00 PK ! ����� � [Content_Types].xml �(� Ėˎ�@E������gQٞ�L�LF�����i�~�����`,'�
�Hـ��{n�����ޖ�"�f�!�����q�����=�"$�rUz3q O��� �p�Ù(��W)Q`f>��/K�"~�+�^�������8J����,զ��۞_7NƉ�W�fB�P���ʭ���~�4r�7��3T� �2�01�'�B^dF(q�UƑ�1,L�O���ܓǿD��� *�u���O^�hrH^U�����})w>�ޯ��"C�^��*�ډ�����o#���x�O>��z�T�2�#J�����"*B�F|LW�8��𡽭����Z�<Uт��h���}lYLa����؞�v�ػ}������?�P6���j"�� ,�ml(I�:(��1�ik��L�0�{��s]`�Br�}�> ��ëH�K��4�D2pꦗ�Ɖ�w��z���
e�
��w��pY�i� �� PK ! ��, t _rels/.rels �(� ���N�0�;�徦Bh�.i7��x��Um�(�`{{��`�FAbǶ�:?��d���x�r�,�\
t�L��R�,GwRDg�#���b������;��S\5>�T��R����RQ��B�ȣK_*
8=�Zy�-Ԩ�y~��q
9��sS�07WR,�>�����"��)�ȇ$ܤ^�B�\JC�)���D��R�� �F�Tq��*��F��,�~��
��at��3�փ;vl�^"�������>d���Xħ���юF�]�C��sZ�:2�_6�����I�oϠF����ݕ�; �� PK ! vn�h t xl/workbook.xml�Vmo�:�>���Ӽ�����R����~A��c�&qf;�j��qBh)W�ر������?o��x�B2^E�9��A+�3V=F��Ej��!�2\�F�J�y�ϧ�
O+Ο �d�r��в$�i���i�5%V����ř�)Uea��X%f�Bq
_�� 'MI+ՁZ`�˜ղG+�)p%OMm^� �bS/-(2J^>V\�Uno��
��sl�&]U2"��ku�Vg�m9�A��18
ɳ}f:�{�D�A��=V�
���� �Z�����{�\49_���u�5p]ť�T��K5˘�Y����z�!�:nXR��cdM�t�#�k�jD��2�`�Z�qQ(**��W
x���o9�bOs7n�
��_a�$�+9�*7QDh.�G�������4��}-������JfYp��|�e|\8�fLt�,�Q�G��>^�{�Ε0�2�����g�!0%ە�%$�<TD��Ïx4� ��t{��N3����y~<K�a��?����F�;zh�y��#�5��������}L=�z�O�n�w�n�+����*�C�/�j87��e*�� :m������u��
�nt���x4F&�=�^�����FG�ؤ36����c�7ֶ��ng�j+h�%�0�43�l;+�t�ֹ�!B}�����ɴ��D��o�
�s[��7�-�NOm�ǎ�U$ݪ+�&�0�x����]H����3��8<�Oҁ?t���Ou��)�j����jOS��A]~�:�c���o���]��%�I���ӿS��nAOTN�NT�~�^\��{5[<ܧ�*_\���N���X�8(�>�V��a� �� PK ! �� � xl/_rels/workbook.xml.rels �(� ���N�0E�H���=q� T���-����C��G��Q
�J��Xε|����z�i:�[g%ϒ�3�ڕ��%�=]�s�Q�Ru�= ���g�T�Oش�X����?���0q,�T.���J�U
"Oӕ�=x1�d�R�-�9��:��vU�jxt�̀�GZ�H\@�*�%�Q���8C~N��� ĉ� �^�%��a�Y�cZ�*&�1��݉,��]xZ>g�0����$nϙ6*@�m>N43y ��0������C��^�n�� �� PK ! {-ؼ' �/ xl/worksheets/sheet1.xml��]o�0��'�?D�'���DZ1Z�I����k�85�3��i�}ljMؘ&\�@��|�ǰ�=֕��DW�&E��Ú��e�Mї���9��MN+ް���}�8p�����.E;)۹�uَմsy��RpQS ���Z�h�o�+��~�մl�@��k�(ʌ��l_�F�**!�nW����x�����s��p����E|u� ��B���!��,/�#�:�&њ��};p�mʪ�/}�ȩ���m��TБ#i�< ����饧�C�ىtYɫ08�{.��Fy]���"#,x%,:�T��|_�)�y��A�It/'�2�M�Ux?yG����:��_�f���=��#X��%�/�8D�͢�ג�3�Q���I]� �|`��a��SmMd�M�G�#+�UU�V!rh&�g� ;R��R���d?��+����?���)~� �o��a�>����
�86.!�L��_�Σ��ީ綩ͺ?�����+���JΡ�W�����2v(�(����N]�C&���P�r��!��L��a�\�*�}پ��}�wi�@��X5e�N�a4��g@�=Vø�?x��j� n�%i����k����,��(��g���@��uD�q��EK0�֠04'�%��Ed��G$��S@{%��e���2L�S{Ec�e���]��(`�e������2��l1��(A���5I�M/e�V�Ĝ6��B\�'6�$F��[c}h�Ze�R�=����2��؇c���;�vsO�l�1B��|��u�
�� �����n�8�_���%K���
[>��Y>�� �iS�����/�,b���b�������QZ篏�/����z~����;�����Y��s3���t������r��?<�>��o�x>=~yw�Si�]ڸ���g��χ�����j���tlN&�I�DRd��" ��٤f�R_ y^�|�&������39#)J��{SMl��ii*Z��r! ʃ(3�0,%DY��!XJ�Ʊ���MR1�]��qڈ�S��
�M�`+Z��;�����!|:�����籺���]�4?�
:�
��2��H��]��H�l�.sX�$��&�2��C��e$�`i,�Ny�i"ZB
O�8E��\��<�Ù�v7,%>:�B��/��'!�&�B���IקY�qwAa��!h�-���Db�����+aw�B!���y\a���3��az���ѱIQ�F��$����&K_t1�$�
P
C��$d�Xڝb7�I��g7����O��n&YJ�`)�!�Q�n ���\�$�
���@
��.l��Ȃ�h !؉�k��T�/�ί�y�
�Ǖˁ�
w����c?��x��
ݧ���ňgO"%H��i�B��F.��)w!�]H�҅�r!�]H�i�B:��Ҵ�e$p�n�ݶ�w���7�����
[��T6ܻ���N_�����I�8�:�T����3�,��� 0Ob�虣��Ƿ�)`�I9`�FK�8bV�qn�oÛ۞&�?nC�`��[+`(��������'�K2r9����@R>��hR%�9X� C�`H>