/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php

https://github.com/markn86/moodle · PHP · 676 lines · 370 code · 99 blank · 207 comment · 72 complexity · 57ad4ecdfb67d7052f7b3a885d1db8b6 MD5 · raw file

  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Calculation;
  3. use DateTimeInterface;
  4. use PhpOffice\PhpSpreadsheet\Shared\Date;
  5. use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
  6. use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  7. class TextData
  8. {
  9. private static $invalidChars;
  10. private static function unicodeToOrd($character)
  11. {
  12. return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1];
  13. }
  14. /**
  15. * CHARACTER.
  16. *
  17. * @param string $character Value
  18. *
  19. * @return string
  20. */
  21. public static function CHARACTER($character)
  22. {
  23. $character = Functions::flattenSingleValue($character);
  24. if (!is_numeric($character)) {
  25. return Functions::VALUE();
  26. }
  27. $character = (int) $character;
  28. if ($character < 1 || $character > 255) {
  29. return Functions::VALUE();
  30. }
  31. return iconv('UCS-4LE', 'UTF-8', pack('V', $character));
  32. }
  33. /**
  34. * TRIMNONPRINTABLE.
  35. *
  36. * @param mixed $stringValue Value to check
  37. *
  38. * @return string
  39. */
  40. public static function TRIMNONPRINTABLE($stringValue = '')
  41. {
  42. $stringValue = Functions::flattenSingleValue($stringValue);
  43. if (is_bool($stringValue)) {
  44. return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
  45. }
  46. if (self::$invalidChars === null) {
  47. self::$invalidChars = range(chr(0), chr(31));
  48. }
  49. if (is_string($stringValue) || is_numeric($stringValue)) {
  50. return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F"));
  51. }
  52. return null;
  53. }
  54. /**
  55. * TRIMSPACES.
  56. *
  57. * @param mixed $stringValue Value to check
  58. *
  59. * @return string
  60. */
  61. public static function TRIMSPACES($stringValue = '')
  62. {
  63. $stringValue = Functions::flattenSingleValue($stringValue);
  64. if (is_bool($stringValue)) {
  65. return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE();
  66. }
  67. if (is_string($stringValue) || is_numeric($stringValue)) {
  68. return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' ');
  69. }
  70. return null;
  71. }
  72. private static function convertBooleanValue($value)
  73. {
  74. if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) {
  75. return (int) $value;
  76. }
  77. return ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
  78. }
  79. /**
  80. * ASCIICODE.
  81. *
  82. * @param string $characters Value
  83. *
  84. * @return int|string A string if arguments are invalid
  85. */
  86. public static function ASCIICODE($characters)
  87. {
  88. if (($characters === null) || ($characters === '')) {
  89. return Functions::VALUE();
  90. }
  91. $characters = Functions::flattenSingleValue($characters);
  92. if (is_bool($characters)) {
  93. $characters = self::convertBooleanValue($characters);
  94. }
  95. $character = $characters;
  96. if (mb_strlen($characters, 'UTF-8') > 1) {
  97. $character = mb_substr($characters, 0, 1, 'UTF-8');
  98. }
  99. return self::unicodeToOrd($character);
  100. }
  101. /**
  102. * CONCATENATE.
  103. *
  104. * @return string
  105. */
  106. public static function CONCATENATE(...$args)
  107. {
  108. $returnValue = '';
  109. // Loop through arguments
  110. $aArgs = Functions::flattenArray($args);
  111. foreach ($aArgs as $arg) {
  112. if (is_bool($arg)) {
  113. $arg = self::convertBooleanValue($arg);
  114. }
  115. $returnValue .= $arg;
  116. }
  117. return $returnValue;
  118. }
  119. /**
  120. * DOLLAR.
  121. *
  122. * This function converts a number to text using currency format, with the decimals rounded to the specified place.
  123. * The format used is $#,##0.00_);($#,##0.00)..
  124. *
  125. * @param float $value The value to format
  126. * @param int $decimals The number of digits to display to the right of the decimal point.
  127. * If decimals is negative, number is rounded to the left of the decimal point.
  128. * If you omit decimals, it is assumed to be 2
  129. *
  130. * @return string
  131. */
  132. public static function DOLLAR($value = 0, $decimals = 2)
  133. {
  134. $value = Functions::flattenSingleValue($value);
  135. $decimals = $decimals === null ? 0 : Functions::flattenSingleValue($decimals);
  136. // Validate parameters
  137. if (!is_numeric($value) || !is_numeric($decimals)) {
  138. return Functions::VALUE();
  139. }
  140. $decimals = floor($decimals);
  141. $mask = '$#,##0';
  142. if ($decimals > 0) {
  143. $mask .= '.' . str_repeat('0', $decimals);
  144. } else {
  145. $round = 10 ** abs($decimals);
  146. if ($value < 0) {
  147. $round = 0 - $round;
  148. }
  149. $value = MathTrig::MROUND($value, $round);
  150. }
  151. $mask = "$mask;($mask)";
  152. return NumberFormat::toFormattedString($value, $mask);
  153. }
  154. /**
  155. * SEARCHSENSITIVE.
  156. *
  157. * @param string $needle The string to look for
  158. * @param string $haystack The string in which to look
  159. * @param int $offset Offset within $haystack
  160. *
  161. * @return string
  162. */
  163. public static function SEARCHSENSITIVE($needle, $haystack, $offset = 1)
  164. {
  165. $needle = Functions::flattenSingleValue($needle);
  166. $haystack = Functions::flattenSingleValue($haystack);
  167. $offset = Functions::flattenSingleValue($offset);
  168. if (!is_bool($needle)) {
  169. if (is_bool($haystack)) {
  170. $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
  171. }
  172. if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
  173. if (StringHelper::countCharacters($needle) === 0) {
  174. return $offset;
  175. }
  176. $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8');
  177. if ($pos !== false) {
  178. return ++$pos;
  179. }
  180. }
  181. }
  182. return Functions::VALUE();
  183. }
  184. /**
  185. * SEARCHINSENSITIVE.
  186. *
  187. * @param string $needle The string to look for
  188. * @param string $haystack The string in which to look
  189. * @param int $offset Offset within $haystack
  190. *
  191. * @return string
  192. */
  193. public static function SEARCHINSENSITIVE($needle, $haystack, $offset = 1)
  194. {
  195. $needle = Functions::flattenSingleValue($needle);
  196. $haystack = Functions::flattenSingleValue($haystack);
  197. $offset = Functions::flattenSingleValue($offset);
  198. if (!is_bool($needle)) {
  199. if (is_bool($haystack)) {
  200. $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE();
  201. }
  202. if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) {
  203. if (StringHelper::countCharacters($needle) === 0) {
  204. return $offset;
  205. }
  206. $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8');
  207. if ($pos !== false) {
  208. return ++$pos;
  209. }
  210. }
  211. }
  212. return Functions::VALUE();
  213. }
  214. /**
  215. * FIXEDFORMAT.
  216. *
  217. * @param mixed $value Value to check
  218. * @param int $decimals
  219. * @param bool $no_commas
  220. *
  221. * @return string
  222. */
  223. public static function FIXEDFORMAT($value, $decimals = 2, $no_commas = false)
  224. {
  225. $value = Functions::flattenSingleValue($value);
  226. $decimals = Functions::flattenSingleValue($decimals);
  227. $no_commas = Functions::flattenSingleValue($no_commas);
  228. // Validate parameters
  229. if (!is_numeric($value) || !is_numeric($decimals)) {
  230. return Functions::VALUE();
  231. }
  232. $decimals = (int) floor($decimals);
  233. $valueResult = round($value, $decimals);
  234. if ($decimals < 0) {
  235. $decimals = 0;
  236. }
  237. if (!$no_commas) {
  238. $valueResult = number_format(
  239. $valueResult,
  240. $decimals,
  241. StringHelper::getDecimalSeparator(),
  242. StringHelper::getThousandsSeparator()
  243. );
  244. }
  245. return (string) $valueResult;
  246. }
  247. /**
  248. * LEFT.
  249. *
  250. * @param string $value Value
  251. * @param int $chars Number of characters
  252. *
  253. * @return string
  254. */
  255. public static function LEFT($value = '', $chars = 1)
  256. {
  257. $value = Functions::flattenSingleValue($value);
  258. $chars = Functions::flattenSingleValue($chars);
  259. if ($chars < 0) {
  260. return Functions::VALUE();
  261. }
  262. if (is_bool($value)) {
  263. $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
  264. }
  265. return mb_substr($value, 0, $chars, 'UTF-8');
  266. }
  267. /**
  268. * MID.
  269. *
  270. * @param string $value Value
  271. * @param int $start Start character
  272. * @param int $chars Number of characters
  273. *
  274. * @return string
  275. */
  276. public static function MID($value = '', $start = 1, $chars = null)
  277. {
  278. $value = Functions::flattenSingleValue($value);
  279. $start = Functions::flattenSingleValue($start);
  280. $chars = Functions::flattenSingleValue($chars);
  281. if (($start < 1) || ($chars < 0)) {
  282. return Functions::VALUE();
  283. }
  284. if (is_bool($value)) {
  285. $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
  286. }
  287. if (empty($chars)) {
  288. return '';
  289. }
  290. return mb_substr($value, --$start, $chars, 'UTF-8');
  291. }
  292. /**
  293. * RIGHT.
  294. *
  295. * @param string $value Value
  296. * @param int $chars Number of characters
  297. *
  298. * @return string
  299. */
  300. public static function RIGHT($value = '', $chars = 1)
  301. {
  302. $value = Functions::flattenSingleValue($value);
  303. $chars = Functions::flattenSingleValue($chars);
  304. if ($chars < 0) {
  305. return Functions::VALUE();
  306. }
  307. if (is_bool($value)) {
  308. $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
  309. }
  310. return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8');
  311. }
  312. /**
  313. * STRINGLENGTH.
  314. *
  315. * @param string $value Value
  316. *
  317. * @return int
  318. */
  319. public static function STRINGLENGTH($value = '')
  320. {
  321. $value = Functions::flattenSingleValue($value);
  322. if (is_bool($value)) {
  323. $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE();
  324. }
  325. return mb_strlen($value, 'UTF-8');
  326. }
  327. /**
  328. * LOWERCASE.
  329. *
  330. * Converts a string value to upper case.
  331. *
  332. * @param string $mixedCaseString
  333. *
  334. * @return string
  335. */
  336. public static function LOWERCASE($mixedCaseString)
  337. {
  338. $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
  339. if (is_bool($mixedCaseString)) {
  340. $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
  341. }
  342. return StringHelper::strToLower($mixedCaseString);
  343. }
  344. /**
  345. * UPPERCASE.
  346. *
  347. * Converts a string value to upper case.
  348. *
  349. * @param string $mixedCaseString
  350. *
  351. * @return string
  352. */
  353. public static function UPPERCASE($mixedCaseString)
  354. {
  355. $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
  356. if (is_bool($mixedCaseString)) {
  357. $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
  358. }
  359. return StringHelper::strToUpper($mixedCaseString);
  360. }
  361. /**
  362. * PROPERCASE.
  363. *
  364. * Converts a string value to upper case.
  365. *
  366. * @param string $mixedCaseString
  367. *
  368. * @return string
  369. */
  370. public static function PROPERCASE($mixedCaseString)
  371. {
  372. $mixedCaseString = Functions::flattenSingleValue($mixedCaseString);
  373. if (is_bool($mixedCaseString)) {
  374. $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE();
  375. }
  376. return StringHelper::strToTitle($mixedCaseString);
  377. }
  378. /**
  379. * REPLACE.
  380. *
  381. * @param string $oldText String to modify
  382. * @param int $start Start character
  383. * @param int $chars Number of characters
  384. * @param string $newText String to replace in defined position
  385. *
  386. * @return string
  387. */
  388. public static function REPLACE($oldText, $start, $chars, $newText)
  389. {
  390. $oldText = Functions::flattenSingleValue($oldText);
  391. $start = Functions::flattenSingleValue($start);
  392. $chars = Functions::flattenSingleValue($chars);
  393. $newText = Functions::flattenSingleValue($newText);
  394. $left = self::LEFT($oldText, $start - 1);
  395. $right = self::RIGHT($oldText, self::STRINGLENGTH($oldText) - ($start + $chars) + 1);
  396. return $left . $newText . $right;
  397. }
  398. /**
  399. * SUBSTITUTE.
  400. *
  401. * @param string $text Value
  402. * @param string $fromText From Value
  403. * @param string $toText To Value
  404. * @param int $instance Instance Number
  405. *
  406. * @return string
  407. */
  408. public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0)
  409. {
  410. $text = Functions::flattenSingleValue($text);
  411. $fromText = Functions::flattenSingleValue($fromText);
  412. $toText = Functions::flattenSingleValue($toText);
  413. $instance = floor(Functions::flattenSingleValue($instance));
  414. if ($instance == 0) {
  415. return str_replace($fromText, $toText, $text);
  416. }
  417. $pos = -1;
  418. while ($instance > 0) {
  419. $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8');
  420. if ($pos === false) {
  421. break;
  422. }
  423. --$instance;
  424. }
  425. if ($pos !== false) {
  426. return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText);
  427. }
  428. return $text;
  429. }
  430. /**
  431. * RETURNSTRING.
  432. *
  433. * @param mixed $testValue Value to check
  434. *
  435. * @return null|string
  436. */
  437. public static function RETURNSTRING($testValue = '')
  438. {
  439. $testValue = Functions::flattenSingleValue($testValue);
  440. if (is_string($testValue)) {
  441. return $testValue;
  442. }
  443. return null;
  444. }
  445. /**
  446. * TEXTFORMAT.
  447. *
  448. * @param mixed $value Value to check
  449. * @param string $format Format mask to use
  450. *
  451. * @return string
  452. */
  453. public static function TEXTFORMAT($value, $format)
  454. {
  455. $value = Functions::flattenSingleValue($value);
  456. $format = Functions::flattenSingleValue($format);
  457. if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) {
  458. $value = DateTime::DATEVALUE($value);
  459. }
  460. return (string) NumberFormat::toFormattedString($value, $format);
  461. }
  462. /**
  463. * VALUE.
  464. *
  465. * @param mixed $value Value to check
  466. *
  467. * @return DateTimeInterface|float|int|string A string if arguments are invalid
  468. */
  469. public static function VALUE($value = '')
  470. {
  471. $value = Functions::flattenSingleValue($value);
  472. if (!is_numeric($value)) {
  473. $numberValue = str_replace(
  474. StringHelper::getThousandsSeparator(),
  475. '',
  476. trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
  477. );
  478. if (is_numeric($numberValue)) {
  479. return (float) $numberValue;
  480. }
  481. $dateSetting = Functions::getReturnDateType();
  482. Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
  483. if (strpos($value, ':') !== false) {
  484. $timeValue = DateTime::TIMEVALUE($value);
  485. if ($timeValue !== Functions::VALUE()) {
  486. Functions::setReturnDateType($dateSetting);
  487. return $timeValue;
  488. }
  489. }
  490. $dateValue = DateTime::DATEVALUE($value);
  491. if ($dateValue !== Functions::VALUE()) {
  492. Functions::setReturnDateType($dateSetting);
  493. return $dateValue;
  494. }
  495. Functions::setReturnDateType($dateSetting);
  496. return Functions::VALUE();
  497. }
  498. return (float) $value;
  499. }
  500. /**
  501. * NUMBERVALUE.
  502. *
  503. * @param mixed $value Value to check
  504. * @param string $decimalSeparator decimal separator, defaults to locale defined value
  505. * @param string $groupSeparator group/thosands separator, defaults to locale defined value
  506. *
  507. * @return float|string
  508. */
  509. public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null)
  510. {
  511. $value = Functions::flattenSingleValue($value);
  512. $decimalSeparator = Functions::flattenSingleValue($decimalSeparator);
  513. $groupSeparator = Functions::flattenSingleValue($groupSeparator);
  514. if (!is_numeric($value)) {
  515. $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator;
  516. $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator;
  517. $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
  518. if ($decimalPositions > 1) {
  519. return Functions::VALUE();
  520. }
  521. $decimalOffset = array_pop($matches[0])[1];
  522. if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
  523. return Functions::VALUE();
  524. }
  525. $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
  526. // Handle the special case of trailing % signs
  527. $percentageString = rtrim($value, '%');
  528. if (!is_numeric($percentageString)) {
  529. return Functions::VALUE();
  530. }
  531. $percentageAdjustment = strlen($value) - strlen($percentageString);
  532. if ($percentageAdjustment) {
  533. $value = (float) $percentageString;
  534. $value /= 10 ** ($percentageAdjustment * 2);
  535. }
  536. }
  537. return (float) $value;
  538. }
  539. /**
  540. * Compares two text strings and returns TRUE if they are exactly the same, FALSE otherwise.
  541. * EXACT is case-sensitive but ignores formatting differences.
  542. * Use EXACT to test text being entered into a document.
  543. *
  544. * @param $value1
  545. * @param $value2
  546. *
  547. * @return bool
  548. */
  549. public static function EXACT($value1, $value2)
  550. {
  551. $value1 = Functions::flattenSingleValue($value1);
  552. $value2 = Functions::flattenSingleValue($value2);
  553. return (string) $value2 === (string) $value1;
  554. }
  555. /**
  556. * TEXTJOIN.
  557. *
  558. * @param mixed $delimiter
  559. * @param mixed $ignoreEmpty
  560. * @param mixed $args
  561. *
  562. * @return string
  563. */
  564. public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args)
  565. {
  566. // Loop through arguments
  567. $aArgs = Functions::flattenArray($args);
  568. foreach ($aArgs as $key => &$arg) {
  569. if ($ignoreEmpty && trim($arg) == '') {
  570. unset($aArgs[$key]);
  571. } elseif (is_bool($arg)) {
  572. $arg = self::convertBooleanValue($arg);
  573. }
  574. }
  575. return implode($delimiter, $aArgs);
  576. }
  577. }