For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

879 lines
36 KiB

  1. <?php
  2. /** PHPExcel root directory */
  3. if (!defined('PHPEXCEL_ROOT')) {
  4. /**
  5. * @ignore
  6. */
  7. define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
  8. require(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
  9. }
  10. /**
  11. * PHPExcel_Calculation_LookupRef
  12. *
  13. * Copyright (c) 2006 - 2015 PHPExcel
  14. *
  15. * This library is free software; you can redistribute it and/or
  16. * modify it under the terms of the GNU Lesser General Public
  17. * License as published by the Free Software Foundation; either
  18. * version 2.1 of the License, or (at your option) any later version.
  19. *
  20. * This library is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  23. * Lesser General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU Lesser General Public
  26. * License along with this library; if not, write to the Free Software
  27. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  28. *
  29. * @category PHPExcel
  30. * @package PHPExcel_Calculation
  31. * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
  32. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  33. * @version ##VERSION##, ##DATE##
  34. */
  35. class PHPExcel_Calculation_LookupRef
  36. {
  37. /**
  38. * CELL_ADDRESS
  39. *
  40. * Creates a cell address as text, given specified row and column numbers.
  41. *
  42. * Excel Function:
  43. * =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText])
  44. *
  45. * @param row Row number to use in the cell reference
  46. * @param column Column number to use in the cell reference
  47. * @param relativity Flag indicating the type of reference to return
  48. * 1 or omitted Absolute
  49. * 2 Absolute row; relative column
  50. * 3 Relative row; absolute column
  51. * 4 Relative
  52. * @param referenceStyle A logical value that specifies the A1 or R1C1 reference style.
  53. * TRUE or omitted CELL_ADDRESS returns an A1-style reference
  54. * FALSE CELL_ADDRESS returns an R1C1-style reference
  55. * @param sheetText Optional Name of worksheet to use
  56. * @return string
  57. */
  58. public static function CELL_ADDRESS($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '')
  59. {
  60. $row = PHPExcel_Calculation_Functions::flattenSingleValue($row);
  61. $column = PHPExcel_Calculation_Functions::flattenSingleValue($column);
  62. $relativity = PHPExcel_Calculation_Functions::flattenSingleValue($relativity);
  63. $sheetText = PHPExcel_Calculation_Functions::flattenSingleValue($sheetText);
  64. if (($row < 1) || ($column < 1)) {
  65. return PHPExcel_Calculation_Functions::VALUE();
  66. }
  67. if ($sheetText > '') {
  68. if (strpos($sheetText, ' ') !== false) {
  69. $sheetText = "'".$sheetText."'";
  70. }
  71. $sheetText .='!';
  72. }
  73. if ((!is_bool($referenceStyle)) || $referenceStyle) {
  74. $rowRelative = $columnRelative = '$';
  75. $column = PHPExcel_Cell::stringFromColumnIndex($column-1);
  76. if (($relativity == 2) || ($relativity == 4)) {
  77. $columnRelative = '';
  78. }
  79. if (($relativity == 3) || ($relativity == 4)) {
  80. $rowRelative = '';
  81. }
  82. return $sheetText.$columnRelative.$column.$rowRelative.$row;
  83. } else {
  84. if (($relativity == 2) || ($relativity == 4)) {
  85. $column = '['.$column.']';
  86. }
  87. if (($relativity == 3) || ($relativity == 4)) {
  88. $row = '['.$row.']';
  89. }
  90. return $sheetText.'R'.$row.'C'.$column;
  91. }
  92. }
  93. /**
  94. * COLUMN
  95. *
  96. * Returns the column number of the given cell reference
  97. * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array.
  98. * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
  99. * reference of the cell in which the COLUMN function appears; otherwise this function returns 0.
  100. *
  101. * Excel Function:
  102. * =COLUMN([cellAddress])
  103. *
  104. * @param cellAddress A reference to a range of cells for which you want the column numbers
  105. * @return integer or array of integer
  106. */
  107. public static function COLUMN($cellAddress = null)
  108. {
  109. if (is_null($cellAddress) || trim($cellAddress) === '') {
  110. return 0;
  111. }
  112. if (is_array($cellAddress)) {
  113. foreach ($cellAddress as $columnKey => $value) {
  114. $columnKey = preg_replace('/[^a-z]/i', '', $columnKey);
  115. return (integer) PHPExcel_Cell::columnIndexFromString($columnKey);
  116. }
  117. } else {
  118. if (strpos($cellAddress, '!') !== false) {
  119. list($sheet, $cellAddress) = explode('!', $cellAddress);
  120. }
  121. if (strpos($cellAddress, ':') !== false) {
  122. list($startAddress, $endAddress) = explode(':', $cellAddress);
  123. $startAddress = preg_replace('/[^a-z]/i', '', $startAddress);
  124. $endAddress = preg_replace('/[^a-z]/i', '', $endAddress);
  125. $returnValue = array();
  126. do {
  127. $returnValue[] = (integer) PHPExcel_Cell::columnIndexFromString($startAddress);
  128. } while ($startAddress++ != $endAddress);
  129. return $returnValue;
  130. } else {
  131. $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress);
  132. return (integer) PHPExcel_Cell::columnIndexFromString($cellAddress);
  133. }
  134. }
  135. }
  136. /**
  137. * COLUMNS
  138. *
  139. * Returns the number of columns in an array or reference.
  140. *
  141. * Excel Function:
  142. * =COLUMNS(cellAddress)
  143. *
  144. * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns
  145. * @return integer The number of columns in cellAddress
  146. */
  147. public static function COLUMNS($cellAddress = null)
  148. {
  149. if (is_null($cellAddress) || $cellAddress === '') {
  150. return 1;
  151. } elseif (!is_array($cellAddress)) {
  152. return PHPExcel_Calculation_Functions::VALUE();
  153. }
  154. reset($cellAddress);
  155. $isMatrix = (is_numeric(key($cellAddress)));
  156. list($columns, $rows) = PHPExcel_Calculation::_getMatrixDimensions($cellAddress);
  157. if ($isMatrix) {
  158. return $rows;
  159. } else {
  160. return $columns;
  161. }
  162. }
  163. /**
  164. * ROW
  165. *
  166. * Returns the row number of the given cell reference
  167. * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array.
  168. * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the
  169. * reference of the cell in which the ROW function appears; otherwise this function returns 0.
  170. *
  171. * Excel Function:
  172. * =ROW([cellAddress])
  173. *
  174. * @param cellAddress A reference to a range of cells for which you want the row numbers
  175. * @return integer or array of integer
  176. */
  177. public static function ROW($cellAddress = null)
  178. {
  179. if (is_null($cellAddress) || trim($cellAddress) === '') {
  180. return 0;
  181. }
  182. if (is_array($cellAddress)) {
  183. foreach ($cellAddress as $columnKey => $rowValue) {
  184. foreach ($rowValue as $rowKey => $cellValue) {
  185. return (integer) preg_replace('/[^0-9]/i', '', $rowKey);
  186. }
  187. }
  188. } else {
  189. if (strpos($cellAddress, '!') !== false) {
  190. list($sheet, $cellAddress) = explode('!', $cellAddress);
  191. }
  192. if (strpos($cellAddress, ':') !== false) {
  193. list($startAddress, $endAddress) = explode(':', $cellAddress);
  194. $startAddress = preg_replace('/[^0-9]/', '', $startAddress);
  195. $endAddress = preg_replace('/[^0-9]/', '', $endAddress);
  196. $returnValue = array();
  197. do {
  198. $returnValue[][] = (integer) $startAddress;
  199. } while ($startAddress++ != $endAddress);
  200. return $returnValue;
  201. } else {
  202. list($cellAddress) = explode(':', $cellAddress);
  203. return (integer) preg_replace('/[^0-9]/', '', $cellAddress);
  204. }
  205. }
  206. }
  207. /**
  208. * ROWS
  209. *
  210. * Returns the number of rows in an array or reference.
  211. *
  212. * Excel Function:
  213. * =ROWS(cellAddress)
  214. *
  215. * @param cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows
  216. * @return integer The number of rows in cellAddress
  217. */
  218. public static function ROWS($cellAddress = null)
  219. {
  220. if (is_null($cellAddress) || $cellAddress === '') {
  221. return 1;
  222. } elseif (!is_array($cellAddress)) {
  223. return PHPExcel_Calculation_Functions::VALUE();
  224. }
  225. reset($cellAddress);
  226. $isMatrix = (is_numeric(key($cellAddress)));
  227. list($columns, $rows) = PHPExcel_Calculation::_getMatrixDimensions($cellAddress);
  228. if ($isMatrix) {
  229. return $columns;
  230. } else {
  231. return $rows;
  232. }
  233. }
  234. /**
  235. * HYPERLINK
  236. *
  237. * Excel Function:
  238. * =HYPERLINK(linkURL,displayName)
  239. *
  240. * @access public
  241. * @category Logical Functions
  242. * @param string $linkURL Value to check, is also the value returned when no error
  243. * @param string $displayName Value to return when testValue is an error condition
  244. * @param PHPExcel_Cell $pCell The cell to set the hyperlink in
  245. * @return mixed The value of $displayName (or $linkURL if $displayName was blank)
  246. */
  247. public static function HYPERLINK($linkURL = '', $displayName = null, PHPExcel_Cell $pCell = null)
  248. {
  249. $args = func_get_args();
  250. $pCell = array_pop($args);
  251. $linkURL = (is_null($linkURL)) ? '' : PHPExcel_Calculation_Functions::flattenSingleValue($linkURL);
  252. $displayName = (is_null($displayName)) ? '' : PHPExcel_Calculation_Functions::flattenSingleValue($displayName);
  253. if ((!is_object($pCell)) || (trim($linkURL) == '')) {
  254. return PHPExcel_Calculation_Functions::REF();
  255. }
  256. if ((is_object($displayName)) || trim($displayName) == '') {
  257. $displayName = $linkURL;
  258. }
  259. $pCell->getHyperlink()->setUrl($linkURL);
  260. $pCell->getHyperlink()->setTooltip($displayName);
  261. return $displayName;
  262. }
  263. /**
  264. * INDIRECT
  265. *
  266. * Returns the reference specified by a text string.
  267. * References are immediately evaluated to display their contents.
  268. *
  269. * Excel Function:
  270. * =INDIRECT(cellAddress)
  271. *
  272. * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010
  273. *
  274. * @param cellAddress $cellAddress The cell address of the current cell (containing this formula)
  275. * @param PHPExcel_Cell $pCell The current cell (containing this formula)
  276. * @return mixed The cells referenced by cellAddress
  277. *
  278. * @todo Support for the optional a1 parameter introduced in Excel 2010
  279. *
  280. */
  281. public static function INDIRECT($cellAddress = null, PHPExcel_Cell $pCell = null)
  282. {
  283. $cellAddress = PHPExcel_Calculation_Functions::flattenSingleValue($cellAddress);
  284. if (is_null($cellAddress) || $cellAddress === '') {
  285. return PHPExcel_Calculation_Functions::REF();
  286. }
  287. $cellAddress1 = $cellAddress;
  288. $cellAddress2 = null;
  289. if (strpos($cellAddress, ':') !== false) {
  290. list($cellAddress1, $cellAddress2) = explode(':', $cellAddress);
  291. }
  292. if ((!preg_match('/^'.PHPExcel_Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress1, $matches)) ||
  293. ((!is_null($cellAddress2)) && (!preg_match('/^'.PHPExcel_Calculation::CALCULATION_REGEXP_CELLREF.'$/i', $cellAddress2, $matches)))) {
  294. if (!preg_match('/^'.PHPExcel_Calculation::CALCULATION_REGEXP_NAMEDRANGE.'$/i', $cellAddress1, $matches)) {
  295. return PHPExcel_Calculation_Functions::REF();
  296. }
  297. if (strpos($cellAddress, '!') !== false) {
  298. list($sheetName, $cellAddress) = explode('!', $cellAddress);
  299. $sheetName = trim($sheetName, "'");
  300. $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
  301. } else {
  302. $pSheet = $pCell->getWorksheet();
  303. }
  304. return PHPExcel_Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false);
  305. }
  306. if (strpos($cellAddress, '!') !== false) {
  307. list($sheetName, $cellAddress) = explode('!', $cellAddress);
  308. $sheetName = trim($sheetName, "'");
  309. $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
  310. } else {
  311. $pSheet = $pCell->getWorksheet();
  312. }
  313. return PHPExcel_Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
  314. }
  315. /**
  316. * OFFSET
  317. *
  318. * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
  319. * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
  320. * the number of columns to be returned.
  321. *
  322. * Excel Function:
  323. * =OFFSET(cellAddress, rows, cols, [height], [width])
  324. *
  325. * @param cellAddress The reference from which you want to base the offset. Reference must refer to a cell or
  326. * range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value.
  327. * @param rows The number of rows, up or down, that you want the upper-left cell to refer to.
  328. * Using 5 as the rows argument specifies that the upper-left cell in the reference is
  329. * five rows below reference. Rows can be positive (which means below the starting reference)
  330. * or negative (which means above the starting reference).
  331. * @param cols The number of columns, to the left or right, that you want the upper-left cell of the result
  332. * to refer to. Using 5 as the cols argument specifies that the upper-left cell in the
  333. * reference is five columns to the right of reference. Cols can be positive (which means
  334. * to the right of the starting reference) or negative (which means to the left of the
  335. * starting reference).
  336. * @param height The height, in number of rows, that you want the returned reference to be. Height must be a positive number.
  337. * @param width The width, in number of columns, that you want the returned reference to be. Width must be a positive number.
  338. * @return string A reference to a cell or range of cells
  339. */
  340. public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null)
  341. {
  342. $rows = PHPExcel_Calculation_Functions::flattenSingleValue($rows);
  343. $columns = PHPExcel_Calculation_Functions::flattenSingleValue($columns);
  344. $height = PHPExcel_Calculation_Functions::flattenSingleValue($height);
  345. $width = PHPExcel_Calculation_Functions::flattenSingleValue($width);
  346. if ($cellAddress == null) {
  347. return 0;
  348. }
  349. $args = func_get_args();
  350. $pCell = array_pop($args);
  351. if (!is_object($pCell)) {
  352. return PHPExcel_Calculation_Functions::REF();
  353. }
  354. $sheetName = null;
  355. if (strpos($cellAddress, "!")) {
  356. list($sheetName, $cellAddress) = explode("!", $cellAddress);
  357. $sheetName = trim($sheetName, "'");
  358. }
  359. if (strpos($cellAddress, ":")) {
  360. list($startCell, $endCell) = explode(":", $cellAddress);
  361. } else {
  362. $startCell = $endCell = $cellAddress;
  363. }
  364. list($startCellColumn, $startCellRow) = PHPExcel_Cell::coordinateFromString($startCell);
  365. list($endCellColumn, $endCellRow) = PHPExcel_Cell::coordinateFromString($endCell);
  366. $startCellRow += $rows;
  367. $startCellColumn = PHPExcel_Cell::columnIndexFromString($startCellColumn) - 1;
  368. $startCellColumn += $columns;
  369. if (($startCellRow <= 0) || ($startCellColumn < 0)) {
  370. return PHPExcel_Calculation_Functions::REF();
  371. }
  372. $endCellColumn = PHPExcel_Cell::columnIndexFromString($endCellColumn) - 1;
  373. if (($width != null) && (!is_object($width))) {
  374. $endCellColumn = $startCellColumn + $width - 1;
  375. } else {
  376. $endCellColumn += $columns;
  377. }
  378. $startCellColumn = PHPExcel_Cell::stringFromColumnIndex($startCellColumn);
  379. if (($height != null) && (!is_object($height))) {
  380. $endCellRow = $startCellRow + $height - 1;
  381. } else {
  382. $endCellRow += $rows;
  383. }
  384. if (($endCellRow <= 0) || ($endCellColumn < 0)) {
  385. return PHPExcel_Calculation_Functions::REF();
  386. }
  387. $endCellColumn = PHPExcel_Cell::stringFromColumnIndex($endCellColumn);
  388. $cellAddress = $startCellColumn.$startCellRow;
  389. if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) {
  390. $cellAddress .= ':'.$endCellColumn.$endCellRow;
  391. }
  392. if ($sheetName !== null) {
  393. $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName);
  394. } else {
  395. $pSheet = $pCell->getWorksheet();
  396. }
  397. return PHPExcel_Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false);
  398. }
  399. /**
  400. * CHOOSE
  401. *
  402. * Uses lookup_value to return a value from the list of value arguments.
  403. * Use CHOOSE to select one of up to 254 values based on the lookup_value.
  404. *
  405. * Excel Function:
  406. * =CHOOSE(index_num, value1, [value2], ...)
  407. *
  408. * @param index_num Specifies which value argument is selected.
  409. * Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number
  410. * between 1 and 254.
  411. * @param value1... Value1 is required, subsequent values are optional.
  412. * Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on
  413. * index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or
  414. * text.
  415. * @return mixed The selected value
  416. */
  417. public static function CHOOSE()
  418. {
  419. $chooseArgs = func_get_args();
  420. $chosenEntry = PHPExcel_Calculation_Functions::flattenArray(array_shift($chooseArgs));
  421. $entryCount = count($chooseArgs) - 1;
  422. if (is_array($chosenEntry)) {
  423. $chosenEntry = array_shift($chosenEntry);
  424. }
  425. if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) {
  426. --$chosenEntry;
  427. } else {
  428. return PHPExcel_Calculation_Functions::VALUE();
  429. }
  430. $chosenEntry = floor($chosenEntry);
  431. if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) {
  432. return PHPExcel_Calculation_Functions::VALUE();
  433. }
  434. if (is_array($chooseArgs[$chosenEntry])) {
  435. return PHPExcel_Calculation_Functions::flattenArray($chooseArgs[$chosenEntry]);
  436. } else {
  437. return $chooseArgs[$chosenEntry];
  438. }
  439. }
  440. /**
  441. * MATCH
  442. *
  443. * The MATCH function searches for a specified item in a range of cells
  444. *
  445. * Excel Function:
  446. * =MATCH(lookup_value, lookup_array, [match_type])
  447. *
  448. * @param lookup_value The value that you want to match in lookup_array
  449. * @param lookup_array The range of cells being searched
  450. * @param match_type The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. If match_type is 1 or -1, the list has to be ordered.
  451. * @return integer The relative position of the found item
  452. */
  453. public static function MATCH($lookup_value, $lookup_array, $match_type = 1)
  454. {
  455. $lookup_array = PHPExcel_Calculation_Functions::flattenArray($lookup_array);
  456. $lookup_value = PHPExcel_Calculation_Functions::flattenSingleValue($lookup_value);
  457. $match_type = (is_null($match_type)) ? 1 : (int) PHPExcel_Calculation_Functions::flattenSingleValue($match_type);
  458. // MATCH is not case sensitive
  459. $lookup_value = strtolower($lookup_value);
  460. // lookup_value type has to be number, text, or logical values
  461. if ((!is_numeric($lookup_value)) && (!is_string($lookup_value)) && (!is_bool($lookup_value))) {
  462. return PHPExcel_Calculation_Functions::NA();
  463. }
  464. // match_type is 0, 1 or -1
  465. if (($match_type !== 0) && ($match_type !== -1) && ($match_type !== 1)) {
  466. return PHPExcel_Calculation_Functions::NA();
  467. }
  468. // lookup_array should not be empty
  469. $lookupArraySize = count($lookup_array);
  470. if ($lookupArraySize <= 0) {
  471. return PHPExcel_Calculation_Functions::NA();
  472. }
  473. // lookup_array should contain only number, text, or logical values, or empty (null) cells
  474. foreach ($lookup_array as $i => $lookupArrayValue) {
  475. // check the type of the value
  476. if ((!is_numeric($lookupArrayValue)) && (!is_string($lookupArrayValue)) &&
  477. (!is_bool($lookupArrayValue)) && (!is_null($lookupArrayValue))) {
  478. return PHPExcel_Calculation_Functions::NA();
  479. }
  480. // convert strings to lowercase for case-insensitive testing
  481. if (is_string($lookupArrayValue)) {
  482. $lookup_array[$i] = strtolower($lookupArrayValue);
  483. }
  484. if ((is_null($lookupArrayValue)) && (($match_type == 1) || ($match_type == -1))) {
  485. $lookup_array = array_slice($lookup_array, 0, $i-1);
  486. }
  487. }
  488. // if match_type is 1 or -1, the list has to be ordered
  489. if ($match_type == 1) {
  490. asort($lookup_array);
  491. $keySet = array_keys($lookup_array);
  492. } elseif ($match_type == -1) {
  493. arsort($lookup_array);
  494. $keySet = array_keys($lookup_array);
  495. }
  496. // **
  497. // find the match
  498. // **
  499. foreach ($lookup_array as $i => $lookupArrayValue) {
  500. if (($match_type == 0) && ($lookupArrayValue == $lookup_value)) {
  501. // exact match
  502. return ++$i;
  503. } elseif (($match_type == -1) && ($lookupArrayValue <= $lookup_value)) {
  504. $i = array_search($i, $keySet);
  505. // if match_type is -1 <=> find the smallest value that is greater than or equal to lookup_value
  506. if ($i < 1) {
  507. // 1st cell was already smaller than the lookup_value
  508. break;
  509. } else {
  510. // the previous cell was the match
  511. return $keySet[$i-1]+1;
  512. }
  513. } elseif (($match_type == 1) && ($lookupArrayValue >= $lookup_value)) {
  514. $i = array_search($i, $keySet);
  515. // if match_type is 1 <=> find the largest value that is less than or equal to lookup_value
  516. if ($i < 1) {
  517. // 1st cell was already bigger than the lookup_value
  518. break;
  519. } else {
  520. // the previous cell was the match
  521. return $keySet[$i-1]+1;
  522. }
  523. }
  524. }
  525. // unsuccessful in finding a match, return #N/A error value
  526. return PHPExcel_Calculation_Functions::NA();
  527. }
  528. /**
  529. * INDEX
  530. *
  531. * Uses an index to choose a value from a reference or array
  532. *
  533. * Excel Function:
  534. * =INDEX(range_array, row_num, [column_num])
  535. *
  536. * @param range_array A range of cells or an array constant
  537. * @param row_num The row in array from which to return a value. If row_num is omitted, column_num is required.
  538. * @param column_num The column in array from which to return a value. If column_num is omitted, row_num is required.
  539. * @return mixed the value of a specified cell or array of cells
  540. */
  541. public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0)
  542. {
  543. if (($rowNum < 0) || ($columnNum < 0)) {
  544. return PHPExcel_Calculation_Functions::VALUE();
  545. }
  546. if (!is_array($arrayValues)) {
  547. return PHPExcel_Calculation_Functions::REF();
  548. }
  549. $rowKeys = array_keys($arrayValues);
  550. $columnKeys = @array_keys($arrayValues[$rowKeys[0]]);
  551. if ($columnNum > count($columnKeys)) {
  552. return PHPExcel_Calculation_Functions::VALUE();
  553. } elseif ($columnNum == 0) {
  554. if ($rowNum == 0) {
  555. return $arrayValues;
  556. }
  557. $rowNum = $rowKeys[--$rowNum];
  558. $returnArray = array();
  559. foreach ($arrayValues as $arrayColumn) {
  560. if (is_array($arrayColumn)) {
  561. if (isset($arrayColumn[$rowNum])) {
  562. $returnArray[] = $arrayColumn[$rowNum];
  563. } else {
  564. return $arrayValues[$rowNum];
  565. }
  566. } else {
  567. return $arrayValues[$rowNum];
  568. }
  569. }
  570. return $returnArray;
  571. }
  572. $columnNum = $columnKeys[--$columnNum];
  573. if ($rowNum > count($rowKeys)) {
  574. return PHPExcel_Calculation_Functions::VALUE();
  575. } elseif ($rowNum == 0) {
  576. return $arrayValues[$columnNum];
  577. }
  578. $rowNum = $rowKeys[--$rowNum];
  579. return $arrayValues[$rowNum][$columnNum];
  580. }
  581. /**
  582. * TRANSPOSE
  583. *
  584. * @param array $matrixData A matrix of values
  585. * @return array
  586. *
  587. * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix.
  588. */
  589. public static function TRANSPOSE($matrixData)
  590. {
  591. $returnMatrix = array();
  592. if (!is_array($matrixData)) {
  593. $matrixData = array(array($matrixData));
  594. }
  595. $column = 0;
  596. foreach ($matrixData as $matrixRow) {
  597. $row = 0;
  598. foreach ($matrixRow as $matrixCell) {
  599. $returnMatrix[$row][$column] = $matrixCell;
  600. ++$row;
  601. }
  602. ++$column;
  603. }
  604. return $returnMatrix;
  605. }
  606. private static function vlookupSort($a, $b)
  607. {
  608. reset($a);
  609. $firstColumn = key($a);
  610. if (($aLower = strtolower($a[$firstColumn])) == ($bLower = strtolower($b[$firstColumn]))) {
  611. return 0;
  612. }
  613. return ($aLower < $bLower) ? -1 : 1;
  614. }
  615. /**
  616. * VLOOKUP
  617. * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number.
  618. * @param lookup_value The value that you want to match in lookup_array
  619. * @param lookup_array The range of cells being searched
  620. * @param index_number The column number in table_array from which the matching value must be returned. The first column is 1.
  621. * @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
  622. * @return mixed The value of the found cell
  623. */
  624. public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
  625. {
  626. $lookup_value = PHPExcel_Calculation_Functions::flattenSingleValue($lookup_value);
  627. $index_number = PHPExcel_Calculation_Functions::flattenSingleValue($index_number);
  628. $not_exact_match = PHPExcel_Calculation_Functions::flattenSingleValue($not_exact_match);
  629. // index_number must be greater than or equal to 1
  630. if ($index_number < 1) {
  631. return PHPExcel_Calculation_Functions::VALUE();
  632. }
  633. // index_number must be less than or equal to the number of columns in lookup_array
  634. if ((!is_array($lookup_array)) || (empty($lookup_array))) {
  635. return PHPExcel_Calculation_Functions::REF();
  636. } else {
  637. $f = array_keys($lookup_array);
  638. $firstRow = array_pop($f);
  639. if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
  640. return PHPExcel_Calculation_Functions::REF();
  641. } else {
  642. $columnKeys = array_keys($lookup_array[$firstRow]);
  643. $returnColumn = $columnKeys[--$index_number];
  644. $firstColumn = array_shift($columnKeys);
  645. }
  646. }
  647. if (!$not_exact_match) {
  648. uasort($lookup_array, array('self', 'vlookupSort'));
  649. }
  650. $rowNumber = $rowValue = false;
  651. foreach ($lookup_array as $rowKey => $rowData) {
  652. if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) ||
  653. (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && (strtolower($rowData[$firstColumn]) > strtolower($lookup_value)))) {
  654. break;
  655. }
  656. $rowNumber = $rowKey;
  657. $rowValue = $rowData[$firstColumn];
  658. }
  659. if ($rowNumber !== false) {
  660. if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
  661. // if an exact match is required, we have what we need to return an appropriate response
  662. return PHPExcel_Calculation_Functions::NA();
  663. } else {
  664. // otherwise return the appropriate value
  665. return $lookup_array[$rowNumber][$returnColumn];
  666. }
  667. }
  668. return PHPExcel_Calculation_Functions::NA();
  669. }
  670. /**
  671. * HLOOKUP
  672. * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number.
  673. * @param lookup_value The value that you want to match in lookup_array
  674. * @param lookup_array The range of cells being searched
  675. * @param index_number The row number in table_array from which the matching value must be returned. The first row is 1.
  676. * @param not_exact_match Determines if you are looking for an exact match based on lookup_value.
  677. * @return mixed The value of the found cell
  678. */
  679. public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true)
  680. {
  681. $lookup_value = PHPExcel_Calculation_Functions::flattenSingleValue($lookup_value);
  682. $index_number = PHPExcel_Calculation_Functions::flattenSingleValue($index_number);
  683. $not_exact_match = PHPExcel_Calculation_Functions::flattenSingleValue($not_exact_match);
  684. // index_number must be greater than or equal to 1
  685. if ($index_number < 1) {
  686. return PHPExcel_Calculation_Functions::VALUE();
  687. }
  688. // index_number must be less than or equal to the number of columns in lookup_array
  689. if ((!is_array($lookup_array)) || (empty($lookup_array))) {
  690. return PHPExcel_Calculation_Functions::REF();
  691. } else {
  692. $f = array_keys($lookup_array);
  693. $firstRow = array_pop($f);
  694. if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) {
  695. return PHPExcel_Calculation_Functions::REF();
  696. } else {
  697. $columnKeys = array_keys($lookup_array[$firstRow]);
  698. $firstkey = $f[0] - 1;
  699. $returnColumn = $firstkey + $index_number;
  700. $firstColumn = array_shift($f);
  701. }
  702. }
  703. if (!$not_exact_match) {
  704. $firstRowH = asort($lookup_array[$firstColumn]);
  705. }
  706. $rowNumber = $rowValue = false;
  707. foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) {
  708. if ((is_numeric($lookup_value) && is_numeric($rowData) && ($rowData > $lookup_value)) ||
  709. (!is_numeric($lookup_value) && !is_numeric($rowData) && (strtolower($rowData) > strtolower($lookup_value)))) {
  710. break;
  711. }
  712. $rowNumber = $rowKey;
  713. $rowValue = $rowData;
  714. }
  715. if ($rowNumber !== false) {
  716. if ((!$not_exact_match) && ($rowValue != $lookup_value)) {
  717. // if an exact match is required, we have what we need to return an appropriate response
  718. return PHPExcel_Calculation_Functions::NA();
  719. } else {
  720. // otherwise return the appropriate value
  721. return $lookup_array[$returnColumn][$rowNumber];
  722. }
  723. }
  724. return PHPExcel_Calculation_Functions::NA();
  725. }
  726. /**
  727. * LOOKUP
  728. * The LOOKUP function searches for value either from a one-row or one-column range or from an array.
  729. * @param lookup_value The value that you want to match in lookup_array
  730. * @param lookup_vector The range of cells being searched
  731. * @param result_vector The column from which the matching value must be returned
  732. * @return mixed The value of the found cell
  733. */
  734. public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null)
  735. {
  736. $lookup_value = PHPExcel_Calculation_Functions::flattenSingleValue($lookup_value);
  737. if (!is_array($lookup_vector)) {
  738. return PHPExcel_Calculation_Functions::NA();
  739. }
  740. $lookupRows = count($lookup_vector);
  741. $l = array_keys($lookup_vector);
  742. $l = array_shift($l);
  743. $lookupColumns = count($lookup_vector[$l]);
  744. if ((($lookupRows == 1) && ($lookupColumns > 1)) || (($lookupRows == 2) && ($lookupColumns != 2))) {
  745. $lookup_vector = self::TRANSPOSE($lookup_vector);
  746. $lookupRows = count($lookup_vector);
  747. $l = array_keys($lookup_vector);
  748. $lookupColumns = count($lookup_vector[array_shift($l)]);
  749. }
  750. if (is_null($result_vector)) {
  751. $result_vector = $lookup_vector;
  752. }
  753. $resultRows = count($result_vector);
  754. $l = array_keys($result_vector);
  755. $l = array_shift($l);
  756. $resultColumns = count($result_vector[$l]);
  757. if ((($resultRows == 1) && ($resultColumns > 1)) || (($resultRows == 2) && ($resultColumns != 2))) {
  758. $result_vector = self::TRANSPOSE($result_vector);
  759. $resultRows = count($result_vector);
  760. $r = array_keys($result_vector);
  761. $resultColumns = count($result_vector[array_shift($r)]);
  762. }
  763. if ($lookupRows == 2) {
  764. $result_vector = array_pop($lookup_vector);
  765. $lookup_vector = array_shift($lookup_vector);
  766. }
  767. if ($lookupColumns != 2) {
  768. foreach ($lookup_vector as &$value) {
  769. if (is_array($value)) {
  770. $k = array_keys($value);
  771. $key1 = $key2 = array_shift($k);
  772. $key2++;
  773. $dataValue1 = $value[$key1];
  774. } else {
  775. $key1 = 0;
  776. $key2 = 1;
  777. $dataValue1 = $value;
  778. }
  779. $dataValue2 = array_shift($result_vector);
  780. if (is_array($dataValue2)) {
  781. $dataValue2 = array_shift($dataValue2);
  782. }
  783. $value = array($key1 => $dataValue1, $key2 => $dataValue2);
  784. }
  785. unset($value);
  786. }
  787. return self::VLOOKUP($lookup_value, $lookup_vector, 2);
  788. }
  789. }